<!DOCTYPE html>
<html ng-app="plunker">
<head>
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.1/css/selectize.css">
<link rel=stylesheet href="style.css">
</head>
<body ng-controller="MainCtrl">
<h2 class='header'>Tagging</h2>
<selectize config="tagsConfig" options='tagOptions' ng-model="tags"></selectize>
<p>
<b>ngModel:</b>
{{tags}}
<br>
<b>options:</b>
{{tagOptions}}
</p>
<br>
<h2 class='header'>E-mail Contacts</h2>
<button ng-click="addEmail()">Add an e-mail</button>
<selectize placeholder='Pick some people...' config="emailsConfig" options="emailsOptions" class='contacts' ng-model="emails"></selectize>
<div>
<b>ngModel:</b>
{{emails}}<br>
<b>options:</b>
<pre>{{emailsOptions | json}}</pre>
</div>
<br>
<h2 class='header'>Single Item Select</h2>
<input type="checkbox" ng-model="display">Enable
<button ng-click="addPerson()">Add a person</button>
<selectize placeholder='Select a person...' config="singleConfig" options="singleItem.options" ng-model="singleItem.single" ng-if="display"></selectize>
<p>
<b>ngModel:</b>
{{singleItem.single}} {{getAge()}}
</p>
<br>
<form name='myForm'>
<h2 class='header'>Angular Form Bindings</h2>
<selectize placeholder='Pick some things...' options='myOptions' config="myConfig" ng-model="myModel" ng-disabled='disable' required></selectize>
<br>
<input type='checkbox' ng-model='disable'>Disable
<div>
<b>ngModel:</b>
<pre>{{myModel |json}}</pre>
<br>
<b>myForm.$valid:</b>
<pre>{{myForm.$valid | json}}</pre>
<b>myForm.$dirty:</b>
<pre>{{myForm.$dirty | json}}</pre>
<b>Async option loading:</b>
<pre>{{myOptions | json}}</pre>
</div>
</form>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.1/js/standalone/selectize.js"></script>
<script src="http://code.angularjs.org/1.3.8/angular.js" type="text/javascript"></script>
<script src="angular-selectize.js"></script>
<script src="app.js"></script>
</body>
</html>
/* Styles go here */
body{
font-family: 'Open Sans', Helvetica, arial, sans-serif;
font-size: 14px;
}
.header {
font-size: 20px;
font-weight: 600;
padding-top: 0;
margin-bottom: 10px;
}
b{
font-weight: normal;
margin-bottom: 10px;
}
pre{
background: rgb(239, 239, 239);
font-size: 10px;
}
.ng-invalid{
border-color: red;
}
/**
* Email Contacts
*/
.selectize-control.contacts .selectize-input [data-value] .email {
opacity: 0.5;
}
.selectize-control.contacts .selectize-input [data-value] .name + .email {
margin-left: 5px;
}
.selectize-control.contacts .selectize-input [data-value] .email:before {
content: '<';
}
.selectize-control.contacts .selectize-input [data-value] .email:after {
content: '>';
}
.selectize-control.contacts .selectize-dropdown .caption {
font-size: 12px;
display: block;
opacity: 0.5;
}
angular-selectize2
==================
This is an Angular.js directive for Brian Reavis's Selectize. It supports 2-way model binding, ng-required, and 2-way binding of the options.
https://github.com/machineboy2045/angular-selectize
/**
* Angular Selectize2
* https://github.com/machineboy2045/angular-selectize
**/
angular.module('selectize', []).value('selectizeConfig', {}).directive("selectize", ['selectizeConfig', function(selectizeConfig) {
return {
restrict: 'EA',
require: '^ngModel',
scope: { ngModel: '=', config: '=?', options: '=?', ngDisabled: '=', ngRequired: '&' },
link: function(scope, element, attrs, modelCtrl) {
Selectize.defaults.maxItems = null; //default to tag editor
var selectize,
config = angular.extend({}, Selectize.defaults, selectizeConfig, scope.config);
modelCtrl.$isEmpty = function(val) {
return val === undefined || val === null || !val.length; //override to support checking empty arrays
};
function createItem(input) {
var data = {};
data[config.labelField] = input;
data[config.valueField] = input;
return data;
}
function addOptions(options) {
angular.forEach(options, function(opt) { selectize.registerOption(opt); });
}
function toggle(disabled) {
disabled ? selectize.disable() : selectize.enable();
}
var validate = function() {
var isInvalid = (scope.ngRequired() || attrs.required || config.required) && modelCtrl.$isEmpty(scope.ngModel);
modelCtrl.$setValidity('required', !isInvalid);
};
function normalizeOptions(data) {
if (!data)
return [];
data = angular.isArray(data) || angular.isObject(data) ? data : [data]
angular.forEach(data, function(opt, key) {
if (typeof opt === 'string') {
data[key] = createItem(opt);
}
});
return data;
}
function updateSelectize() {
validate();
selectize.$control.toggleClass('ng-valid', modelCtrl.$valid);
selectize.$control.toggleClass('ng-invalid', modelCtrl.$invalid);
selectize.$control.toggleClass('ng-dirty', modelCtrl.$dirty);
selectize.$control.toggleClass('ng-pristine', modelCtrl.$pristine);
if (!angular.equals(selectize.items, scope.ngModel)) {
selectize.addOption(normalizeOptions(angular.copy(scope.ngModel)));
selectize.setValue(scope.ngModel, true);
}
}
var onChange = config.onChange,
onOptionAdd = config.onOptionAdd,
onOptionRemove = config.onOptionRemove;
config.onChange = function() {
if (scope._disableOnChange)
return;
if (!angular.equals(selectize.items, scope.ngModel))
scope.$evalAsync(function() {
var value = angular.copy(selectize.items);
if (config.maxItems == 1) {
value = value[0]
}
modelCtrl.$setViewValue( value );
});
if (onChange) {
onChange.apply(this, arguments);
}
};
config.onOptionAdd = function(value, data) {
if (scope.options.indexOf(data) === -1) {
scope._disableWatchOptions = true;
scope.options.push(data);
if (onOptionAdd) {
onOptionAdd.apply(this, arguments);
}
}
};
config.onOptionRemove = function(value) {
var idx = -1;
for (var i = 0; i < scope.options.length; i++) {
if (scope.options[i][config.valueField] === value) {
idx = i;
break;
}
}
if (idx === -1)
return;
scope._disableWatchOptions = true;
scope.options.splice(idx, 1);
if (onOptionRemove) {
onOptionRemove.apply(this, arguments);
}
};
if (scope.options) {
// replace string options with generated options while retaining a reference to the same array
normalizeOptions(scope.options);
} else {
// default options = [ngModel] if no options specified
scope.options = normalizeOptions(angular.copy(scope.ngModel));
}
var angularCallback = config.onInitialize;
config.onInitialize = function() {
selectize = element[0].selectize;
addOptions(scope.options);
selectize.setValue(scope.ngModel, true);
//provides a way to access the selectize element from an
//angular controller
if (angularCallback) {
angularCallback(selectize);
}
scope.$watch('options', function() {
if (scope._disableWatchOptions) {
scope._disableWatchOptions = false;
return;
}
scope._disableOnChange = true;
selectize.clearOptions();
addOptions(scope.options);
selectize.setValue(scope.ngModel);
scope._disableOnChange = false;
}, true);
scope.$watchCollection('ngModel', updateSelectize);
scope.$watch('ngDisabled', toggle);
};
element.selectize(config);
element.on('$destroy', function() {
if (selectize) {
selectize.destroy();
element = null;
}
});
}
};
}]);
var app = angular.module('plunker', ['selectize']);
app.controller('MainCtrl', function($scope, $timeout) {
$scope.disable = false;
$scope.display = true;
function monkeyPatch(selectize)
{
// Fix unwanted behavior for Selectize. Warning: might have side effects.
selectize.$control.off('mousedown');
selectize.$control_input.off('mousedown');
selectize.$control.on('mousedown', function()
{
// We click on the control, so we have to set the focus on it!
// Even if openOnFocus is false. Which is more to control the default focus (first item of a forum)
// or focus on keyboard navigation (tab).
selectize.isFocused = false; // Set focus
selectize.onMouseDown.apply(selectize, arguments);
selectize.isFocused = true; // Open drop-down if needed
return selectize.onMouseDown.apply(selectize, arguments);
});
selectize.$control_input.on('mousedown', function(e)
{
e.stopPropagation();
if (selectize.settings.mode === 'single')
{
// toggle dropdown
if (selectize.isOpen) selectize.close(); else selectize.open();
}
});
}
//=======================================================
//Tagging
//=======================================================
$scope.tagOptions = [ 'Awesome', 'Neat', 'Stupendous' ];
$scope.tags = ['Awesome', 'Great'];
$scope.tagsConfig = {
delimiter: ',',
persist: false,
plugins: [ 'remove_button' ],
create: function(input) {
return {
value: input,
text: input
}
},
onInitialize: function(selectize){
console.log('Initialized', selectize);
},
onChange: function(value){
console.log('T onChange', value);
}
}
//=======================================================
//Email Contacts
//=======================================================
$scope.emails;
var REGEX_EMAIL = '([a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@' +
'(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)';
$scope.emailsOptions = [
{email: 'brian@thirdroute.com', name: 'Brian Reavis'},
{email: 'nikola@tesla.com', name: 'Nikola Tesla'},
{email: 'someone@gmail.com'}
];
$scope.emailsConfig = {
persist: false,
maxItems: null,
valueField: 'email',
labelField: 'name',
plugins: [ 'remove_button' ],
searchField: ['name', 'email'],
render: {
item: function(item, escape) {
return '<div>' +
(item.name ? '<span class="name">' + escape(item.name) + '</span>' : '') +
(item.email ? '<span class="email">' + escape(item.email) + '</span>' : '') +
'</div>';
},
option: function(item, escape) {
var label = item.name || item.email;
var caption = item.name ? item.email : null;
return '<div>' +
'<span class="label">' + escape(label) + '</span>' +
(caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
'</div>';
}
},
createFilter: function(input) {
var match, regex;
// email@address.com
regex = new RegExp('^' + REGEX_EMAIL + '$', 'i');
match = input.match(regex);
if (match) return !this.options.hasOwnProperty(match[0]);
// name <email@address.com>
regex = new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i');
match = input.match(regex);
if (match) return !this.options.hasOwnProperty(match[2]);
return false;
},
create: function(input) {
if ((new RegExp('^' + REGEX_EMAIL + '$', 'i')).test(input)) {
return {email: input};
}
var match = input.match(new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i'));
if (match) {
return {
email : match[2],
name : $.trim(match[1])
};
}
alert('Invalid email address.');
return false;
}
}
$scope.addEmail = function() {
$scope.emailsOptions.push({ email: 'frank@einstein.com', name: 'Frank Einstein' });
};
//=======================================================
//Single Item Select
//=======================================================
var someAjaxData = {
'A': { key: 'C', name: 'Chuck Testa', age: 20 },
'B': { key: 'N', name: 'Nikola Tesla', age: 217 },
'C': { key: 'R', name: 'Rosa Testa', age: 8 }
};
$scope.singleItem = { single: null, options: someAjaxData };
$scope.singleConfig = {
// options: [{value: 1, text: 'Chuck Testa'}, {value: 2, text:'Nikola Tesla'}],
// create: true,
openOnFocus: false,
valueField: 'key',
labelField: 'name',
sortField: 'name',
maxItems: 1,
onInitialize: function(selectize)
{
monkeyPatch(selectize);
},
onChange: function(value){
console.log('I onChange', value);
}
};
$scope.getAge = function() {
var key = $scope.singleItem.single;
if (!key)
return -1;
// console.log(someAjaxData);
for (var opt in someAjaxData) {
if (someAjaxData[opt].key === key)
return someAjaxData[opt].age;
}
return -1;
};
$scope.addPerson = function() {
$scope.singleItem.options['D'] = { value: 'F', text: 'Frank Einstein' };
};
//=======================================================
//Angular Form Bindings
//=======================================================
$scope.myModel;
$scope.myOptions = [];
$scope.myConfig = {
create: true,
// required: true,
}
//simulate async option loading
$timeout(function(){
for(var i = 0; i < 10; i++){
$scope.myOptions.push({value: i, text: 'Option '+i})
}
}, 1000)
});