<!DOCTYPE html>
<html>

  <head>
    <link data-require="bootstrap@*" data-semver="3.3.2" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
    <link data-require="bootstrap@*" data-semver="3.3.2" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" />
    <link rel="stylesheet" href="ng-multiselect.css">
    
    <script data-require="jquery@2.1.3" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
    <script data-require="bootstrap@*" data-semver="3.3.2" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
    <script data-require="angular.js@1.3.8" data-semver="1.3.8" src="https://code.angularjs.org/1.3.8/angular.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-sanitize.js"></script>
    <script src="ng-multiselect.js"></script>
    <script src="app.js"></script>
    <style>
      .user{
        color: #0074D9;
      }
    </style>
  </head>

  <body ng-app="myApp">
    <div class="container" ng-controller="DemoController">
        <h3>Custom Template Demo</h3>
        <script type="text/ng-template" id="custom-template.html">
            <div class="multiselect">
                <div class="multiselect-labels">
                  <span class="label label-primary multiselect-labels-lg" ng-repeat="element in msModel">
                    <b>CUSTOM: </b>
                    <span ng-bind-html="config.labelTemplate(element)"></span>
                    <a href="" ng-click="$event.preventDefault(); multiselect.deleteSelected($index)" title="Remove element"> <i class="fa fa-times"></i> </a>
                  </span>
                </div>
                <input ng-show="multiselect.options.length > 0" placeholder="Select users..." type="text" class="form-control" ng-model="multiselect.filter" ng-focus="multiselect.focusFilter()" ng-blur="multiselect.blurFilter()" />
                <div ng-show="msModel.length < multiselect.options.length && multiselect.options.length === 0 && !multiselect.options.$resolved"><em>Loading...</em></div>
                <ul class="dropdown-menu" role="menu" ng-show="multiselect.displayDropdown && multiselect.options.length > 0">
                    <li ng-repeat="element in multiselect.filtered = (multiselect.options | filter:multiselect.filter) track by $index" role="presentation" ng-class="{active: $index == multiselect.currentElement}">
                        <a href="" role="menuitem" ng-click="$event.preventDefault(); multiselect.selectElement($index)" ng-bind-html="config.itemTemplate(element)"></a>
                    </li>
                </ul>
            </div>
        </script>
        <multiselect placeholder="Select users..." ms-model="multiselect.selected" ms-config="multiselect.config" ms-options="multiselect.options" template-url="custom-template.html">
        </multiselect>
        <hr>
        <small class="text-muted">
        <p><b>Selected users:</b> {{displayUsers()}}</p>
        <p class="text-center">
          <a href="https://github.com/aurbano/ng-multiselect">ng-multiselect</a> &bull;
          Demo by <a href="http://urbanoalvarez.es">Alejandro U. Alvarez</a> &bull;
          <em>Test data from jsonplaceholder.typicode.com</em>
        </p>
      </small>
    </div>
</body>

</html>
# [Angular Multiselect](https://github.com/aurbano/ng-multiselect)

Angular directive for multiselect elements with a nice UX.

## Getting started

