text: uis.directive('uiSelectMultiple', ['uiSelectMinErr', '$timeout', function (uiSelectMinErr, $timeout) {
      return {
          restrict: 'EA',
          require: ['^uiSelect', '^ngModel'],
  
          controller: ['$scope', '$timeout', function ($scope, $timeout) {
  
              var ctrl = this,
                  $select = $scope.$select,
                  ngModel;
  
              //Wait for link fn to inject it
              $scope.$evalAsync(function () {
                  ngModel = $scope.ngModel;
              });
  
              ctrl.activeMatchIndex = -1;
  
              ctrl.updateModel = function () {
                  ngModel.$setViewValue(Date.now()); //Set timestamp as a unique string to force changes
                  ctrl.refreshComponent();
              };
  
              ctrl.refreshComponent = function () {
                  //Remove already selected items
                  //e.g. When user clicks on a selection, the selected array changes and
                  //the dropdown should remove that item
                  $select.refreshItems();
                  $select.sizeSearchInput();
              };
  
              // Remove item from multiple select
              ctrl.removeChoice = function (index) {
  
                  var removedChoice = $select.selected[index];
  
                  // if the choice is locked, can't remove it
                  if (removedChoice._uiSelectChoiceLocked) return;
  
                  var locals = {};
                  locals[$select.parserResult.itemName] = removedChoice;
  
                  $select.selected.splice(index, 1);
                  ctrl.activeMatchIndex = -1;
                  $select.sizeSearchInput();
  
                  // Give some time for scope propagation.
                  $timeout(function () {
                      $select.onRemoveCallback($scope, {
                          $item: removedChoice,
                          $model: $select.parserResult.modelMapper($scope, locals)
                      });
                  });
  
                  ctrl.updateModel();
  
              };
  
              ctrl.getPlaceholder = function () {
                  //Refactor single?
                  if ($select.selected && $select.selected.length) return;
                  return $select.placeholder;
              };
  
  
          }],
          controllerAs: '$selectMultiple',
  
          link: function (scope, element, attrs, ctrls) {
  
              var $select = ctrls[0];
              var ngModel = scope.ngModel = ctrls[1];
              var $selectMultiple = scope.$selectMultiple;
  
              //$select.selected = raw selected objects (ignoring any property binding)
  
              $select.multiple = true;
              $select.removeSelected = true;
  
              //Input that will handle focus
              $select.focusInput = $select.searchInput;
  
              //From view --> model
              ngModel.$parsers.unshift(function () {
                  var locals = {},
                      result,
                      resultMultiple = [];
                  for (var j = $select.selected.length - 1; j >= 0; j--) {
                      locals = {};
                      locals[$select.parserResult.itemName] = $select.selected[j];
                      result = $select.parserResult.modelMapper(scope, locals);
                      resultMultiple.unshift(result);
                  }
                  return resultMultiple;
              });
  
              // From model --> view
              ngModel.$formatters.unshift(function (inputValue) {
                  var data = $select.parserResult.source (scope, {$select: {search: ''}}), //Overwrite $search
                      locals = {},
                      result;
                  if (!data) return inputValue;
                  var resultMultiple = [];
                  var checkFnMultiple = function (list, value) {
                      if (!list || !list.length) return;
                      for (var p = list.length - 1; p >= 0; p--) {
                          locals[$select.parserResult.itemName] = list[p];
                          result = $select.parserResult.modelMapper(scope, locals);
                          if ($select.parserResult.trackByExp) {
                              var matches = /\.(.+)/.exec($select.parserResult.trackByExp);
                              if (matches.length > 0 && result[matches[1]] == value[matches[1]]) {
                                  resultMultiple.unshift(list[p]);
                                  return true;
                              }
                          }
                          if (angular.equals(result, value)) {
                              resultMultiple.unshift(list[p]);
                              return true;
                          }
                      }
                      return false;
                  };
                  if (!inputValue) return resultMultiple; //If ngModel was undefined
                  for (var k = inputValue.length - 1; k >= 0; k--) {
                      //Check model array of currently selected items
                      if (!checkFnMultiple($select.selected, inputValue[k])) {
                          //Check model array of all items available
                          if (!checkFnMultiple(data, inputValue[k])) {
                              //If not found on previous lists, just add it directly to resultMultiple
                              resultMultiple.unshift(inputValue[k]);
                          }
                      }
                  }
                  return resultMultiple;
              });
  
              //Watch for external model changes
              scope.$watchCollection(function () {
                  return ngModel.$modelValue;
              }, function (newValue, oldValue) {
                  if (oldValue != newValue) {
                      ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
                      $selectMultiple.refreshComponent();
                  }
              });
  
              ngModel.$render = function () {
                  // Make sure that model value is array
                  if (!angular.isArray(ngModel.$viewValue)) {
                      // Have tolerance for null or undefined values
                      if (angular.isUndefined(ngModel.$viewValue) || ngModel.$viewValue === null) {
                          $select.selected = [];
                      } else {
                          throw uiSelectMinErr('multiarr', "Expected model value to be array but got '{0}'", ngModel.$viewValue);
                      }
                  }
                  $select.selected = ngModel.$viewValue;
                  scope.$evalAsync(); //To force $digest
              };
  
              scope.$on('uis:select', function (event, item) {
                  if ($select.selected.length >= $select.limit) {
                      return;
                  }
                  $select.selected.push(item);
                  $selectMultiple.updateModel();
              });
  
              scope.$on('uis:activate', function () {
                  $selectMultiple.activeMatchIndex = -1;
              });
  
              scope.$watch('$select.disabled', function (newValue, oldValue) {
                  // As the search input field may now become visible, it may be necessary to recompute its size
                  if (oldValue && !newValue) $select.sizeSearchInput();
              });
  
              $select.searchInput.on('keydown', function (e) {
                  var key = e.which;
                  scope.$apply(function () {
                      var processed = false;
                      // var tagged = false; //Checkme
                      if (KEY.isHorizontalMovement(key)) {
                          processed = _handleMatchSelection(key);
                      }
                      if (processed && key != KEY.TAB) {
                          //TODO Check si el tab selecciona aun correctamente
                          //Crear test
                          e.preventDefault();
                          e.stopPropagation();
                      }
                  });
              });
              function _getCaretPosition(el) {
                  if (angular.isNumber(el.selectionStart)) return el.selectionStart;
                  // selectionStart is not supported in IE8 and we don't want hacky workarounds so we compromise
                  else return el.value.length;
              }
  
              // Handles selected options in "multiple" mode
              function _handleMatchSelection(key) {
                  var caretPosition = _getCaretPosition($select.searchInput[0]),
                      length = $select.selected.length,
                  // none  = -1,
                      first = 0,
                      last = length - 1,
                      curr = $selectMultiple.activeMatchIndex,
                      next = $selectMultiple.activeMatchIndex + 1,
                      prev = $selectMultiple.activeMatchIndex - 1,
                      newIndex = curr;
  
                  if (caretPosition > 0 || ($select.search.length && key == KEY.RIGHT)) return false;
  
                  $select.close();
  
                  function getNewActiveMatchIndex() {
                      switch (key) {
                          case KEY.LEFT:
                              // Select previous/first item
                              if (~$selectMultiple.activeMatchIndex) return prev;
                              // Select last item
                              else return last;
                              break;
                          case KEY.RIGHT:
                              // Open drop-down
                              if (!~$selectMultiple.activeMatchIndex || curr === last) {
                                  $select.activate();
                                  return false;
                              }
                              // Select next/last item
                              else return next;
                              break;
                          case KEY.BACKSPACE:
                              // Remove selected item and select previous/first
                              if (~$selectMultiple.activeMatchIndex) {
                                  $selectMultiple.removeChoice(curr);
                                  return prev;
                              }
                              // Select last item
                              else return last;
                              break;
                          case KEY.DELETE:
                              // Remove selected item and select next item
                              if (~$selectMultiple.activeMatchIndex) {
                                  $selectMultiple.removeChoice($selectMultiple.activeMatchIndex);
                                  return curr;
                              }
                              else return false;
                      }
                  }
  
                  newIndex = getNewActiveMatchIndex();
  
                  if (!$select.selected.length || newIndex === false) $selectMultiple.activeMatchIndex = -1;
                  else $selectMultiple.activeMatchIndex = Math.min(last, Math.max(first, newIndex));
  
                  return true;
              }
  
              $select.searchInput.on('keyup', function (e) {
  
                  if (!KEY.isVerticalMovement(e.which)) {
                      scope.$evalAsync(function () {
                          $select.activeIndex = $select.taggingLabel === false ? -1 : 0;
                      });
                  }
                  // Push a "create new" item into array if there is a search string
                  if ($select.tagging.isActivated && $select.search.length > 0) {
  
                      // return early with these keys
                      if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || KEY.isVerticalMovement(e.which)) {
                          return;
                      }
                      // always reset the activeIndex to the first item when tagging
                      $select.activeIndex = $select.taggingLabel === false ? -1 : 0;
                      // taggingLabel === false bypasses all of this
                      if ($select.taggingLabel === false) return;
  
                      var items = angular.copy($select.items);
                      var stashArr = angular.copy($select.items);
                      var newItem;
                      var item;
                      var hasTag = false;
                      var dupeIndex = -1;
                      var tagItems;
                      var tagItem;
  
                      // case for object tagging via transform `$select.tagging.fct` function
                      if ($select.tagging.fct !== undefined) {
                          tagItems = $select.$filter('filter')(items, {'isTag': true});
                          if (tagItems.length > 0) {
                              tagItem = tagItems[0];
                          }
                          // remove the first element, if it has the `isTag` prop we generate a new one with each keyup, shaving the previous
                          if (items.length > 0 && tagItem) {
                              hasTag = true;
                              items = items.slice(1, items.length);
                              stashArr = stashArr.slice(1, stashArr.length);
                          }
                          newItem = $select.tagging.fct($select.search);
                          newItem.isTag = true;
                          // verify the the tag doesn't match the value of an existing item
                          if (stashArr.filter(function (origItem) {
                                  return angular.equals(origItem, $select.tagging.fct($select.search));
                              }).length > 0) {
                              return;
                          }
                          newItem.isTag = true;
                          // handle newItem string and stripping dupes in tagging string context
                      } else {
                          // find any tagging items already in the $select.items array and store them
                          tagItems = $select.$filter('filter')(items, function (item) {
                              return item.match($select.taggingLabel);
                          });
                          if (tagItems.length > 0) {
                              tagItem = tagItems[0];
                          }
                          item = items[0];
                          // remove existing tag item if found (should only ever be one tag item)
                          if (item !== undefined && items.length > 0 && tagItem) {
                              hasTag = true;
                              items = items.slice(1, items.length);
                              stashArr = stashArr.slice(1, stashArr.length);
                          }
                          newItem = $select.search + ' ' + $select.taggingLabel;
                          if (_findApproxDupe($select.selected, $select.search) > -1) {
                              return;
                          }
                          // verify the the tag doesn't match the value of an existing item from
                          // the searched data set or the items already selected
                          if (_findCaseInsensitiveDupe(stashArr.concat($select.selected))) {
                              // if there is a tag from prev iteration, strip it / queue the change
                              // and return early
                              if (hasTag) {
                                  items = stashArr;
                                  scope.$evalAsync(function () {
                                      $select.activeIndex = 0;
                                      $select.items = items;
                                  });
                              }
                              return;
                          }
                          if (_findCaseInsensitiveDupe(stashArr)) {
                              // if there is a tag from prev iteration, strip it
                              if (hasTag) {
                                  $select.items = stashArr.slice(1, stashArr.length);
                              }
                              return;
                          }
                      }
                      if (hasTag) dupeIndex = _findApproxDupe($select.selected, newItem);
                      // dupe found, shave the first item
                      if (dupeIndex > -1) {
                          items = items.slice(dupeIndex + 1, items.length - 1);
                      } else {
                          items = [];
                          items.push(newItem);
                          items = items.concat(stashArr);
                      }
                      scope.$evalAsync(function () {
                          $select.activeIndex = 0;
                          $select.items = items;
                      });
                  }
              });
              function _findCaseInsensitiveDupe(arr) {
                  if (arr === undefined || $select.search === undefined) {
                      return false;
                  }
                  var hasDupe = arr.filter(function (origItem) {
                          if ($select.search.toUpperCase() === undefined || origItem === undefined) {
                              return false;
                          }
                          return origItem.toUpperCase() === $select.search.toUpperCase();
                      }).length > 0;
  
                  return hasDupe;
              }
  
              function _findApproxDupe(haystack, needle) {
                  var dupeIndex = -1;
                  if (angular.isArray(haystack)) {
                      var tempArr = angular.copy(haystack);
                      for (var i = 0; i < tempArr.length; i++) {
                          // handle the simple string version of tagging
                          if ($select.tagging.fct === undefined) {
                              // search the array for the match
                              if (tempArr[i] + ' ' + $select.taggingLabel === needle) {
                                  dupeIndex = i;
                              }
                              // handle the object tagging implementation
                          } else {
                              var mockObj = tempArr[i];
                              mockObj.isTag = true;
                              if (angular.equals(mockObj, needle)) {
                                  dupeIndex = i;
                              }
                          }
                      }
                  }
                  return dupeIndex;
              }
  
              $select.searchInput.on('blur', function () {
                  $timeout(function () {
                      $selectMultiple.activeMatchIndex = -1;
                  });
              });
  
          }
      };
  }]);
  