<!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="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/md5.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>
      img{
        margin-right:5px;
      }
    </style>
  </head>

  <body ng-app="myApp">
    <div class="container" ng-controller="DemoController">
      <h3>Custom Formatting Demo</h3>
      <multiselect placeholder="Select users..." ms-model="multiselect.selected" ms-config="multiselect.config" ms-options="multiselect.options"></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>
 •
          Demo by           <a href="http://urbanoalvarez.es">Alejandro U. Alvarez</a>
 •
                    <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){
          var md5 = CryptoJS.MD5(item.email),
              img = '<img class="img img-circle" src="http://www.gravatar.com/avatar/'+md5+'.jpg?s=32&d=identicon" />';
          
          return $sce.trustAsHtml(img + '<b>' + item.name + '</b> <span class="text-muted">(' + item.email + ')</span>');
        },
        labelTemplate: function(item){
          var md5 = CryptoJS.MD5(item.email),
              img = '<img class="img img-circle" src="http://www.gravatar.com/avatar/'+md5+'.jpg?s=16&d=identicon" />';
              
          return $sce.trustAsHtml(img+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', function() {
    return {
        restrict: 'E',
        scope: {
            msModel: '=',
            msOptions: '=',
            msConfig: '=',
            msChange: '&'
        },
        replace: true,
        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.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 template = $(elem).find('template');
            return  '<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>';
        }
    };
});
.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;
}