<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link href="style.css" rel="stylesheet" />
<script src="script.js"></script>
</head>
<body ng-controller="ctrl">
<div tag-suggest>
<div id="suggest-search" class="inputor" contenteditable="true" strip-br="true" ng-model="suggestContent" suggesteditable></div>
<div id="suggest-wrapper">
<ul id="suggest">
<li class="suggest-item" ng-click="select($event, person)" ng-repeat="person in people | filter:tagsearch">
<img src="http://placehold.it/50x50" height="50px" />
<a href=""><span>{{person.name}}</span></a>
</li>
</ul>
</div>
</div>
<button ng-click="send()">Send</button>
{{suggestContent}}
</body>
</html>
// Code goes here
angular.module('app', [])
.controller('ctrl', function($scope){
$scope.people = [
{id: 1, name: 'A. Barros'},
{id: 2, name: 'D. Piva'},
{id: 3, name: 'C. Silva'},
{id: 4, name: 'M. Romanosque'},
{id: 5, name: 'R. Sousa'},
{id: 6, name: 'G. Souza'},
{id: 7, name: 'A. Silva'}
]
})
.directive('suggesteditable', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
scope.$apply(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && (html == '<br>' || html == '<br class="ng-scope">' )) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
})
.directive('tagSuggest', function($filter, $parse, $compile){
function placeCaretAtEnd(el) {
el.focus();
if (typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
return {
restrict: 'A',
controller: function($scope, $element) {
var input = angular.element($element[0].children[0]);
var word =/@(\w+)/ig; //@abc Match
var suggestBox = angular.element($element[0].children[1]);
$scope.select = function($event, item) {
var old = input.html();
var replace = '<p class="item-container"><span contenteditable="false" class="item-id">' + item.id + '</span><span class="item-name" contenteditable="false"> ' + item.name + '</span> </p> ';
var content = old.replace(word, replace);
input.html(content);
suggestBox.css({display:'none'});
placeCaretAtEnd(input[0]);
}
},
link: function(scope,elm,attrs) {
var input = angular.element(elm[0].children[0]);
var suggestBox = angular.element(elm[0].children[1]);
var start =/@/ig; // @ Match
var word =/@(\w+)/ig; //@abc Match
suggestBox.css({display:'none'});
elm.bind("keyup", function(e) {
var content = input.html(); //Content Box Data
var go = content.match(start); //Content Matching @
var name = content.match(word); //Content Matching @abc
if( go !== null && go.length > 0) {
var search = (name === null) ? null : name[0].replace(go, '');
$parse('tagsearch').assign(scope, search);
scope.$apply();
if (search !== null && search.length > 0) {
suggestBox.css({display:'block'});
} else {
suggestBox.css({display:'none'});
}
} else {
suggestBox.css({display:'none'});
}
});
}
}
})
#suggest-wrapper {
position:absolute;
width:100%;
z-index: 10;
}
#suggest {
list-style: none outside none;
margin: auto;
padding: 0;
}
.suggest-item {
background: none repeat scroll 0 0 #EEEEEE;
font-size: 14px;
padding: 7px;
position: relative;
max-width: 250px;
border-bottom: 1px solid #FFFFFF;
cursor: pointer;
display: block;
}
.suggest-item a:hover {
color:#ccc;
}
.suggest-item span {
left: 70px;
position: absolute;
top: 23px;
}
.item-container {display:inline;}
.item-id { display:none;}
.item-name{
border:1px solid;
background:#333;
color:white;
padding:0 2px;
font-size:95%;
}
.item-delete {padding:0 6px; color:red; cursor:pointer;}
.a {color: red;}
.inputor {
background: none repeat scroll 0 0 #FFFFFF;
border: 1px solid #DADADA;
border-radius: 4px;
font-size: inherit;
min-height: 40px;
margin: 10px 0 0;
outline: 0 none;
overflow-y: scroll;
padding: 5px 8px;
max-width: 90%;
}
http://ichord.github.io/At.js/