<!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.bootstrap3.css">
<link rel=stylesheet href="style.css">
</head>
<body ng-controller="MainCtrl">
<h2 class='header'>Tagging</h2>
<button ng-click="addSuperlative()">Add a superlative</button><button ng-click="removeSuperlative()">Remove last superlative</button>
<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.5.0/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
**/
// Included in the project until my (PLH) pull request is accepted...
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) {
var selectize,
settings = angular.extend({}, Selectize.defaults, selectizeConfig, scope.config);
scope.options = scope.options || [];
scope.config = scope.config || {};
var isEmpty = function(val) {
return val === undefined || val === null || val.length === 0; // support checking empty arrays / strings
};
var toggle = function(disabled) {
disabled ? selectize.disable() : selectize.enable();
};
var validate = function() {
var isInvalid = (scope.ngRequired() || attrs.required || settings.required) && isEmpty(scope.ngModel);
modelCtrl.$setValidity('required', !isInvalid);
};
var setSelectizeOptions = function(curr, prev) {
if (!curr)
return;
if (scope._noUpdate) { // Internal changes to scope.options, eschew the watch mechanism
scope._noUpdate = false;
return;
}
scope._skipRemove = true;
angular.forEach(prev, function(opt) {
if (curr.indexOf(opt) === -1) {
var value = opt[settings.valueField];
selectize.removeOption(value, true);
}
});
scope._skipRemove = false;
angular.forEach(curr, function(opt) {
selectize.registerOption(opt);
});
selectize.lastQuery = undefined; // Hack because of a Selectize bug...
selectize.refreshOptions(false); // Update the content of the drop-down
// setSelectizeValue();
};
var setSelectizeValue = function() {
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)) {
if (scope.config.create && angular.isArray(scope.ngModel)) {
// Items might be in model but not in options: we create them in both, as user options
for (var i = 0; i < scope.ngModel.length; i++) {
selectize.createItem(scope.ngModel[i], false);
}
} else {
selectize.setValue(scope.ngModel, true);
}
}
};
settings.onChange = function(value) {
var items = angular.copy(selectize.items);
if (settings.maxItems === 1) {
items = items[0];
}
modelCtrl.$setViewValue(items);
if (scope.config.onChange) {
scope.config.onChange.apply(this, arguments);
}
};
// User entered a new tag.
settings.onOptionAdd = function(value, data) {
if (scope.options.indexOf(data) === -1) {
scope._noUpdate = true;
scope.options.push(data);
}
if (scope.config.onOptionAdd) {
scope.config.onOptionAdd.apply(this, arguments);
}
};
// User removed a tag they entered, or we called removeOption().
// Note: it is not called if persist is true.
settings.onOptionRemove = function(value) {
if (scope._skipRemove)
return;
var idx = -1;
for (var i = 0; i < scope.options.length; i++) {
if (scope.options[i][scope.config.valueField] === value) {
idx = i;
break;
}
}
if (idx !== -1) {
scope._noUpdate = true;
scope.options.splice(idx, 1);
}
if (scope.config.onOptionRemove) {
scope.config.onOptionRemove.apply(this, arguments);
}
};
settings.onInitialize = function() {
selectize = element[0].selectize;
setSelectizeOptions(scope.options);
//provides a way to access the selectize element from an
//angular controller
if (scope.config.onInitialize) {
scope.config.onInitialize(selectize);
}
scope.$watchCollection('options', setSelectizeOptions);
scope.$watch('ngModel', setSelectizeValue);
scope.$watch('ngDisabled', toggle);
};
element.selectize(settings);
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.
// Note: this monkey patch should be applied only to single selection mode, not in tag mode:
// the second patch (mousedown) is just unnecessary,
// the first one (mousedown) disables selection of tags by clicking on them!
selectize.$control.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); // Original call
});
selectize.$control_input.off('mousedown');
selectize.$control_input.on('mousedown', function(e)
{
// When we click on the input area (instead of the arrow), we want the drop-down to be toggled too.
e.stopPropagation(); // Original call
if (selectize.settings.mode === 'single')
{
// toggle dropdown
if (selectize.isOpen) selectize.close(); else selectize.open();
}
});
//*/
}
//=======================================================
//Tagging
//=======================================================
$scope.tagOptions = [ { name: 'Awesome', rank: 5 }, { name: 'Neat', rank: 2 }, { name: 'Stupendous', rank: 10 } ];
// Second one isn't in tagOptions, so will be added as used option:
// in the list of items, but if removed it won't be in the drop-down.
$scope.tags = [ 'Awesome', 'Interesting' ];
$scope.tagsConfig = {
maxItems: null, // Tags
delimiter: ',', // Alternative to Enter / Tab to enter new tags
persist: false, // User entered options won't appear in the drop-down list when removed.
valueField: 'name', // Used as id, unique identifier of the options. Thus, no duplicate name.
labelField: 'name', // Option field to display as tag.
searchField: [ 'name' ], // We search on this (these) fields.
plugins: [ 'remove_button' ],
// onInitialize: function(selectize)
// {
// monkeyPatch(selectize);
// },
create: function(input) {
return {
rank: 1,
name: input
}
},
onChange: function(value){
console.log('T onChange', value);
}
}
// Test programmatic changes to list of options, by altering the array given to the options attribute.
// Thus distinguish user-entered options (not in drop-down list when removed, if not persisted)
// from original / program entered options.
// To programmatically add user options, take the Selectize instance from onInitialize
// and call selectize.addOption(oneOptionOrListOfOptions);
// Or, in my latest change, just add it to the item list (here, tags).
$scope.addSuperlative = function() {
$scope.tagOptions.push({ name: 'Great', rank: 3 });
};
// Works only if the last superlative is not in the item list, otherwise it will be added back!
// As a user option...
$scope.removeSuperlative = function() {
$scope.tagOptions.shift();
};
//=======================================================
//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 = [
{ key: 'C', name: 'Chuck Testta', age: 20 },
{ key: 'N', name: 'Nikola Tesla', age: 217 },
{ key: 'R', name: 'Rosa Testa', age: 8 }
];
$scope.singleItem = { single: 'N', options: someAjaxData };
$scope.singleConfig = {
// options: [{value: 1, text: 'Chuck Testa'}, {value: 2, text:'Nikola Tesla'}],
// create: true,
openOnFocus: false, // Illustrate monkeypatch!
valueField: 'key',
labelField: 'name',
searchField: [ 'name' ],
sortField: 'name',
maxItems: 1,
onInitialize: function(selectize)
{
monkeyPatch(selectize);
},
onChange: function(value){
console.log('I onChange', value);
}
};
$scope.addPerson = function() {
$scope.singleItem.options.push({ key: 'F', name: 'Frank Einstein', age: 3 });
};
//=======================================================
//Angular Form Bindings
//=======================================================
$scope.myModel;
$scope.myOptions = [];
$scope.myConfig = {
create: true,
// required: true,
}
//simulate async option loading
$timeout(function(){
for(var i = 0; i < 31; i++){
$scope.myOptions.push({value: i, text: 'Option '+i})
}
}, 1000)
});