<!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)
  
  
});