1. Download [ng-multiselect.js](https://github.com/aurbano/ng-multiselect/blob/master/dist/ng-multiselect.js) and [ng-multiselect.css](https://github.com/aurbano/ng-multiselect/blob/master/dist/ng-multiselect.css) and add to your project. Or get automatically:

    ```bash
    bower install ng-multiselect
    ```

2. Add the module to the app:
    ```js
    angular.module('myApp', ['aurbano.multiselect'])
    ```
3. Profit!

## Usage

Add the element in the view:

```html
<multiselect placeholder="Select users..."
             ms-model="multiselect.selected"
             ms-config="multiselect.config"
             ms-options="multiselect.options">
</multiselect>
```

The parameters are:
* `ms-model`: The model where the selection will be stored.
* `ms-config`: Configuration object to customize the behavior (see below).
* `ms-options`: Array with options to offer.

### Customization
Using the `ms-config` you can pass an object with the following configuration options:

* **`hideOnBlur`**: Only show the dropdown when the input is focused
* **`showSelected`**: Show the elements that have already been selected
* **`itemTemplate`**: function that takes each element from ms-options and returns what should be displayed on the list. It defaults to the element itself. Use sce.trustAsHtml if you want to add custom HTML here.
* **`labelTemplate`**: function that takes each selected element, and displays it on the label list. This goes inside the `<span class="label"></span>`. Use `$sce.trustAsHtml()` if you want to add custom HTML here.

## Demo
### [Custom Formatting](http://embed.plnkr.co/s2GFxXBXf1vderC0XCvP/)

### [Dynamic data](http://embed.plnkr.co/TrwUBp1odAYmBPRarK5k/)

-----------
*By [Alejandro U. Alvarez](http://urbanoalvarez.es) - MIT License*

[![Analytics](https://ga-beacon.appspot.com/UA-3181088-16/ng-multiselect/readme)](https://github.com/aurbano)
angular.module('myApp', ['aurbano.multiselect'])
        .controller('DemoController', ['$scope', '$http', '$sce',
            function($scope, $http, $sce) {

                $scope.multiselect = {
                    selected: [],
                    options: [],
                    config: {
                        hideOnBlur: false,
                        showSelected: false,
                        itemTemplate: function(item) {
                            return $sce.trustAsHtml(item.name + ' (' + item.email + ')');
                        },
                        labelTemplate: function(item) {
                            return $sce.trustAsHtml(item.name);
                        }
                    }
                };

                $scope.displayUsers = function() {
                    return $scope.multiselect.selected.map(function(each) {
                        return each.name;
                    }).join(', ');
                };

                $http.get('http://jsonplaceholder.typicode.com/users')
                    .success(function(data) {
                        // Prepare the fake data
                        $scope.multiselect.options = data.map(function(item) {
                            return {
                                name: item.name,
                                email: item.email,
                                id: item.id
                            };
                        });

                    }).error(function(err) {
                        console.error(err);
                    });

            }
        ]);
/**
 * Angular Multiselect
 * Directive for multiselect fields, with labels for the selected items
 *
 * Usage:
 * <multiselect placeholder="Placeholder for the input"
 *              ms-model="model"
 *              ms-options="select options"
 *              ms-config="configObject"
 *              ms-change="changeFunction(newVal, oldVal)"/>
 *
 * ms-config takes an object with the following optional options (all booleans are false by default):
 *      - hideOnBlur: Only show the dropdown when the input is focused
 *      - showSelected: Show the elements that have already been selected
 *      - itemTemplate: function that takes each element from ms-options and returns what should be displayed on the list.
 *                      defaults to the element itself. Use sce.trustAsHtml if you want to add custom HTML here.
 *      - labelTemplate: function that takes each selected element, and displays it on the label list. This goes inside the
 *                       <span class="label"></span>. Use sce.trustAsHtml if you want to add custom HTML here.
 *
 * @author Alejandro U. Alvarez
 * @homepage https://github.com/aurbano/ng-multiselect
 */
angular.module('aurbano.multiselect', [])
  .directive('multiselect', ['$compile', '$templateCache', function($compile, $templateCache) {
    'use strict';

    return {
        restrict: 'E',
        scope: {
            msModel: '=',
            msOptions: '=',
            msConfig: '=',
            msChange: '&'
        },
        controller: ['$scope',
            function($scope){
                /* -------------------------------- *
                 *      Configuration defaults      *
                 * -------------------------------- */
                $scope.config = $scope.msConfig;
                if(typeof($scope.config) === 'undefined'){
                    $scope.config = {};
                }
                if(typeof($scope.config.itemTemplate) === 'undefined'){
                    $scope.config.itemTemplate = function(elem){
                        return elem;
                    };
                }
                if(typeof($scope.config.labelTemplate) === 'undefined'){
                    $scope.config.labelTemplate = function(elem){
                        return elem;
                    };
                }
                if(typeof($scope.config.modelFormat) === 'undefined'){
                    $scope.config.modelFormat = function(elem){
                        return elem;
                    };
                }
                $scope.$watch('msOptions', function(){
                    _filterOptions();
                });

                /* -------------------------------- *
                 *          Multiselect setup       *
                 * -------------------------------- */
                if($scope.msModel === null || typeof($scope.msModel) === 'undefined'){
                    $scope.msModel = [];
                }
                if($scope.msOptions === null || typeof($scope.msOptions) === 'undefined'){
                    $scope.msOptions = [];
                }
                $scope.multiselect = {
                    filter: '',
                    filtered: [],
                    options: $scope.msOptions,
                    displayDropdown: true,
                    deleteSelected: function(index){
                        var oldVal = angular.copy($scope.msModel);

                        $scope.msModel.splice(index, 1);

                        $scope.msChange({
                            newVal: $scope.msModel,
                            oldVal: oldVal
                        });

                        _filterOptions();
                    },
                    selectElement: function(index){
                        var oldVal = angular.copy($scope.msModel);

                        $scope.msModel.push($scope.multiselect.filtered[index]);

                        $scope.msChange({
                            newVal: $scope.msModel,
                            oldVal: oldVal
                        });

                        $scope.multiselect.filter = '';

                        _filterOptions();
                    },
                    focusFilter: function(){
                        $scope.multiselect.currentElement = 0;
                        if($scope.config.hideOnBlur){
                            $scope.multiselect.displayDropdown = true;
                        }
                    },
                    blurFilter: function(){
                        $scope.multiselect.currentElement = null;
                        if($scope.config.hideOnBlur){
                            $scope.multiselect.displayDropdown = false;
                        }
                    },
                    currentElement: null
                };

                if($scope.config.hideOnBlur){
                    $scope.multiselect.displayDropdown = false;
                }

                /* -------------------------------- *
                 *           Helper methods         *
                 * -------------------------------- */

                var _filterOptions = function(){
                    if($scope.msModel === null){
                        $scope.multiselect.options = $scope.msOptions;
                        return;
                    }
                    $scope.multiselect.options = $scope.msOptions.filter(function(each){
                        return $scope.msModel.indexOf(each) < 0;
                    });
                };
            }
        ],
        link: function(scope, element, attrs){
            //element.html('<div ng-include="ng-multiselect-view.html"></div>');
            if(!attrs.templateUrl){
              var template = '<div class="multiselect">' +
                             '    <div class="multiselect-labels">' +
                             '         <span class="label label-default multiselect-labels-lg" ng-repeat="element in msModel">' +
                             '             <span ng-bind-html="config.labelTemplate(element)"></span>' +
                             '             <a href="" ng-click="$event.preventDefault(); multiselect.deleteSelected($index)" title="Remove element">' +
                             '                 <i class="fa fa-times"></i>' +
                             '             </a>' +
                             '          </span>'+
                             '    </div>' +
                             '    <input ng-show="multiselect.options.length > 0" placeholder="'+attrs.placeholder+'" type="text" class="form-control" ng-model="multiselect.filter" ng-focus="multiselect.focusFilter()" ng-blur="multiselect.blurFilter()" />' +
                             '    <div ng-show="msModel.length < multiselect.options.length && multiselect.options.length === 0 && !multiselect.options.$resolved"><em>Loading...</em></div>'+
                             '    <ul class="dropdown-menu" role="menu" ng-show="multiselect.displayDropdown && multiselect.options.length > 0">' +
                             '      <li ng-repeat="element in multiselect.filtered = (multiselect.options | filter:multiselect.filter) track by $index" role="presentation" ng-class="{active: $index == multiselect.currentElement}">' +
                             '        <a href="" role="menuitem" ng-click="$event.preventDefault(); multiselect.selectElement($index)" ng-bind-html="config.itemTemplate(element)"></a>' +
                             '      </li>' +
                             '    </ul>' +
                             '</div>';
              var templateWrapped = '<script type="text/ng-template" id="ng-multiselect-view.html">' +
                                      template +
                                    '</script';
              
              $compile(templateWrapped)(scope);
              element.after(templateWrapped);

              $templateCache.put('ng-multiselect-view.html', template);
            }

            // Compile the container
            //$compile(element)(scope);
            
            //Pass the attributes to the template
            scope.attrs = attrs;
            element.on('keydown', function(e){
                var code = e.keyCode || e.which;
                switch(code){
                    case 40: // Down arrow
                        scope.$apply(function(){
                            scope.multiselect.currentElement++;
                            if(scope.multiselect.currentElement >= scope.multiselect.filtered.length){
                                scope.multiselect.currentElement = 0;
                            }
                        });
                        break;
                    case 38: // Up arrow
                        scope.$apply(function(){
                            scope.multiselect.currentElement--;
                            if(scope.multiselect.currentElement < 0){
                                scope.multiselect.currentElement = scope.multiselect.filtered.length - 1;
                            }
                        });
                        break;
                    case 13: // Enter
                        scope.$apply(function(){
                            scope.multiselect.selectElement(scope.multiselect.currentElement);
                        });
                        break;
                    default:
                        scope.$apply(function(){
                            scope.multiselect.currentElement = 0;
                        });
                }
            });
        },
        template: function(elem, attrs){
          var url = attrs.templateUrl || 'ng-multiselect-view.html';
          return '<div ng-include="\''+url+'\'"></div>';
        }
    };
}]);
.multiselect .dropdown-menu{
    position: static;
    box-shadow: none;
    display: block;
    float:none;
    max-height:300px;
    overflow:auto;
}
.multiselect-labels a .fa{
    color: #ccc;
}
.multiselect-labels a .fa:hover{
    color: #fff;
}
.multiselect-labels .label{
    margin-right: 5px;
    margin-bottom: 5px;
    display: inline-block;
    padding: 4px 6px;
}
.multiselect .dropdown-menu > li > a{
    white-space: normal;
}