angular-ui-select-rails 0.12.1.1 → 0.18.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ec556dde7b0416d867926acf486833d5df9abaf
4
- data.tar.gz: e5b3de921383453fbe621c99c338c274bc94a81e
3
+ metadata.gz: cc9f5c04458b517d77af56fcb38133499073df28
4
+ data.tar.gz: 523e9f2cbd1e8c6594b44fa43bf48ec99432a6b2
5
5
  SHA512:
6
- metadata.gz: 50ca7870a72fa11af27c0ae510fa17c7db461b99aa2bd152ad975fd6cf22c6a1515165e55ccc18945fd367294ce4e9616a5fa24a8c7f6fb59c78bb0cc0499948
7
- data.tar.gz: 503c97395852a7fdc180f7b359b1f2a55d96f02e0a6ee13660c869b2d596ae312883fb87e336540d23bf70fb041d270369307a8a7593908ed24e95855c66248e
6
+ metadata.gz: 5c066b129c7fd056d53ed48d167362310b53a05d81bf7eb8ec451e37afc733728ca790b96b4c3380443ed8ab4e6059d7d2a232f8c73af28851367d97e10df44c
7
+ data.tar.gz: 5c7102048d9055cad7698b837f65a725e30e175b56998d5836ad7e2eeb8570d07cca2db6916063ee856522f565c987262dd64d173fd1928c5b3d20244a9f3298
@@ -2,7 +2,7 @@ module Angular
2
2
  module Ui
3
3
  module Select
4
4
  module Rails
5
- VERSION = "0.12.1.1"
5
+ VERSION = "0.18.1"
6
6
  end
7
7
  end
8
8
  end
@@ -1,14 +1,13 @@
1
1
  /*!
2
2
  * ui-select
3
3
  * http://github.com/angular-ui/ui-select
4
- * Version: 0.12.1 - 2015-07-28T03:50:59.076Z
4
+ * Version: 0.18.1 - 2016-07-10T00:18:10.535Z
5
5
  * License: MIT
6
6
  */
7
7
 
8
8
 
9
9
  (function () {
10
10
  "use strict";
11
-
12
11
  var KEY = {
13
12
  TAB: 9,
14
13
  ENTER: 13,
@@ -42,7 +41,7 @@ var KEY = {
42
41
  return true;
43
42
  }
44
43
 
45
- if (e.metaKey) return true;
44
+ if (e.metaKey || e.ctrlKey || e.altKey) return true;
46
45
 
47
46
  return false;
48
47
  },
@@ -55,6 +54,13 @@ var KEY = {
55
54
  },
56
55
  isHorizontalMovement: function (k){
57
56
  return ~[KEY.LEFT,KEY.RIGHT,KEY.BACKSPACE,KEY.DELETE].indexOf(k);
57
+ },
58
+ toSeparator: function (k) {
59
+ var sep = {ENTER:"\n",TAB:"\t",SPACE:" "}[k];
60
+ if (sep) return sep;
61
+ // return undefined for special keys other than enter, tab or space.
62
+ // no way to use them to cut strings.
63
+ return KEY[k] ? undefined : k;
58
64
  }
59
65
  };
60
66
 
@@ -103,6 +109,9 @@ var uis = angular.module('ui.select', [])
103
109
  placeholder: '', // Empty by default, like HTML tag <select>
104
110
  refreshDelay: 1000, // In milliseconds
105
111
  closeOnSelect: true,
112
+ skipFocusser: false,
113
+ dropdownPosition: 'auto',
114
+ removeSelected: true,
106
115
  generateId: function() {
107
116
  return latestId++;
108
117
  },
@@ -138,11 +147,11 @@ var uis = angular.module('ui.select', [])
138
147
  */
139
148
  .filter('highlight', function() {
140
149
  function escapeRegexp(queryToEscape) {
141
- return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
150
+ return ('' + queryToEscape).replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
142
151
  }
143
152
 
144
153
  return function(matchItem, query) {
145
- return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<span class="ui-select-highlight">$&</span>') : matchItem;
154
+ return query && matchItem ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<span class="ui-select-highlight">$&</span>') : matchItem;
146
155
  };
147
156
  })
148
157
 
@@ -167,9 +176,34 @@ var uis = angular.module('ui.select', [])
167
176
  };
168
177
  }]);
169
178
 
179
+ /**
180
+ * Debounces functions
181
+ *
182
+ * Taken from UI Bootstrap $$debounce source code
183
+ * See https://github.com/angular-ui/bootstrap/blob/master/src/debounce/debounce.js
184
+ *
185
+ */
186
+ uis.factory('$$uisDebounce', ['$timeout', function($timeout) {
187
+ return function(callback, debounceTime) {
188
+ var timeoutPromise;
189
+
190
+ return function() {
191
+ var self = this;
192
+ var args = Array.prototype.slice.call(arguments);
193
+ if (timeoutPromise) {
194
+ $timeout.cancel(timeoutPromise);
195
+ }
196
+
197
+ timeoutPromise = $timeout(function() {
198
+ callback.apply(self, args);
199
+ }, debounceTime);
200
+ };
201
+ };
202
+ }]);
203
+
170
204
  uis.directive('uiSelectChoices',
171
- ['uiSelectConfig', 'uisRepeatParser', 'uiSelectMinErr', '$compile',
172
- function(uiSelectConfig, RepeatParser, uiSelectMinErr, $compile) {
205
+ ['uiSelectConfig', 'uisRepeatParser', 'uiSelectMinErr', '$compile', '$window',
206
+ function(uiSelectConfig, RepeatParser, uiSelectMinErr, $compile, $window) {
173
207
 
174
208
  return {
175
209
  restrict: 'EA',
@@ -177,6 +211,9 @@ uis.directive('uiSelectChoices',
177
211
  replace: true,
178
212
  transclude: true,
179
213
  templateUrl: function(tElement) {
214
+ // Needed so the uiSelect can detect the transcluded content
215
+ tElement.addClass('ui-select-choices');
216
+
180
217
  // Gets theme attribute from parent (ui-select)
181
218
  var theme = tElement.parent().attr('theme') || uiSelectConfig.theme;
182
219
  return theme + '/choices.tpl.html';
@@ -186,43 +223,59 @@ uis.directive('uiSelectChoices',
186
223
 
187
224
  if (!tAttrs.repeat) throw uiSelectMinErr('repeat', "Expected 'repeat' expression.");
188
225
 
189
- return function link(scope, element, attrs, $select, transcludeFn) {
226
+ // var repeat = RepeatParser.parse(attrs.repeat);
227
+ var groupByExp = tAttrs.groupBy;
228
+ var groupFilterExp = tAttrs.groupFilter;
190
229
 
191
- // var repeat = RepeatParser.parse(attrs.repeat);
192
- var groupByExp = attrs.groupBy;
193
- var groupFilterExp = attrs.groupFilter;
230
+ if (groupByExp) {
231
+ var groups = tElement.querySelectorAll('.ui-select-choices-group');
232
+ if (groups.length !== 1) throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-group but got '{0}'.", groups.length);
233
+ groups.attr('ng-repeat', RepeatParser.getGroupNgRepeatExpression());
234
+ }
194
235
 
195
- $select.parseRepeatAttr(attrs.repeat, groupByExp, groupFilterExp); //Result ready at $select.parserResult
236
+ var parserResult = RepeatParser.parse(tAttrs.repeat);
196
237
 
197
- $select.disableChoiceExpression = attrs.uiDisableChoice;
198
- $select.onHighlightCallback = attrs.onHighlight;
238
+ var choices = tElement.querySelectorAll('.ui-select-choices-row');
239
+ if (choices.length !== 1) {
240
+ throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
241
+ }
199
242
 
200
- if(groupByExp) {
201
- var groups = element.querySelectorAll('.ui-select-choices-group');
202
- if (groups.length !== 1) throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-group but got '{0}'.", groups.length);
203
- groups.attr('ng-repeat', RepeatParser.getGroupNgRepeatExpression());
204
- }
243
+ choices.attr('ng-repeat', parserResult.repeatExpression(groupByExp))
244
+ .attr('ng-if', '$select.open'); //Prevent unnecessary watches when dropdown is closed
245
+
205
246
 
206
- var choices = element.querySelectorAll('.ui-select-choices-row');
207
- if (choices.length !== 1) {
208
- throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
209
- }
247
+ var rowsInner = tElement.querySelectorAll('.ui-select-choices-row-inner');
248
+ if (rowsInner.length !== 1) {
249
+ throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row-inner but got '{0}'.", rowsInner.length);
250
+ }
251
+ rowsInner.attr('uis-transclude-append', ''); //Adding uisTranscludeAppend directive to row element after choices element has ngRepeat
210
252
 
211
- choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression($select.parserResult.itemName, '$select.items', $select.parserResult.trackByExp, groupByExp))
212
- .attr('ng-if', '$select.open') //Prevent unnecessary watches when dropdown is closed
213
- .attr('ng-mouseenter', '$select.setActiveItem('+$select.parserResult.itemName +')')
214
- .attr('ng-click', '$select.select(' + $select.parserResult.itemName + ',false,$event)');
253
+ // If IE8 then need to target rowsInner to apply the ng-click attr as choices will not capture the event.
254
+ var clickTarget = $window.document.addEventListener ? choices : rowsInner;
255
+ clickTarget.attr('ng-click', '$select.select(' + parserResult.itemName + ',$select.skipFocusser,$event)');
256
+
257
+ return function link(scope, element, attrs, $select) {
215
258
 
216
- var rowsInner = element.querySelectorAll('.ui-select-choices-row-inner');
217
- if (rowsInner.length !== 1) throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row-inner but got '{0}'.", rowsInner.length);
218
- rowsInner.attr('uis-transclude-append', ''); //Adding uisTranscludeAppend directive to row element after choices element has ngRepeat
259
+
260
+ $select.parseRepeatAttr(attrs.repeat, groupByExp, groupFilterExp); //Result ready at $select.parserResult
219
261
 
220
- $compile(element, transcludeFn)(scope); //Passing current transcludeFn to be able to append elements correctly from uisTranscludeAppend
262
+ $select.disableChoiceExpression = attrs.uiDisableChoice;
263
+ $select.onHighlightCallback = attrs.onHighlight;
264
+
265
+ $select.dropdownPosition = attrs.position ? attrs.position.toLowerCase() : uiSelectConfig.dropdownPosition;
266
+
267
+ scope.$on('$destroy', function() {
268
+ choices.remove();
269
+ });
221
270
 
222
271
  scope.$watch('$select.search', function(newValue) {
223
272
  if(newValue && !$select.open && $select.multiple) $select.activate(false, true);
224
273
  $select.activeIndex = $select.tagging.isActivated ? -1 : 0;
225
- $select.refresh(attrs.refresh);
274
+ if (!attrs.minimumInputLength || $select.search.length >= attrs.minimumInputLength) {
275
+ $select.refresh(attrs.refresh);
276
+ } else {
277
+ $select.items = [];
278
+ }
226
279
  });
227
280
 
228
281
  attrs.$observe('refreshDelay', function() {
@@ -242,8 +295,8 @@ uis.directive('uiSelectChoices',
242
295
  * put as much logic in the controller (instead of the link functions) as possible so it can be easily tested.
243
296
  */
244
297
  uis.controller('uiSelectCtrl',
245
- ['$scope', '$element', '$timeout', '$filter', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig',
246
- function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr, uiSelectConfig) {
298
+ ['$scope', '$element', '$timeout', '$filter', '$$uisDebounce', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig', '$parse', '$injector', '$window',
299
+ function($scope, $element, $timeout, $filter, $$uisDebounce, RepeatParser, uiSelectMinErr, uiSelectConfig, $parse, $injector, $window) {
247
300
 
248
301
  var ctrl = this;
249
302
 
@@ -253,9 +306,11 @@ uis.controller('uiSelectCtrl',
253
306
  ctrl.searchEnabled = uiSelectConfig.searchEnabled;
254
307
  ctrl.sortable = uiSelectConfig.sortable;
255
308
  ctrl.refreshDelay = uiSelectConfig.refreshDelay;
309
+ ctrl.paste = uiSelectConfig.paste;
256
310
 
257
- ctrl.removeSelected = false; //If selected item(s) should be removed from dropdown list
311
+ ctrl.removeSelected = uiSelectConfig.removeSelected; //If selected item(s) should be removed from dropdown list
258
312
  ctrl.closeOnSelect = true; //Initialized inside uiSelect directive link function
313
+ ctrl.skipFocusser = false; //Set to true to avoid returning focus to ctrl when item is selected
259
314
  ctrl.search = EMPTY_SEARCH;
260
315
 
261
316
  ctrl.activeIndex = 0; //Dropdown of choices
@@ -266,6 +321,8 @@ uis.controller('uiSelectCtrl',
266
321
  ctrl.disabled = false;
267
322
  ctrl.selected = undefined;
268
323
 
324
+ ctrl.dropdownPosition = 'auto';
325
+
269
326
  ctrl.focusser = undefined; //Reference to input element used to handle focus events
270
327
  ctrl.resetSearchInput = true;
271
328
  ctrl.multiple = undefined; // Initialized inside uiSelect directive link function
@@ -275,23 +332,54 @@ uis.controller('uiSelectCtrl',
275
332
  ctrl.lockChoiceExpression = undefined; // Initialized inside uiSelectMatch directive link function
276
333
  ctrl.clickTriggeredSelect = false;
277
334
  ctrl.$filter = $filter;
335
+ ctrl.$element = $element;
336
+
337
+ // Use $injector to check for $animate and store a reference to it
338
+ ctrl.$animate = (function () {
339
+ try {
340
+ return $injector.get('$animate');
341
+ } catch (err) {
342
+ // $animate does not exist
343
+ return null;
344
+ }
345
+ })();
278
346
 
279
347
  ctrl.searchInput = $element.querySelectorAll('input.ui-select-search');
280
348
  if (ctrl.searchInput.length !== 1) {
281
349
  throw uiSelectMinErr('searchInput', "Expected 1 input.ui-select-search but got '{0}'.", ctrl.searchInput.length);
282
350
  }
283
-
351
+
284
352
  ctrl.isEmpty = function() {
285
- return angular.isUndefined(ctrl.selected) || ctrl.selected === null || ctrl.selected === '';
353
+ return angular.isUndefined(ctrl.selected) || ctrl.selected === null || ctrl.selected === '' || (ctrl.multiple && ctrl.selected.length === 0);
286
354
  };
287
355
 
356
+ function _findIndex(collection, predicate, thisArg){
357
+ if (collection.findIndex){
358
+ return collection.findIndex(predicate, thisArg);
359
+ } else {
360
+ var list = Object(collection);
361
+ var length = list.length >>> 0;
362
+ var value;
363
+
364
+ for (var i = 0; i < length; i++) {
365
+ value = list[i];
366
+ if (predicate.call(thisArg, value, i, list)) {
367
+ return i;
368
+ }
369
+ }
370
+ return -1;
371
+ }
372
+ }
373
+
288
374
  // Most of the time the user does not want to empty the search input when in typeahead mode
289
375
  function _resetSearchInput() {
290
376
  if (ctrl.resetSearchInput || (ctrl.resetSearchInput === undefined && uiSelectConfig.resetSearchInput)) {
291
377
  ctrl.search = EMPTY_SEARCH;
292
378
  //reset activeIndex
293
379
  if (ctrl.selected && ctrl.items.length && !ctrl.multiple) {
294
- ctrl.activeIndex = ctrl.items.indexOf(ctrl.selected);
380
+ ctrl.activeIndex = _findIndex(ctrl.items, function(item){
381
+ return angular.equals(this, item);
382
+ }, ctrl.selected);
295
383
  }
296
384
  }
297
385
  }
@@ -325,14 +413,46 @@ uis.controller('uiSelectCtrl',
325
413
  ctrl.activeIndex = 0;
326
414
  }
327
415
 
328
- // Give it time to appear before focus
329
- $timeout(function() {
330
- ctrl.search = initSearchValue || ctrl.search;
331
- ctrl.searchInput[0].focus();
332
- });
416
+ var container = $element.querySelectorAll('.ui-select-choices-content');
417
+ var searchInput = $element.querySelectorAll('.ui-select-search');
418
+ if (ctrl.$animate && ctrl.$animate.on && ctrl.$animate.enabled(container[0])) {
419
+ var animateHandler = function(elem, phase) {
420
+ if (phase === 'start' && ctrl.items.length === 0) {
421
+ // Only focus input after the animation has finished
422
+ ctrl.$animate.off('removeClass', searchInput[0], animateHandler);
423
+ $timeout(function () {
424
+ ctrl.focusSearchInput(initSearchValue);
425
+ });
426
+ } else if (phase === 'close') {
427
+ // Only focus input after the animation has finished
428
+ ctrl.$animate.off('enter', container[0], animateHandler);
429
+ $timeout(function () {
430
+ ctrl.focusSearchInput(initSearchValue);
431
+ });
432
+ }
433
+ };
434
+
435
+ if (ctrl.items.length > 0) {
436
+ ctrl.$animate.on('enter', container[0], animateHandler);
437
+ } else {
438
+ ctrl.$animate.on('removeClass', searchInput[0], animateHandler);
439
+ }
440
+ } else {
441
+ $timeout(function () {
442
+ ctrl.focusSearchInput(initSearchValue);
443
+ if(!ctrl.tagging.isActivated && ctrl.items.length > 1) {
444
+ _ensureHighlightVisible();
445
+ }
446
+ });
447
+ }
333
448
  }
334
449
  };
335
450
 
451
+ ctrl.focusSearchInput = function (initSearchValue) {
452
+ ctrl.search = initSearchValue || ctrl.search;
453
+ ctrl.searchInput[0].focus();
454
+ };
455
+
336
456
  ctrl.findGroupByName = function(name) {
337
457
  return ctrl.groups && ctrl.groups.filter(function(group) {
338
458
  return group.name === name;
@@ -378,18 +498,50 @@ uis.controller('uiSelectCtrl',
378
498
  ctrl.isGrouped = !!groupByExp;
379
499
  ctrl.itemProperty = ctrl.parserResult.itemName;
380
500
 
501
+ //If collection is an Object, convert it to Array
502
+
503
+ var originalSource = ctrl.parserResult.source;
504
+
505
+ //When an object is used as source, we better create an array and use it as 'source'
506
+ var createArrayFromObject = function(){
507
+ var origSrc = originalSource($scope);
508
+ $scope.$uisSource = Object.keys(origSrc).map(function(v){
509
+ var result = {};
510
+ result[ctrl.parserResult.keyName] = v;
511
+ result.value = origSrc[v];
512
+ return result;
513
+ });
514
+ };
515
+
516
+ if (ctrl.parserResult.keyName){ // Check for (key,value) syntax
517
+ createArrayFromObject();
518
+ ctrl.parserResult.source = $parse('$uisSource' + ctrl.parserResult.filters);
519
+ $scope.$watch(originalSource, function(newVal, oldVal){
520
+ if (newVal !== oldVal) createArrayFromObject();
521
+ }, true);
522
+ }
523
+
381
524
  ctrl.refreshItems = function (data){
382
525
  data = data || ctrl.parserResult.source($scope);
383
526
  var selectedItems = ctrl.selected;
384
527
  //TODO should implement for single mode removeSelected
385
- if ((angular.isArray(selectedItems) && !selectedItems.length) || !ctrl.removeSelected) {
528
+ if (ctrl.isEmpty() || (angular.isArray(selectedItems) && !selectedItems.length) || !ctrl.multiple || !ctrl.removeSelected) {
386
529
  ctrl.setItemsFn(data);
387
530
  }else{
388
- if ( data !== undefined ) {
389
- var filteredItems = data.filter(function(i) {return selectedItems.indexOf(i) < 0;});
531
+ if ( data !== undefined && data !== null ) {
532
+ var filteredItems = data.filter(function(i) {
533
+ return angular.isArray(selectedItems) ? selectedItems.every(function(selectedItem) {
534
+ return !angular.equals(i, selectedItem);
535
+ }) : !angular.equals(i, selectedItems);
536
+ });
390
537
  ctrl.setItemsFn(filteredItems);
391
538
  }
392
539
  }
540
+ if (ctrl.dropdownPosition === 'auto' || ctrl.dropdownPosition === 'up'){
541
+ $scope.calculateDropdownPos();
542
+ }
543
+
544
+ $scope.$broadcast('uis:refresh');
393
545
  };
394
546
 
395
547
  // See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L259
@@ -406,7 +558,11 @@ uis.controller('uiSelectCtrl',
406
558
  //Remove already selected items (ex: while searching)
407
559
  //TODO Should add a test
408
560
  ctrl.refreshItems(items);
409
- ctrl.ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
561
+
562
+ //update the view value with fresh data from items, if there is a valid model value
563
+ if(angular.isDefined(ctrl.ngModel.$modelValue)) {
564
+ ctrl.ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
565
+ }
410
566
  }
411
567
  }
412
568
  });
@@ -435,18 +591,14 @@ uis.controller('uiSelectCtrl',
435
591
  }
436
592
  };
437
593
 
438
- ctrl.setActiveItem = function(item) {
439
- ctrl.activeIndex = ctrl.items.indexOf(item);
440
- };
441
-
442
594
  ctrl.isActive = function(itemScope) {
443
595
  if ( !ctrl.open ) {
444
596
  return false;
445
597
  }
446
598
  var itemIndex = ctrl.items.indexOf(itemScope[ctrl.itemProperty]);
447
- var isActive = itemIndex === ctrl.activeIndex;
599
+ var isActive = itemIndex == ctrl.activeIndex;
448
600
 
449
- if ( !isActive || ( itemIndex < 0 && ctrl.taggingLabel !== false ) ||( itemIndex < 0 && ctrl.taggingLabel === false) ) {
601
+ if ( !isActive || itemIndex < 0 ) {
450
602
  return false;
451
603
  }
452
604
 
@@ -457,18 +609,49 @@ uis.controller('uiSelectCtrl',
457
609
  return isActive;
458
610
  };
459
611
 
612
+ var _isItemSelected = function (item) {
613
+ return (ctrl.selected && angular.isArray(ctrl.selected) &&
614
+ ctrl.selected.filter(function (selection) { return angular.equals(selection, item); }).length > 0);
615
+ };
616
+
617
+ var disabledItems = [];
618
+
619
+ function _updateItemDisabled(item, isDisabled) {
620
+ var disabledItemIndex = disabledItems.indexOf(item);
621
+ if (isDisabled && disabledItemIndex === -1) {
622
+ disabledItems.push(item);
623
+ }
624
+
625
+ if (!isDisabled && disabledItemIndex > -1) {
626
+ disabledItems.splice(disabledItemIndex, 0);
627
+ }
628
+ }
629
+
630
+ function _isItemDisabled(item) {
631
+ return disabledItems.indexOf(item) > -1;
632
+ }
633
+
460
634
  ctrl.isDisabled = function(itemScope) {
461
635
 
462
636
  if (!ctrl.open) return;
463
637
 
464
- var itemIndex = ctrl.items.indexOf(itemScope[ctrl.itemProperty]);
638
+ var item = itemScope[ctrl.itemProperty];
639
+ var itemIndex = ctrl.items.indexOf(item);
465
640
  var isDisabled = false;
466
- var item;
641
+
642
+ if (itemIndex >= 0 && (angular.isDefined(ctrl.disableChoiceExpression) || ctrl.multiple)) {
643
+
644
+ if (item.isTag) return false;
467
645
 
468
- if (itemIndex >= 0 && !angular.isUndefined(ctrl.disableChoiceExpression)) {
469
- item = ctrl.items[itemIndex];
470
- isDisabled = !!(itemScope.$eval(ctrl.disableChoiceExpression)); // force the boolean value
471
- item._uiSelectChoiceDisabled = isDisabled; // store this for later reference
646
+ if (ctrl.multiple) {
647
+ isDisabled = _isItemSelected(item);
648
+ }
649
+
650
+ if (!isDisabled && angular.isDefined(ctrl.disableChoiceExpression)) {
651
+ isDisabled = !!(itemScope.$eval(ctrl.disableChoiceExpression));
652
+ }
653
+
654
+ _updateItemDisabled(item, isDisabled);
472
655
  }
473
656
 
474
657
  return isDisabled;
@@ -477,16 +660,18 @@ uis.controller('uiSelectCtrl',
477
660
 
478
661
  // When the user selects an item with ENTER or clicks the dropdown
479
662
  ctrl.select = function(item, skipFocusser, $event) {
480
- if (item === undefined || !item._uiSelectChoiceDisabled) {
663
+ if (item === undefined || !_isItemDisabled(item)) {
481
664
 
482
- if ( ! ctrl.items && ! ctrl.search ) return;
665
+ if ( ! ctrl.items && ! ctrl.search && ! ctrl.tagging.isActivated) return;
483
666
 
484
- if (!item || !item._uiSelectChoiceDisabled) {
667
+ if (!item || !_isItemDisabled(item)) {
485
668
  if(ctrl.tagging.isActivated) {
486
- // if taggingLabel is disabled, we pull from ctrl.search val
669
+ // if taggingLabel is disabled and item is undefined we pull from ctrl.search
487
670
  if ( ctrl.taggingLabel === false ) {
488
671
  if ( ctrl.activeIndex < 0 ) {
489
- item = ctrl.tagging.fct !== undefined ? ctrl.tagging.fct(ctrl.search) : ctrl.search;
672
+ if (item === undefined) {
673
+ item = ctrl.tagging.fct !== undefined ? ctrl.tagging.fct(ctrl.search) : ctrl.search;
674
+ }
490
675
  if (!item || angular.equals( ctrl.items[0], item ) ) {
491
676
  return;
492
677
  }
@@ -505,7 +690,7 @@ uis.controller('uiSelectCtrl',
505
690
  // create new item on the fly if we don't already have one;
506
691
  // use tagging function if we have one
507
692
  if ( ctrl.tagging.fct !== undefined && typeof item === 'string' ) {
508
- item = ctrl.tagging.fct(ctrl.search);
693
+ item = ctrl.tagging.fct(item);
509
694
  if (!item) return;
510
695
  // if item type is 'string', apply the tagging label
511
696
  } else if ( typeof item === 'string' ) {
@@ -515,7 +700,7 @@ uis.controller('uiSelectCtrl',
515
700
  }
516
701
  }
517
702
  // search ctrl.selected for dupes potentially caused by tagging and return early if found
518
- if ( ctrl.selected && angular.isArray(ctrl.selected) && ctrl.selected.filter( function (selection) { return angular.equals(selection, item); }).length > 0 ) {
703
+ if (_isItemSelected(item)) {
519
704
  ctrl.close(skipFocusser);
520
705
  return;
521
706
  }
@@ -577,18 +762,56 @@ uis.controller('uiSelectCtrl',
577
762
  }
578
763
  };
579
764
 
580
- ctrl.isLocked = function(itemScope, itemIndex) {
581
- var isLocked, item = ctrl.selected[itemIndex];
765
+ // Set default function for locked choices - avoids unnecessary
766
+ // logic if functionality is not being used
767
+ ctrl.isLocked = function () {
768
+ return false;
769
+ };
770
+
771
+ $scope.$watch(function () {
772
+ return angular.isDefined(ctrl.lockChoiceExpression) && ctrl.lockChoiceExpression !== "";
773
+ }, _initaliseLockedChoices);
774
+
775
+ function _initaliseLockedChoices(doInitalise) {
776
+ if(!doInitalise) return;
777
+
778
+ var lockedItems = [];
779
+
780
+ function _updateItemLocked(item, isLocked) {
781
+ var lockedItemIndex = lockedItems.indexOf(item);
782
+ if (isLocked && lockedItemIndex === -1) {
783
+ lockedItems.push(item);
784
+ }
582
785
 
583
- if (item && !angular.isUndefined(ctrl.lockChoiceExpression)) {
584
- isLocked = !!(itemScope.$eval(ctrl.lockChoiceExpression)); // force the boolean value
585
- item._uiSelectChoiceLocked = isLocked; // store this for later reference
786
+ if (!isLocked && lockedItemIndex > -1) {
787
+ lockedItems.splice(lockedItemIndex, 0);
788
+ }
789
+ }
790
+
791
+ function _isItemlocked(item) {
792
+ return lockedItems.indexOf(item) > -1;
793
+ }
794
+
795
+ ctrl.isLocked = function (itemScope, itemIndex) {
796
+ var isLocked = false,
797
+ item = ctrl.selected[itemIndex];
798
+
799
+ if(item) {
800
+ if (itemScope) {
801
+ isLocked = !!(itemScope.$eval(ctrl.lockChoiceExpression));
802
+ _updateItemLocked(item, isLocked);
803
+ } else {
804
+ isLocked = _isItemlocked(item);
805
+ }
586
806
  }
587
807
 
588
808
  return isLocked;
589
- };
809
+ };
810
+ }
811
+
590
812
 
591
813
  var sizeWatch = null;
814
+ var updaterScheduled = false;
592
815
  ctrl.sizeSearchInput = function() {
593
816
 
594
817
  var input = ctrl.searchInput[0],
@@ -610,12 +833,18 @@ uis.controller('uiSelectCtrl',
610
833
  ctrl.searchInput.css('width', '10px');
611
834
  $timeout(function() { //Give tags time to render correctly
612
835
  if (sizeWatch === null && !updateIfVisible(calculateContainerWidth())) {
613
- sizeWatch = $scope.$watch(calculateContainerWidth, function(containerWidth) {
614
- if (updateIfVisible(containerWidth)) {
615
- sizeWatch();
616
- sizeWatch = null;
836
+ sizeWatch = $scope.$watch(function() {
837
+ if (!updaterScheduled) {
838
+ updaterScheduled = true;
839
+ $scope.$$postDigest(function() {
840
+ updaterScheduled = false;
841
+ if (updateIfVisible(calculateContainerWidth())) {
842
+ sizeWatch();
843
+ sizeWatch = null;
844
+ }
845
+ });
617
846
  }
618
- });
847
+ }, angular.noop);
619
848
  }
620
849
  });
621
850
  };
@@ -636,7 +865,7 @@ uis.controller('uiSelectCtrl',
636
865
  break;
637
866
  case KEY.ENTER:
638
867
  if(ctrl.open && (ctrl.tagging.isActivated || ctrl.activeIndex >= 0)){
639
- ctrl.select(ctrl.items[ctrl.activeIndex]); // Make sure at least one dropdown item is highlighted before adding if not in tagging mode
868
+ ctrl.select(ctrl.items[ctrl.activeIndex], ctrl.skipFocusser); // Make sure at least one dropdown item is highlighted before adding if not in tagging mode
640
869
  } else {
641
870
  ctrl.activate(false, true); //In case its the search input in 'multiple' mode
642
871
  }
@@ -655,6 +884,11 @@ uis.controller('uiSelectCtrl',
655
884
 
656
885
  var key = e.which;
657
886
 
887
+ if (~[KEY.ENTER,KEY.ESC].indexOf(key)){
888
+ e.preventDefault();
889
+ e.stopPropagation();
890
+ }
891
+
658
892
  // if(~[KEY.ESC,KEY.TAB].indexOf(key)){
659
893
  // //TODO: SEGURO?
660
894
  // ctrl.close();
@@ -701,18 +935,45 @@ uis.controller('uiSelectCtrl',
701
935
 
702
936
  });
703
937
 
704
- // If tagging try to split by tokens and add items
705
938
  ctrl.searchInput.on('paste', function (e) {
706
- var data = e.originalEvent.clipboardData.getData('text/plain');
707
- if (data && data.length > 0 && ctrl.taggingTokens.isActivated && ctrl.tagging.fct) {
708
- var items = data.split(ctrl.taggingTokens.tokens[0]); // split by first token only
709
- if (items && items.length > 0) {
939
+ var data;
940
+
941
+ if (window.clipboardData && window.clipboardData.getData) { // IE
942
+ data = window.clipboardData.getData('Text');
943
+ } else {
944
+ data = (e.originalEvent || e).clipboardData.getData('text/plain');
945
+ }
946
+
947
+ // Prepend the current input field text to the paste buffer.
948
+ data = ctrl.search + data;
949
+
950
+ if (data && data.length > 0) {
951
+ // If tagging try to split by tokens and add items
952
+ if (ctrl.taggingTokens.isActivated) {
953
+ var items = [];
954
+ for (var i = 0; i < ctrl.taggingTokens.tokens.length; i++) { // split by first token that is contained in data
955
+ var separator = KEY.toSeparator(ctrl.taggingTokens.tokens[i]) || ctrl.taggingTokens.tokens[i];
956
+ if (data.indexOf(separator) > -1) {
957
+ items = data.split(separator);
958
+ break; // only split by one token
959
+ }
960
+ }
961
+ if (items.length === 0) {
962
+ items = [data];
963
+ }
964
+ var oldsearch = ctrl.search;
710
965
  angular.forEach(items, function (item) {
711
- var newItem = ctrl.tagging.fct(item);
966
+ var newItem = ctrl.tagging.fct ? ctrl.tagging.fct(item) : item;
712
967
  if (newItem) {
713
968
  ctrl.select(newItem, true);
714
969
  }
715
970
  });
971
+ ctrl.search = oldsearch || EMPTY_SEARCH;
972
+ e.preventDefault();
973
+ e.stopPropagation();
974
+ } else if (ctrl.paste) {
975
+ ctrl.paste(data);
976
+ ctrl.search = EMPTY_SEARCH;
716
977
  e.preventDefault();
717
978
  e.stopPropagation();
718
979
  }
@@ -751,10 +1012,16 @@ uis.controller('uiSelectCtrl',
751
1012
  }
752
1013
  }
753
1014
 
1015
+ var onResize = $$uisDebounce(function() {
1016
+ ctrl.sizeSearchInput();
1017
+ }, 50);
1018
+
1019
+ angular.element($window).bind('resize', onResize);
1020
+
754
1021
  $scope.$on('$destroy', function() {
755
1022
  ctrl.searchInput.off('keyup keydown tagged blur paste');
1023
+ angular.element($window).off('resize', onResize);
756
1024
  });
757
-
758
1025
  }]);
759
1026
 
760
1027
  uis.directive('uiSelect',
@@ -776,11 +1043,22 @@ uis.directive('uiSelect',
776
1043
  controllerAs: '$select',
777
1044
  compile: function(tElement, tAttrs) {
778
1045
 
1046
+ // Allow setting ngClass on uiSelect
1047
+ var match = /{(.*)}\s*{(.*)}/.exec(tAttrs.ngClass);
1048
+ if(match) {
1049
+ var combined = '{'+ match[1] +', '+ match[2] +'}';
1050
+ tAttrs.ngClass = combined;
1051
+ tElement.attr('ng-class', combined);
1052
+ }
1053
+
779
1054
  //Multiple or Single depending if multiple attribute presence
780
1055
  if (angular.isDefined(tAttrs.multiple))
781
- tElement.append("<ui-select-multiple/>").removeAttr('multiple');
1056
+ tElement.append('<ui-select-multiple/>').removeAttr('multiple');
782
1057
  else
783
- tElement.append("<ui-select-single/>");
1058
+ tElement.append('<ui-select-single/>');
1059
+
1060
+ if (tAttrs.inputId)
1061
+ tElement.querySelectorAll('input.ui-select-search')[0].id = tAttrs.inputId;
784
1062
 
785
1063
  return function(scope, element, attrs, ctrls, transcludeFn) {
786
1064
 
@@ -800,9 +1078,14 @@ uis.directive('uiSelect',
800
1078
  }
801
1079
  }();
802
1080
 
1081
+ scope.$watch('skipFocusser', function() {
1082
+ var skipFocusser = scope.$eval(attrs.skipFocusser);
1083
+ $select.skipFocusser = skipFocusser !== undefined ? skipFocusser : uiSelectConfig.skipFocusser;
1084
+ });
1085
+
803
1086
  $select.onSelectCallback = $parse(attrs.onSelect);
804
1087
  $select.onRemoveCallback = $parse(attrs.onRemove);
805
-
1088
+
806
1089
  //Set reference to ngModel from uiSelectCtrl
807
1090
  $select.ngModel = ngModel;
808
1091
 
@@ -812,14 +1095,13 @@ uis.directive('uiSelect',
812
1095
 
813
1096
  if(attrs.tabindex){
814
1097
  attrs.$observe('tabindex', function(value) {
815
- $select.focusInput.attr("tabindex", value);
816
- element.removeAttr("tabindex");
1098
+ $select.focusInput.attr('tabindex', value);
1099
+ element.removeAttr('tabindex');
817
1100
  });
818
1101
  }
819
1102
 
820
- scope.$watch('searchEnabled', function() {
821
- var searchEnabled = scope.$eval(attrs.searchEnabled);
822
- $select.searchEnabled = searchEnabled !== undefined ? searchEnabled : uiSelectConfig.searchEnabled;
1103
+ scope.$watch(function () { return scope.$eval(attrs.searchEnabled); }, function(newVal) {
1104
+ $select.searchEnabled = newVal !== undefined ? newVal : uiSelectConfig.searchEnabled;
823
1105
  });
824
1106
 
825
1107
  scope.$watch('sortable', function() {
@@ -827,6 +1109,16 @@ uis.directive('uiSelect',
827
1109
  $select.sortable = sortable !== undefined ? sortable : uiSelectConfig.sortable;
828
1110
  });
829
1111
 
1112
+ attrs.$observe('limit', function() {
1113
+ //Limit the number of selections allowed
1114
+ $select.limit = (angular.isDefined(attrs.limit)) ? parseInt(attrs.limit, 10) : undefined;
1115
+ });
1116
+
1117
+ scope.$watch('removeSelected', function() {
1118
+ var removeSelected = scope.$eval(attrs.removeSelected);
1119
+ $select.removeSelected = removeSelected !== undefined ? removeSelected : uiSelectConfig.removeSelected;
1120
+ });
1121
+
830
1122
  attrs.$observe('disabled', function() {
831
1123
  // No need to use $eval() (thanks to ng-disabled) since we already get a boolean instead of a string
832
1124
  $select.disabled = attrs.disabled !== undefined ? attrs.disabled : false;
@@ -838,6 +1130,10 @@ uis.directive('uiSelect',
838
1130
  $select.resetSearchInput = resetSearchInput !== undefined ? resetSearchInput : true;
839
1131
  });
840
1132
 
1133
+ attrs.$observe('paste', function() {
1134
+ $select.paste = scope.$eval(attrs.paste);
1135
+ });
1136
+
841
1137
  attrs.$observe('tagging', function() {
842
1138
  if(attrs.tagging !== undefined)
843
1139
  {
@@ -903,11 +1199,16 @@ uis.directive('uiSelect',
903
1199
  }
904
1200
 
905
1201
  if (!contains && !$select.clickTriggeredSelect) {
906
- //Will lose focus only with certain targets
907
- var focusableControls = ['input','button','textarea'];
908
- var targetScope = angular.element(e.target).scope(); //To check if target is other ui-select
909
- var skipFocusser = targetScope && targetScope.$select && targetScope.$select !== $select; //To check if target is other ui-select
910
- if (!skipFocusser) skipFocusser = ~focusableControls.indexOf(e.target.tagName.toLowerCase()); //Check if target is input, button or textarea
1202
+ var skipFocusser;
1203
+ if (!$select.skipFocusser) {
1204
+ //Will lose focus only with certain targets
1205
+ var focusableControls = ['input','button','textarea','select'];
1206
+ var targetController = angular.element(e.target).controller('uiSelect'); //To check if target is other ui-select
1207
+ skipFocusser = targetController && targetController !== $select; //To check if target is other ui-select
1208
+ if (!skipFocusser) skipFocusser = ~focusableControls.indexOf(e.target.tagName.toLowerCase()); //Check if target is input, button or textarea
1209
+ } else {
1210
+ skipFocusser = true;
1211
+ }
911
1212
  $select.close(skipFocusser);
912
1213
  scope.$digest();
913
1214
  }
@@ -945,6 +1246,13 @@ uis.directive('uiSelect',
945
1246
  throw uiSelectMinErr('transcluded', "Expected 1 .ui-select-choices but got '{0}'.", transcludedChoices.length);
946
1247
  }
947
1248
  element.querySelectorAll('.ui-select-choices').replaceWith(transcludedChoices);
1249
+
1250
+ var transcludedNoChoice = transcluded.querySelectorAll('.ui-select-no-choice');
1251
+ transcludedNoChoice.removeAttr('ui-select-no-choice'); //To avoid loop in case directive as attr
1252
+ transcludedNoChoice.removeAttr('data-ui-select-no-choice'); // Properly handle HTML5 data-attributes
1253
+ if (transcludedNoChoice.length == 1) {
1254
+ element.querySelectorAll('.ui-select-no-choice').replaceWith(transcludedNoChoice);
1255
+ }
948
1256
  });
949
1257
 
950
1258
  // Support for appending the select field to the body when its open
@@ -1006,6 +1314,9 @@ uis.directive('uiSelect',
1006
1314
  element[0].style.left = '';
1007
1315
  element[0].style.top = '';
1008
1316
  element[0].style.width = originalWidth;
1317
+
1318
+ // Set focus back on to the moved element
1319
+ $select.setFocus();
1009
1320
  }
1010
1321
 
1011
1322
  // Hold on to a reference to the .ui-select-dropdown element for direction support.
@@ -1013,42 +1324,103 @@ uis.directive('uiSelect',
1013
1324
  directionUpClassName = 'direction-up';
1014
1325
 
1015
1326
  // Support changing the direction of the dropdown if there isn't enough space to render it.
1016
- scope.$watch('$select.open', function(isOpen) {
1017
- if (isOpen) {
1018
- dropdown = angular.element(element).querySelectorAll('.ui-select-dropdown');
1019
- if (dropdown === null) {
1020
- return;
1021
- }
1327
+ scope.$watch('$select.open', function() {
1022
1328
 
1023
- // Hide the dropdown so there is no flicker until $timeout is done executing.
1024
- dropdown[0].style.opacity = 0;
1329
+ if ($select.dropdownPosition === 'auto' || $select.dropdownPosition === 'up'){
1330
+ scope.calculateDropdownPos();
1331
+ }
1332
+
1333
+ });
1334
+
1335
+ var setDropdownPosUp = function(offset, offsetDropdown){
1336
+
1337
+ offset = offset || uisOffset(element);
1338
+ offsetDropdown = offsetDropdown || uisOffset(dropdown);
1339
+
1340
+ dropdown[0].style.position = 'absolute';
1341
+ dropdown[0].style.top = (offsetDropdown.height * -1) + 'px';
1342
+ element.addClass(directionUpClassName);
1343
+
1344
+ };
1345
+
1346
+ var setDropdownPosDown = function(offset, offsetDropdown){
1347
+
1348
+ element.removeClass(directionUpClassName);
1349
+
1350
+ offset = offset || uisOffset(element);
1351
+ offsetDropdown = offsetDropdown || uisOffset(dropdown);
1352
+
1353
+ dropdown[0].style.position = '';
1354
+ dropdown[0].style.top = '';
1355
+
1356
+ };
1357
+
1358
+ var calculateDropdownPosAfterAnimation = function() {
1359
+ // Delay positioning the dropdown until all choices have been added so its height is correct.
1360
+ $timeout(function() {
1361
+ if ($select.dropdownPosition === 'up') {
1362
+ //Go UP
1363
+ setDropdownPosUp();
1364
+ } else {
1365
+ //AUTO
1366
+ element.removeClass(directionUpClassName);
1025
1367
 
1026
- // Delay positioning the dropdown until all choices have been added so its height is correct.
1027
- $timeout(function(){
1028
1368
  var offset = uisOffset(element);
1029
1369
  var offsetDropdown = uisOffset(dropdown);
1030
1370
 
1371
+ //https://code.google.com/p/chromium/issues/detail?id=342307#c4
1372
+ var scrollTop = $document[0].documentElement.scrollTop || $document[0].body.scrollTop; //To make it cross browser (blink, webkit, IE, Firefox).
1373
+
1031
1374
  // Determine if the direction of the dropdown needs to be changed.
1032
- if (offset.top + offset.height + offsetDropdown.height > $document[0].documentElement.scrollTop + $document[0].documentElement.clientHeight) {
1033
- dropdown[0].style.position = 'absolute';
1034
- dropdown[0].style.top = (offsetDropdown.height * -1) + 'px';
1035
- element.addClass(directionUpClassName);
1375
+ if (offset.top + offset.height + offsetDropdown.height > scrollTop + $document[0].documentElement.clientHeight) {
1376
+ //Go UP
1377
+ setDropdownPosUp(offset, offsetDropdown);
1378
+ }else{
1379
+ //Go DOWN
1380
+ setDropdownPosDown(offset, offsetDropdown);
1036
1381
  }
1382
+ }
1037
1383
 
1038
- // Display the dropdown once it has been positioned.
1039
- dropdown[0].style.opacity = 1;
1040
- });
1384
+ // Display the dropdown once it has been positioned.
1385
+ dropdown[0].style.opacity = 1;
1386
+ });
1387
+ };
1388
+
1389
+ scope.calculateDropdownPos = function() {
1390
+ if ($select.open) {
1391
+ dropdown = angular.element(element).querySelectorAll('.ui-select-dropdown');
1392
+
1393
+ if (dropdown.length === 0) {
1394
+ return;
1395
+ }
1396
+
1397
+ // Hide the dropdown so there is no flicker until $timeout is done executing.
1398
+ dropdown[0].style.opacity = 0;
1399
+
1400
+ if (!uisOffset(dropdown).height && $select.$animate && $select.$animate.on && $select.$animate.enabled(dropdown)) {
1401
+ var needsCalculated = true;
1402
+
1403
+ $select.$animate.on('enter', dropdown, function (elem, phase) {
1404
+ if (phase === 'close' && needsCalculated) {
1405
+ calculateDropdownPosAfterAnimation();
1406
+ needsCalculated = false;
1407
+ }
1408
+ });
1409
+ } else {
1410
+ calculateDropdownPosAfterAnimation();
1411
+ }
1041
1412
  } else {
1042
- if (dropdown === null) {
1043
- return;
1044
- }
1413
+ if (dropdown === null || dropdown.length === 0) {
1414
+ return;
1415
+ }
1045
1416
 
1046
- // Reset the position of the dropdown.
1047
- dropdown[0].style.position = '';
1048
- dropdown[0].style.top = '';
1049
- element.removeClass(directionUpClassName);
1417
+ // Reset the position of the dropdown.
1418
+ dropdown[0].style.opacity = 0;
1419
+ dropdown[0].style.position = '';
1420
+ dropdown[0].style.top = '';
1421
+ element.removeClass(directionUpClassName);
1050
1422
  }
1051
- });
1423
+ };
1052
1424
  };
1053
1425
  }
1054
1426
  };
@@ -1061,10 +1433,15 @@ uis.directive('uiSelectMatch', ['uiSelectConfig', function(uiSelectConfig) {
1061
1433
  replace: true,
1062
1434
  transclude: true,
1063
1435
  templateUrl: function(tElement) {
1436
+ // Needed so the uiSelect can detect the transcluded content
1437
+ tElement.addClass('ui-select-match');
1438
+
1439
+ var parent = tElement.parent();
1064
1440
  // Gets theme attribute from parent (ui-select)
1065
- var theme = tElement.parent().attr('theme') || uiSelectConfig.theme;
1066
- var multi = tElement.parent().attr('multiple');
1067
- return theme + (multi ? '/match-multiple.tpl.html' : '/match.tpl.html');
1441
+ var theme = getAttribute(parent, 'theme') || uiSelectConfig.theme;
1442
+ var multi = angular.isDefined(getAttribute(parent, 'multiple'));
1443
+
1444
+ return theme + (multi ? '/match-multiple.tpl.html' : '/match.tpl.html');
1068
1445
  },
1069
1446
  link: function(scope, element, attrs, $select) {
1070
1447
  $select.lockChoiceExpression = attrs.uiLockChoice;
@@ -1085,6 +1462,17 @@ uis.directive('uiSelectMatch', ['uiSelectConfig', function(uiSelectConfig) {
1085
1462
 
1086
1463
  }
1087
1464
  };
1465
+
1466
+ function getAttribute(elem, attribute) {
1467
+ if (elem[0].hasAttribute(attribute))
1468
+ return elem.attr(attribute);
1469
+
1470
+ if (elem[0].hasAttribute('data-' + attribute))
1471
+ return elem.attr('data-' + attribute);
1472
+
1473
+ if (elem[0].hasAttribute('x-' + attribute))
1474
+ return elem.attr('x-' + attribute);
1475
+ }
1088
1476
  }]);
1089
1477
 
1090
1478
  uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelectMinErr, $timeout) {
@@ -1098,7 +1486,10 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1098
1486
  $select = $scope.$select,
1099
1487
  ngModel;
1100
1488
 
1101
- //Wait for link fn to inject it
1489
+ if (angular.isUndefined($select.selected))
1490
+ $select.selected = [];
1491
+
1492
+ //Wait for link fn to inject it
1102
1493
  $scope.$evalAsync(function(){ ngModel = $scope.ngModel; });
1103
1494
 
1104
1495
  ctrl.activeMatchIndex = -1;
@@ -1110,19 +1501,23 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1110
1501
 
1111
1502
  ctrl.refreshComponent = function(){
1112
1503
  //Remove already selected items
1113
- //e.g. When user clicks on a selection, the selected array changes and
1504
+ //e.g. When user clicks on a selection, the selected array changes and
1114
1505
  //the dropdown should remove that item
1115
- $select.refreshItems();
1116
- $select.sizeSearchInput();
1506
+ if($select.refreshItems){
1507
+ $select.refreshItems();
1508
+ }
1509
+ if($select.sizeSearchInput){
1510
+ $select.sizeSearchInput();
1511
+ }
1117
1512
  };
1118
1513
 
1119
1514
  // Remove item from multiple select
1120
1515
  ctrl.removeChoice = function(index){
1121
1516
 
1122
- var removedChoice = $select.selected[index];
1517
+ // if the choice is locked, don't remove it
1518
+ if($select.isLocked(null, index)) return false;
1123
1519
 
1124
- // if the choice is locked, can't remove it
1125
- if(removedChoice._uiSelectChoiceLocked) return;
1520
+ var removedChoice = $select.selected[index];
1126
1521
 
1127
1522
  var locals = {};
1128
1523
  locals[$select.parserResult.itemName] = removedChoice;
@@ -1141,11 +1536,12 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1141
1536
 
1142
1537
  ctrl.updateModel();
1143
1538
 
1539
+ return true;
1144
1540
  };
1145
1541
 
1146
1542
  ctrl.getPlaceholder = function(){
1147
1543
  //Refactor single?
1148
- if($select.selected.length) return;
1544
+ if($select.selected && $select.selected.length) return;
1149
1545
  return $select.placeholder;
1150
1546
  };
1151
1547
 
@@ -1162,11 +1558,15 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1162
1558
  //$select.selected = raw selected objects (ignoring any property binding)
1163
1559
 
1164
1560
  $select.multiple = true;
1165
- $select.removeSelected = true;
1166
1561
 
1167
1562
  //Input that will handle focus
1168
1563
  $select.focusInput = $select.searchInput;
1169
1564
 
1565
+ //Properly check for empty if set to multiple
1566
+ ngModel.$isEmpty = function(value) {
1567
+ return !value || value.length === 0;
1568
+ };
1569
+
1170
1570
  //From view --> model
1171
1571
  ngModel.$parsers.unshift(function () {
1172
1572
  var locals = {},
@@ -1183,7 +1583,7 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1183
1583
 
1184
1584
  // From model --> view
1185
1585
  ngModel.$formatters.unshift(function (inputValue) {
1186
- var data = $select.parserResult.source (scope, { $select : {search:''}}), //Overwrite $search
1586
+ var data = $select.parserResult && $select.parserResult.source (scope, { $select : {search:''}}), //Overwrite $search
1187
1587
  locals = {},
1188
1588
  result;
1189
1589
  if (!data) return inputValue;
@@ -1194,10 +1594,13 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1194
1594
  locals[$select.parserResult.itemName] = list[p];
1195
1595
  result = $select.parserResult.modelMapper(scope, locals);
1196
1596
  if($select.parserResult.trackByExp){
1197
- var matches = /\.(.+)/.exec($select.parserResult.trackByExp);
1198
- if(matches.length>0 && result[matches[1]] == value[matches[1]]){
1199
- resultMultiple.unshift(list[p]);
1200
- return true;
1597
+ var propsItemNameMatches = /(\w*)\./.exec($select.parserResult.trackByExp);
1598
+ var matches = /\.([^\s]+)/.exec($select.parserResult.trackByExp);
1599
+ if(propsItemNameMatches && propsItemNameMatches.length > 0 && propsItemNameMatches[1] == $select.parserResult.itemName){
1600
+ if(matches && matches.length>0 && result[matches[1]] == value[matches[1]]){
1601
+ resultMultiple.unshift(list[p]);
1602
+ return true;
1603
+ }
1201
1604
  }
1202
1605
  }
1203
1606
  if (angular.equals(result,value)){
@@ -1209,7 +1612,7 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1209
1612
  };
1210
1613
  if (!inputValue) return resultMultiple; //If ngModel was undefined
1211
1614
  for (var k = inputValue.length - 1; k >= 0; k--) {
1212
- //Check model array of currently selected items
1615
+ //Check model array of currently selected items
1213
1616
  if (!checkFnMultiple($select.selected, inputValue[k])){
1214
1617
  //Check model array of all items available
1215
1618
  if (!checkFnMultiple(data, inputValue[k])){
@@ -1220,11 +1623,14 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1220
1623
  }
1221
1624
  return resultMultiple;
1222
1625
  });
1223
-
1224
- //Watch for external model changes
1626
+
1627
+ //Watch for external model changes
1225
1628
  scope.$watchCollection(function(){ return ngModel.$modelValue; }, function(newValue, oldValue) {
1226
1629
  if (oldValue != newValue){
1227
- ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
1630
+ //update the view value with fresh data from items, if there is a valid model value
1631
+ if(angular.isDefined(ngModel.$modelValue)) {
1632
+ ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
1633
+ }
1228
1634
  $selectMultiple.refreshComponent();
1229
1635
  }
1230
1636
  });
@@ -1234,16 +1640,20 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1234
1640
  if(!angular.isArray(ngModel.$viewValue)){
1235
1641
  // Have tolerance for null or undefined values
1236
1642
  if(angular.isUndefined(ngModel.$viewValue) || ngModel.$viewValue === null){
1237
- $select.selected = [];
1643
+ ngModel.$viewValue = [];
1238
1644
  } else {
1239
1645
  throw uiSelectMinErr('multiarr', "Expected model value to be array but got '{0}'", ngModel.$viewValue);
1240
1646
  }
1241
1647
  }
1242
1648
  $select.selected = ngModel.$viewValue;
1649
+ $selectMultiple.refreshComponent();
1243
1650
  scope.$evalAsync(); //To force $digest
1244
1651
  };
1245
1652
 
1246
1653
  scope.$on('uis:select', function (event, item) {
1654
+ if($select.selected.length >= $select.limit) {
1655
+ return;
1656
+ }
1247
1657
  $select.selected.push(item);
1248
1658
  $selectMultiple.updateModel();
1249
1659
  });
@@ -1314,11 +1724,16 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1314
1724
  case KEY.BACKSPACE:
1315
1725
  // Remove selected item and select previous/first
1316
1726
  if(~$selectMultiple.activeMatchIndex){
1317
- $selectMultiple.removeChoice(curr);
1318
- return prev;
1319
- }
1320
- // Select last item
1321
- else return last;
1727
+ if($selectMultiple.removeChoice(curr)) {
1728
+ return prev;
1729
+ } else {
1730
+ return curr;
1731
+ }
1732
+
1733
+ } else {
1734
+ // If nothing yet selected, select last item
1735
+ return last;
1736
+ }
1322
1737
  break;
1323
1738
  case KEY.DELETE:
1324
1739
  // Remove selected item and select next item
@@ -1379,12 +1794,22 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1379
1794
  stashArr = stashArr.slice(1,stashArr.length);
1380
1795
  }
1381
1796
  newItem = $select.tagging.fct($select.search);
1382
- newItem.isTag = true;
1383
- // verify the the tag doesn't match the value of an existing item
1384
- if ( stashArr.filter( function (origItem) { return angular.equals( origItem, $select.tagging.fct($select.search) ); } ).length > 0 ) {
1797
+ // verify the new tag doesn't match the value of a possible selection choice or an already selected item.
1798
+ if (
1799
+ stashArr.some(function (origItem) {
1800
+ return angular.equals(origItem, newItem);
1801
+ }) ||
1802
+ $select.selected.some(function (origItem) {
1803
+ return angular.equals(origItem, newItem);
1804
+ })
1805
+ ) {
1806
+ scope.$evalAsync(function () {
1807
+ $select.activeIndex = 0;
1808
+ $select.items = items;
1809
+ });
1385
1810
  return;
1386
1811
  }
1387
- newItem.isTag = true;
1812
+ if (newItem) newItem.isTag = true;
1388
1813
  // handle newItem string and stripping dupes in tagging string context
1389
1814
  } else {
1390
1815
  // find any tagging items already in the $select.items array and store them
@@ -1433,12 +1858,23 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1433
1858
  items = items.slice(dupeIndex+1,items.length-1);
1434
1859
  } else {
1435
1860
  items = [];
1436
- items.push(newItem);
1861
+ if (newItem) items.push(newItem);
1437
1862
  items = items.concat(stashArr);
1438
1863
  }
1439
1864
  scope.$evalAsync( function () {
1440
1865
  $select.activeIndex = 0;
1441
1866
  $select.items = items;
1867
+
1868
+ if ($select.isGrouped) {
1869
+ // update item references in groups, so that indexOf will work after angular.copy
1870
+ var itemsWithoutTag = newItem ? items.slice(1) : items;
1871
+ $select.setItemsFn(itemsWithoutTag);
1872
+ if (newItem) {
1873
+ // add tag item as a new group
1874
+ $select.items.unshift(newItem);
1875
+ $select.groups.unshift({name: '', items: [newItem], tagging: true});
1876
+ }
1877
+ }
1442
1878
  });
1443
1879
  }
1444
1880
  });
@@ -1469,9 +1905,11 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1469
1905
  // handle the object tagging implementation
1470
1906
  } else {
1471
1907
  var mockObj = tempArr[i];
1472
- mockObj.isTag = true;
1908
+ if (angular.isObject(mockObj)) {
1909
+ mockObj.isTag = true;
1910
+ }
1473
1911
  if ( angular.equals(mockObj, needle) ) {
1474
- dupeIndex = i;
1912
+ dupeIndex = i;
1475
1913
  }
1476
1914
  }
1477
1915
  }
@@ -1488,6 +1926,25 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec
1488
1926
  }
1489
1927
  };
1490
1928
  }]);
1929
+
1930
+ uis.directive('uiSelectNoChoice',
1931
+ ['uiSelectConfig', function (uiSelectConfig) {
1932
+ return {
1933
+ restrict: 'EA',
1934
+ require: '^uiSelect',
1935
+ replace: true,
1936
+ transclude: true,
1937
+ templateUrl: function (tElement) {
1938
+ // Needed so the uiSelect can detect the transcluded content
1939
+ tElement.addClass('ui-select-no-choice');
1940
+
1941
+ // Gets theme attribute from parent (ui-select)
1942
+ var theme = tElement.parent().attr('theme') || uiSelectConfig.theme;
1943
+ return theme + '/no-choice.tpl.html';
1944
+ }
1945
+ };
1946
+ }]);
1947
+
1491
1948
  uis.directive('uiSelectSingle', ['$timeout','$compile', function($timeout, $compile) {
1492
1949
  return {
1493
1950
  restrict: 'EA',
@@ -1508,14 +1965,14 @@ uis.directive('uiSelectSingle', ['$timeout','$compile', function($timeout, $comp
1508
1965
 
1509
1966
  //From model --> view
1510
1967
  ngModel.$formatters.unshift(function (inputValue) {
1511
- var data = $select.parserResult.source (scope, { $select : {search:''}}), //Overwrite $search
1968
+ var data = $select.parserResult && $select.parserResult.source (scope, { $select : {search:''}}), //Overwrite $search
1512
1969
  locals = {},
1513
1970
  result;
1514
1971
  if (data){
1515
1972
  var checkFnSingle = function(d){
1516
1973
  locals[$select.parserResult.itemName] = d;
1517
1974
  result = $select.parserResult.modelMapper(scope, locals);
1518
- return result == inputValue;
1975
+ return result === inputValue;
1519
1976
  };
1520
1977
  //If possible pass same object stored in $select.selected
1521
1978
  if ($select.selected && checkFnSingle($select.selected)) {
@@ -1612,44 +2069,48 @@ uis.directive('uiSelectSingle', ['$timeout','$compile', function($timeout, $comp
1612
2069
  }
1613
2070
  };
1614
2071
  }]);
2072
+
1615
2073
  // Make multiple matches sortable
1616
2074
  uis.directive('uiSelectSort', ['$timeout', 'uiSelectConfig', 'uiSelectMinErr', function($timeout, uiSelectConfig, uiSelectMinErr) {
1617
2075
  return {
1618
- require: '^uiSelect',
1619
- link: function(scope, element, attrs, $select) {
2076
+ require: ['^^uiSelect', '^ngModel'],
2077
+ link: function(scope, element, attrs, ctrls) {
1620
2078
  if (scope[attrs.uiSelectSort] === null) {
1621
- throw uiSelectMinErr('sort', "Expected a list to sort");
2079
+ throw uiSelectMinErr('sort', 'Expected a list to sort');
1622
2080
  }
1623
2081
 
2082
+ var $select = ctrls[0];
2083
+ var $ngModel = ctrls[1];
2084
+
1624
2085
  var options = angular.extend({
1625
2086
  axis: 'horizontal'
1626
2087
  },
1627
2088
  scope.$eval(attrs.uiSelectSortOptions));
1628
2089
 
1629
- var axis = options.axis,
1630
- draggingClassName = 'dragging',
1631
- droppingClassName = 'dropping',
1632
- droppingBeforeClassName = 'dropping-before',
1633
- droppingAfterClassName = 'dropping-after';
2090
+ var axis = options.axis;
2091
+ var draggingClassName = 'dragging';
2092
+ var droppingClassName = 'dropping';
2093
+ var droppingBeforeClassName = 'dropping-before';
2094
+ var droppingAfterClassName = 'dropping-after';
1634
2095
 
1635
2096
  scope.$watch(function(){
1636
2097
  return $select.sortable;
1637
- }, function(n){
1638
- if (n) {
2098
+ }, function(newValue){
2099
+ if (newValue) {
1639
2100
  element.attr('draggable', true);
1640
2101
  } else {
1641
2102
  element.removeAttr('draggable');
1642
2103
  }
1643
2104
  });
1644
2105
 
1645
- element.on('dragstart', function(e) {
2106
+ element.on('dragstart', function(event) {
1646
2107
  element.addClass(draggingClassName);
1647
2108
 
1648
- (e.dataTransfer || e.originalEvent.dataTransfer).setData('text/plain', scope.$index);
2109
+ (event.dataTransfer || event.originalEvent.dataTransfer).setData('text', scope.$index.toString());
1649
2110
  });
1650
2111
 
1651
2112
  element.on('dragend', function() {
1652
- element.removeClass(draggingClassName);
2113
+ removeClass(draggingClassName);
1653
2114
  });
1654
2115
 
1655
2116
  var move = function(from, to) {
@@ -1657,27 +2118,33 @@ uis.directive('uiSelectSort', ['$timeout', 'uiSelectConfig', 'uiSelectMinErr', f
1657
2118
  this.splice(to, 0, this.splice(from, 1)[0]);
1658
2119
  };
1659
2120
 
1660
- var dragOverHandler = function(e) {
1661
- e.preventDefault();
2121
+ var removeClass = function(className) {
2122
+ angular.forEach($select.$element.querySelectorAll('.' + className), function(el){
2123
+ angular.element(el).removeClass(className);
2124
+ });
2125
+ };
1662
2126
 
1663
- var offset = axis === 'vertical' ? e.offsetY || e.layerY || (e.originalEvent ? e.originalEvent.offsetY : 0) : e.offsetX || e.layerX || (e.originalEvent ? e.originalEvent.offsetX : 0);
2127
+ var dragOverHandler = function(event) {
2128
+ event.preventDefault();
2129
+
2130
+ var offset = axis === 'vertical' ? event.offsetY || event.layerY || (event.originalEvent ? event.originalEvent.offsetY : 0) : event.offsetX || event.layerX || (event.originalEvent ? event.originalEvent.offsetX : 0);
1664
2131
 
1665
2132
  if (offset < (this[axis === 'vertical' ? 'offsetHeight' : 'offsetWidth'] / 2)) {
1666
- element.removeClass(droppingAfterClassName);
2133
+ removeClass(droppingAfterClassName);
1667
2134
  element.addClass(droppingBeforeClassName);
1668
2135
 
1669
2136
  } else {
1670
- element.removeClass(droppingBeforeClassName);
2137
+ removeClass(droppingBeforeClassName);
1671
2138
  element.addClass(droppingAfterClassName);
1672
2139
  }
1673
2140
  };
1674
2141
 
1675
2142
  var dropTimeout;
1676
2143
 
1677
- var dropHandler = function(e) {
1678
- e.preventDefault();
2144
+ var dropHandler = function(event) {
2145
+ event.preventDefault();
1679
2146
 
1680
- var droppedItemIndex = parseInt((e.dataTransfer || e.originalEvent.dataTransfer).getData('text/plain'), 10);
2147
+ var droppedItemIndex = parseInt((event.dataTransfer || event.originalEvent.dataTransfer).getData('text'), 10);
1681
2148
 
1682
2149
  // prevent event firing multiple times in firefox
1683
2150
  $timeout.cancel(dropTimeout);
@@ -1687,9 +2154,9 @@ uis.directive('uiSelectSort', ['$timeout', 'uiSelectConfig', 'uiSelectMinErr', f
1687
2154
  };
1688
2155
 
1689
2156
  var _dropHandler = function(droppedItemIndex) {
1690
- var theList = scope.$eval(attrs.uiSelectSort),
1691
- itemToMove = theList[droppedItemIndex],
1692
- newIndex = null;
2157
+ var theList = scope.$eval(attrs.uiSelectSort);
2158
+ var itemToMove = theList[droppedItemIndex];
2159
+ var newIndex = null;
1693
2160
 
1694
2161
  if (element.hasClass(droppingBeforeClassName)) {
1695
2162
  if (droppedItemIndex < scope.$index) {
@@ -1707,6 +2174,8 @@ uis.directive('uiSelectSort', ['$timeout', 'uiSelectConfig', 'uiSelectMinErr', f
1707
2174
 
1708
2175
  move.apply(theList, [droppedItemIndex, newIndex]);
1709
2176
 
2177
+ $ngModel.$setViewValue(Date.now());
2178
+
1710
2179
  scope.$apply(function() {
1711
2180
  scope.$emit('uiSelectSort:change', {
1712
2181
  array: theList,
@@ -1716,9 +2185,9 @@ uis.directive('uiSelectSort', ['$timeout', 'uiSelectConfig', 'uiSelectMinErr', f
1716
2185
  });
1717
2186
  });
1718
2187
 
1719
- element.removeClass(droppingClassName);
1720
- element.removeClass(droppingBeforeClassName);
1721
- element.removeClass(droppingAfterClassName);
2188
+ removeClass(droppingClassName);
2189
+ removeClass(droppingBeforeClassName);
2190
+ removeClass(droppingAfterClassName);
1722
2191
 
1723
2192
  element.off('drop', dropHandler);
1724
2193
  };
@@ -1734,13 +2203,14 @@ uis.directive('uiSelectSort', ['$timeout', 'uiSelectConfig', 'uiSelectMinErr', f
1734
2203
  element.on('drop', dropHandler);
1735
2204
  });
1736
2205
 
1737
- element.on('dragleave', function(e) {
1738
- if (e.target != element) {
2206
+ element.on('dragleave', function(event) {
2207
+ if (event.target != element) {
1739
2208
  return;
1740
2209
  }
1741
- element.removeClass(droppingClassName);
1742
- element.removeClass(droppingBeforeClassName);
1743
- element.removeClass(droppingAfterClassName);
2210
+
2211
+ removeClass(droppingClassName);
2212
+ removeClass(droppingBeforeClassName);
2213
+ removeClass(droppingAfterClassName);
1744
2214
 
1745
2215
  element.off('dragover', dragOverHandler);
1746
2216
  element.off('drop', dropHandler);
@@ -1771,18 +2241,58 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE
1771
2241
  */
1772
2242
  self.parse = function(expression) {
1773
2243
 
1774
- var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
2244
+
2245
+ var match;
2246
+ //var isObjectCollection = /\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)/.test(expression);
2247
+ // If an array is used as collection
2248
+
2249
+ // if (isObjectCollection){
2250
+ // 000000000000000000000000000000111111111000000000000000222222222222220033333333333333333333330000444444444444444444000000000000000055555555555000000000000000000000066666666600000000
2251
+ match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(\s*[\s\S]+?)?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
2252
+
2253
+ // 1 Alias
2254
+ // 2 Item
2255
+ // 3 Key on (key,value)
2256
+ // 4 Value on (key,value)
2257
+ // 5 Source expression (including filters)
2258
+ // 6 Track by
1775
2259
 
1776
2260
  if (!match) {
1777
2261
  throw uiSelectMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
1778
2262
  expression);
1779
2263
  }
2264
+
2265
+ var source = match[5],
2266
+ filters = '';
2267
+
2268
+ // When using (key,value) ui-select requires filters to be extracted, since the object
2269
+ // is converted to an array for $select.items
2270
+ // (in which case the filters need to be reapplied)
2271
+ if (match[3]) {
2272
+ // Remove any enclosing parenthesis
2273
+ source = match[5].replace(/(^\()|(\)$)/g, '');
2274
+ // match all after | but not after ||
2275
+ var filterMatch = match[5].match(/^\s*(?:[\s\S]+?)(?:[^\|]|\|\|)+([\s\S]*)\s*$/);
2276
+ if(filterMatch && filterMatch[1].trim()) {
2277
+ filters = filterMatch[1];
2278
+ source = source.replace(filters, '');
2279
+ }
2280
+ }
1780
2281
 
1781
2282
  return {
1782
- itemName: match[2], // (lhs) Left-hand side,
1783
- source: $parse(match[3]),
1784
- trackByExp: match[4],
1785
- modelMapper: $parse(match[1] || match[2])
2283
+ itemName: match[4] || match[2], // (lhs) Left-hand side,
2284
+ keyName: match[3], //for (key, value) syntax
2285
+ source: $parse(source),
2286
+ filters: filters,
2287
+ trackByExp: match[6],
2288
+ modelMapper: $parse(match[1] || match[4] || match[2]),
2289
+ repeatExpression: function (grouped) {
2290
+ var expression = this.itemName + ' in ' + (grouped ? '$group.items' : '$select.items');
2291
+ if (this.trackByExp) {
2292
+ expression += ' track by ' + this.trackByExp;
2293
+ }
2294
+ return expression;
2295
+ }
1786
2296
  };
1787
2297
 
1788
2298
  };
@@ -1791,26 +2301,22 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE
1791
2301
  return '$group in $select.groups';
1792
2302
  };
1793
2303
 
1794
- self.getNgRepeatExpression = function(itemName, source, trackByExp, grouped) {
1795
- var expression = itemName + ' in ' + (grouped ? '$group.items' : source);
1796
- if (trackByExp) {
1797
- expression += ' track by ' + trackByExp;
1798
- }
1799
- return expression;
1800
- };
1801
2304
  }]);
1802
2305
 
1803
2306
  }());
1804
- angular.module("ui.select").run(["$templateCache", function($templateCache) {$templateCache.put("bootstrap/choices.tpl.html","<ul class=\"ui-select-choices ui-select-choices-content ui-select-dropdown dropdown-menu\" role=\"listbox\" ng-show=\"$select.items.length > 0\"><li class=\"ui-select-choices-group\" id=\"ui-select-choices-{{ $select.generatedId }}\"><div class=\"divider\" ng-show=\"$select.isGrouped && $index > 0\"></div><div ng-show=\"$select.isGrouped\" class=\"ui-select-choices-group-label dropdown-header\" ng-bind=\"$group.name\"></div><div id=\"ui-select-choices-row-{{ $select.generatedId }}-{{$index}}\" class=\"ui-select-choices-row\" ng-class=\"{active: $select.isActive(this), disabled: $select.isDisabled(this)}\" role=\"option\"><a href=\"javascript:void(0)\" class=\"ui-select-choices-row-inner\"></a></div></li></ul>");
1805
- $templateCache.put("bootstrap/match-multiple.tpl.html","<span class=\"ui-select-match\"><span ng-repeat=\"$item in $select.selected\"><span class=\"ui-select-match-item btn btn-default btn-xs\" tabindex=\"-1\" type=\"button\" ng-disabled=\"$select.disabled\" ng-click=\"$selectMultiple.activeMatchIndex = $index;\" ng-class=\"{\'btn-primary\':$selectMultiple.activeMatchIndex === $index, \'select-locked\':$select.isLocked(this, $index)}\" ui-select-sort=\"$select.selected\"><span class=\"close ui-select-match-close\" ng-hide=\"$select.disabled\" ng-click=\"$selectMultiple.removeChoice($index)\">&nbsp;&times;</span> <span uis-transclude-append=\"\"></span></span></span></span>");
1806
- $templateCache.put("bootstrap/match.tpl.html","<div class=\"ui-select-match\" ng-hide=\"$select.open\" ng-disabled=\"$select.disabled\" ng-class=\"{\'btn-default-focus\':$select.focus}\"><span tabindex=\"-1\" class=\"btn btn-default form-control ui-select-toggle\" aria-label=\"{{ $select.baseTitle }} activate\" ng-disabled=\"$select.disabled\" ng-click=\"$select.activate()\" style=\"outline: 0;\"><span ng-show=\"$select.isEmpty()\" class=\"ui-select-placeholder text-muted\">{{$select.placeholder}}</span> <span ng-hide=\"$select.isEmpty()\" class=\"ui-select-match-text pull-left\" ng-class=\"{\'ui-select-allow-clear\': $select.allowClear && !$select.isEmpty()}\" ng-transclude=\"\"></span> <i class=\"caret pull-right\" ng-click=\"$select.toggle($event)\"></i> <a ng-show=\"$select.allowClear && !$select.isEmpty()\" aria-label=\"{{ $select.baseTitle }} clear\" style=\"margin-right: 10px\" ng-click=\"$select.clear($event)\" class=\"btn btn-xs btn-link pull-right\"><i class=\"glyphicon glyphicon-remove\" aria-hidden=\"true\"></i></a></span></div>");
1807
- $templateCache.put("bootstrap/select-multiple.tpl.html","<div class=\"ui-select-container ui-select-multiple ui-select-bootstrap dropdown form-control\" ng-class=\"{open: $select.open}\"><div><div class=\"ui-select-match\"></div><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" class=\"ui-select-search input-xs\" placeholder=\"{{$selectMultiple.getPlaceholder()}}\" ng-disabled=\"$select.disabled\" ng-hide=\"$select.disabled\" ng-click=\"$select.activate()\" ng-model=\"$select.search\" role=\"combobox\" aria-label=\"{{ $select.baseTitle }}\" ondrop=\"return false;\"></div><div class=\"ui-select-choices\"></div></div>");
1808
- $templateCache.put("bootstrap/select.tpl.html","<div class=\"ui-select-container ui-select-bootstrap dropdown\" ng-class=\"{open: $select.open}\"><div class=\"ui-select-match\"></div><input type=\"text\" autocomplete=\"off\" tabindex=\"-1\" aria-expanded=\"true\" aria-label=\"{{ $select.baseTitle }}\" aria-owns=\"ui-select-choices-{{ $select.generatedId }}\" aria-activedescendant=\"ui-select-choices-row-{{ $select.generatedId }}-{{ $select.activeIndex }}\" class=\"form-control ui-select-search\" placeholder=\"{{$select.placeholder}}\" ng-model=\"$select.search\" ng-show=\"$select.searchEnabled && $select.open\"><div class=\"ui-select-choices\"></div></div>");
1809
- $templateCache.put("select2/choices.tpl.html","<ul class=\"ui-select-choices ui-select-choices-content select2-results\"><li class=\"ui-select-choices-group\" ng-class=\"{\'select2-result-with-children\': $select.choiceGrouped($group) }\"><div ng-show=\"$select.choiceGrouped($group)\" class=\"ui-select-choices-group-label select2-result-label\" ng-bind=\"$group.name\"></div><ul role=\"listbox\" id=\"ui-select-choices-{{ $select.generatedId }}\" ng-class=\"{\'select2-result-sub\': $select.choiceGrouped($group), \'select2-result-single\': !$select.choiceGrouped($group) }\"><li role=\"option\" id=\"ui-select-choices-row-{{ $select.generatedId }}-{{$index}}\" class=\"ui-select-choices-row\" ng-class=\"{\'select2-highlighted\': $select.isActive(this), \'select2-disabled\': $select.isDisabled(this)}\"><div class=\"select2-result-label ui-select-choices-row-inner\"></div></li></ul></li></ul>");
1810
- $templateCache.put("select2/match-multiple.tpl.html","<span class=\"ui-select-match\"><li class=\"ui-select-match-item select2-search-choice\" ng-repeat=\"$item in $select.selected\" ng-class=\"{\'select2-search-choice-focus\':$selectMultiple.activeMatchIndex === $index, \'select2-locked\':$select.isLocked(this, $index)}\" ui-select-sort=\"$select.selected\"><span uis-transclude-append=\"\"></span> <a href=\"javascript:;\" class=\"ui-select-match-close select2-search-choice-close\" ng-click=\"$selectMultiple.removeChoice($index)\" tabindex=\"-1\"></a></li></span>");
2307
+ angular.module("ui.select").run(["$templateCache", function($templateCache) {$templateCache.put("bootstrap/choices.tpl.html","<ul class=\"ui-select-choices ui-select-choices-content ui-select-dropdown dropdown-menu\" role=\"listbox\" ng-show=\"$select.open && $select.items.length > 0\"><li class=\"ui-select-choices-group\" id=\"ui-select-choices-{{ $select.generatedId }}\"><div class=\"divider\" ng-show=\"$select.isGrouped && $index > 0\"></div><div ng-show=\"$select.isGrouped\" class=\"ui-select-choices-group-label dropdown-header\" ng-bind=\"$group.name\"></div><div ng-attr-id=\"ui-select-choices-row-{{ $select.generatedId }}-{{$index}}\" class=\"ui-select-choices-row\" ng-class=\"{active: $select.isActive(this), disabled: $select.isDisabled(this)}\" role=\"option\"><a href=\"\" class=\"ui-select-choices-row-inner\"></a></div></li></ul>");
2308
+ $templateCache.put("bootstrap/match-multiple.tpl.html","<span class=\"ui-select-match\"><span ng-repeat=\"$item in $select.selected track by $index\"><span class=\"ui-select-match-item btn btn-default btn-xs\" tabindex=\"-1\" type=\"button\" ng-disabled=\"$select.disabled\" ng-click=\"$selectMultiple.activeMatchIndex = $index;\" ng-class=\"{\'btn-primary\':$selectMultiple.activeMatchIndex === $index, \'select-locked\':$select.isLocked(this, $index)}\" ui-select-sort=\"$select.selected\"><span class=\"close ui-select-match-close\" ng-hide=\"$select.disabled\" ng-click=\"$selectMultiple.removeChoice($index)\">&nbsp;&times;</span> <span uis-transclude-append=\"\"></span></span></span></span>");
2309
+ $templateCache.put("bootstrap/match.tpl.html","<div class=\"ui-select-match\" ng-hide=\"$select.open && $select.searchEnabled\" ng-disabled=\"$select.disabled\" ng-class=\"{\'btn-default-focus\':$select.focus}\"><span tabindex=\"-1\" class=\"btn btn-default form-control ui-select-toggle\" aria-label=\"{{ $select.baseTitle }} activate\" ng-disabled=\"$select.disabled\" ng-click=\"$select.activate()\" style=\"outline: 0;\"><span ng-show=\"$select.isEmpty()\" class=\"ui-select-placeholder text-muted\">{{$select.placeholder}}</span> <span ng-hide=\"$select.isEmpty()\" class=\"ui-select-match-text pull-left\" ng-class=\"{\'ui-select-allow-clear\': $select.allowClear && !$select.isEmpty()}\" ng-transclude=\"\"></span> <i class=\"caret pull-right\" ng-click=\"$select.toggle($event)\"></i> <a ng-show=\"$select.allowClear && !$select.isEmpty() && ($select.disabled !== true)\" aria-label=\"{{ $select.baseTitle }} clear\" style=\"margin-right: 10px\" ng-click=\"$select.clear($event)\" class=\"btn btn-xs btn-link pull-right\"><i class=\"glyphicon glyphicon-remove\" aria-hidden=\"true\"></i></a></span></div>");
2310
+ $templateCache.put("bootstrap/no-choice.tpl.html","<ul class=\"ui-select-no-choice dropdown-menu\" ng-show=\"$select.items.length == 0\"><li ng-transclude=\"\"></li></ul>");
2311
+ $templateCache.put("bootstrap/select-multiple.tpl.html","<div class=\"ui-select-container ui-select-multiple ui-select-bootstrap dropdown form-control\" ng-class=\"{open: $select.open}\"><div><div class=\"ui-select-match\"></div><input type=\"search\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" class=\"ui-select-search input-xs\" placeholder=\"{{$selectMultiple.getPlaceholder()}}\" ng-disabled=\"$select.disabled\" ng-hide=\"$select.disabled\" ng-click=\"$select.activate()\" ng-model=\"$select.search\" role=\"combobox\" aria-label=\"{{ $select.baseTitle }}\" ondrop=\"return false;\"></div><div class=\"ui-select-choices\"></div><div class=\"ui-select-no-choice\"></div></div>");
2312
+ $templateCache.put("bootstrap/select.tpl.html","<div class=\"ui-select-container ui-select-bootstrap dropdown\" ng-class=\"{open: $select.open}\"><div class=\"ui-select-match\"></div><input type=\"search\" autocomplete=\"off\" tabindex=\"-1\" aria-expanded=\"true\" aria-label=\"{{ $select.baseTitle }}\" aria-owns=\"ui-select-choices-{{ $select.generatedId }}\" aria-activedescendant=\"ui-select-choices-row-{{ $select.generatedId }}-{{ $select.activeIndex }}\" class=\"form-control ui-select-search\" placeholder=\"{{$select.placeholder}}\" ng-model=\"$select.search\" ng-show=\"$select.searchEnabled && $select.open\"><div class=\"ui-select-choices\"></div><div class=\"ui-select-no-choice\"></div></div>");
2313
+ $templateCache.put("select2/choices.tpl.html","<ul tabindex=\"-1\" class=\"ui-select-choices ui-select-choices-content select2-results\"><li class=\"ui-select-choices-group\" ng-class=\"{\'select2-result-with-children\': $select.choiceGrouped($group) }\"><div ng-show=\"$select.choiceGrouped($group)\" class=\"ui-select-choices-group-label select2-result-label\" ng-bind=\"$group.name\"></div><ul role=\"listbox\" id=\"ui-select-choices-{{ $select.generatedId }}\" ng-class=\"{\'select2-result-sub\': $select.choiceGrouped($group), \'select2-result-single\': !$select.choiceGrouped($group) }\"><li role=\"option\" ng-attr-id=\"ui-select-choices-row-{{ $select.generatedId }}-{{$index}}\" class=\"ui-select-choices-row\" ng-class=\"{\'select2-highlighted\': $select.isActive(this), \'select2-disabled\': $select.isDisabled(this)}\"><div class=\"select2-result-label ui-select-choices-row-inner\"></div></li></ul></li></ul>");
2314
+ $templateCache.put("select2/match-multiple.tpl.html","<span class=\"ui-select-match\"><li class=\"ui-select-match-item select2-search-choice\" ng-repeat=\"$item in $select.selected track by $index\" ng-class=\"{\'select2-search-choice-focus\':$selectMultiple.activeMatchIndex === $index, \'select2-locked\':$select.isLocked(this, $index)}\" ui-select-sort=\"$select.selected\"><span uis-transclude-append=\"\"></span> <a href=\"javascript:;\" class=\"ui-select-match-close select2-search-choice-close\" ng-click=\"$selectMultiple.removeChoice($index)\" tabindex=\"-1\"></a></li></span>");
1811
2315
  $templateCache.put("select2/match.tpl.html","<a class=\"select2-choice ui-select-match\" ng-class=\"{\'select2-default\': $select.isEmpty()}\" ng-click=\"$select.toggle($event)\" aria-label=\"{{ $select.baseTitle }} select\"><span ng-show=\"$select.isEmpty()\" class=\"select2-chosen\">{{$select.placeholder}}</span> <span ng-hide=\"$select.isEmpty()\" class=\"select2-chosen\" ng-transclude=\"\"></span> <abbr ng-if=\"$select.allowClear && !$select.isEmpty()\" class=\"select2-search-choice-close\" ng-click=\"$select.clear($event)\"></abbr> <span class=\"select2-arrow ui-select-toggle\"><b></b></span></a>");
1812
- $templateCache.put("select2/select-multiple.tpl.html","<div class=\"ui-select-container ui-select-multiple select2 select2-container select2-container-multi\" ng-class=\"{\'select2-container-active select2-dropdown-open open\': $select.open, \'select2-container-disabled\': $select.disabled}\"><ul class=\"select2-choices\"><span class=\"ui-select-match\"></span><li class=\"select2-search-field\"><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" role=\"combobox\" aria-expanded=\"true\" aria-owns=\"ui-select-choices-{{ $select.generatedId }}\" aria-label=\"{{ $select.baseTitle }}\" aria-activedescendant=\"ui-select-choices-row-{{ $select.generatedId }}-{{ $select.activeIndex }}\" class=\"select2-input ui-select-search\" placeholder=\"{{$selectMultiple.getPlaceholder()}}\" ng-disabled=\"$select.disabled\" ng-hide=\"$select.disabled\" ng-model=\"$select.search\" ng-click=\"$select.activate()\" style=\"width: 34px;\" ondrop=\"return false;\"></li></ul><div class=\"ui-select-dropdown select2-drop select2-with-searchbox select2-drop-active\" ng-class=\"{\'select2-display-none\': !$select.open}\"><div class=\"ui-select-choices\"></div></div></div>");
1813
- $templateCache.put("select2/select.tpl.html","<div class=\"ui-select-container select2 select2-container\" ng-class=\"{\'select2-container-active select2-dropdown-open open\': $select.open, \'select2-container-disabled\': $select.disabled, \'select2-container-active\': $select.focus, \'select2-allowclear\': $select.allowClear && !$select.isEmpty()}\"><div class=\"ui-select-match\"></div><div class=\"ui-select-dropdown select2-drop select2-with-searchbox select2-drop-active\" ng-class=\"{\'select2-display-none\': !$select.open}\"><div class=\"select2-search\" ng-show=\"$select.searchEnabled\"><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" role=\"combobox\" aria-expanded=\"true\" aria-owns=\"ui-select-choices-{{ $select.generatedId }}\" aria-label=\"{{ $select.baseTitle }}\" aria-activedescendant=\"ui-select-choices-row-{{ $select.generatedId }}-{{ $select.activeIndex }}\" class=\"ui-select-search select2-input\" ng-model=\"$select.search\"></div><div class=\"ui-select-choices\"></div></div></div>");
2316
+ $templateCache.put("select2/no-choice.tpl.html","<div class=\"ui-select-no-choice dropdown\" ng-show=\"$select.items.length == 0\"><div class=\"dropdown-content\"><div data-selectable=\"\" ng-transclude=\"\"></div></div></div>");
2317
+ $templateCache.put("select2/select-multiple.tpl.html","<div class=\"ui-select-container ui-select-multiple select2 select2-container select2-container-multi\" ng-class=\"{\'select2-container-active select2-dropdown-open open\': $select.open, \'select2-container-disabled\': $select.disabled}\"><ul class=\"select2-choices\"><span class=\"ui-select-match\"></span><li class=\"select2-search-field\"><input type=\"search\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" role=\"combobox\" aria-expanded=\"true\" aria-owns=\"ui-select-choices-{{ $select.generatedId }}\" aria-label=\"{{ $select.baseTitle }}\" aria-activedescendant=\"ui-select-choices-row-{{ $select.generatedId }}-{{ $select.activeIndex }}\" class=\"select2-input ui-select-search\" placeholder=\"{{$selectMultiple.getPlaceholder()}}\" ng-disabled=\"$select.disabled\" ng-hide=\"$select.disabled\" ng-model=\"$select.search\" ng-click=\"$select.activate()\" style=\"width: 34px;\" ondrop=\"return false;\"></li></ul><div class=\"ui-select-dropdown select2-drop select2-with-searchbox select2-drop-active\" ng-class=\"{\'select2-display-none\': !$select.open || $select.items.length === 0}\"><div class=\"ui-select-choices\"></div></div></div>");
2318
+ $templateCache.put("select2/select.tpl.html","<div class=\"ui-select-container select2 select2-container\" ng-class=\"{\'select2-container-active select2-dropdown-open open\': $select.open, \'select2-container-disabled\': $select.disabled, \'select2-container-active\': $select.focus, \'select2-allowclear\': $select.allowClear && !$select.isEmpty()}\"><div class=\"ui-select-match\"></div><div class=\"ui-select-dropdown select2-drop select2-with-searchbox select2-drop-active\" ng-class=\"{\'select2-display-none\': !$select.open}\"><div class=\"select2-search\" ng-show=\"$select.searchEnabled\"><input type=\"search\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" role=\"combobox\" aria-expanded=\"true\" aria-owns=\"ui-select-choices-{{ $select.generatedId }}\" aria-label=\"{{ $select.baseTitle }}\" aria-activedescendant=\"ui-select-choices-row-{{ $select.generatedId }}-{{ $select.activeIndex }}\" class=\"ui-select-search select2-input\" ng-model=\"$select.search\"></div><div class=\"ui-select-choices\"></div><div class=\"ui-select-no-choice\"></div></div></div>");
1814
2319
  $templateCache.put("selectize/choices.tpl.html","<div ng-show=\"$select.open\" class=\"ui-select-choices ui-select-dropdown selectize-dropdown single\"><div class=\"ui-select-choices-content selectize-dropdown-content\"><div class=\"ui-select-choices-group optgroup\" role=\"listbox\"><div ng-show=\"$select.isGrouped\" class=\"ui-select-choices-group-label optgroup-header\" ng-bind=\"$group.name\"></div><div role=\"option\" class=\"ui-select-choices-row\" ng-class=\"{active: $select.isActive(this), disabled: $select.isDisabled(this)}\"><div class=\"option ui-select-choices-row-inner\" data-selectable=\"\"></div></div></div></div></div>");
1815
- $templateCache.put("selectize/match.tpl.html","<div ng-hide=\"($select.open || $select.isEmpty())\" class=\"ui-select-match\" ng-transclude=\"\"></div>");
1816
- $templateCache.put("selectize/select.tpl.html","<div class=\"ui-select-container selectize-control single\" ng-class=\"{\'open\': $select.open}\"><div class=\"selectize-input\" ng-class=\"{\'focus\': $select.open, \'disabled\': $select.disabled, \'selectize-focus\' : $select.focus}\" ng-click=\"$select.activate()\"><div class=\"ui-select-match\"></div><input type=\"text\" autocomplete=\"off\" tabindex=\"-1\" class=\"ui-select-search ui-select-toggle\" ng-click=\"$select.toggle($event)\" placeholder=\"{{$select.placeholder}}\" ng-model=\"$select.search\" ng-hide=\"!$select.searchEnabled || ($select.selected && !$select.open)\" ng-disabled=\"$select.disabled\" aria-label=\"{{ $select.baseTitle }}\"></div><div class=\"ui-select-choices\"></div></div>");}]);
2320
+ $templateCache.put("selectize/match.tpl.html","<div ng-hide=\"$select.searchEnabled && ($select.open || $select.isEmpty())\" class=\"ui-select-match\" ng-transclude=\"\"></div>");
2321
+ $templateCache.put("selectize/no-choice.tpl.html","<div class=\"ui-select-no-choice selectize-dropdown\" ng-show=\"$select.items.length == 0\"><div class=\"selectize-dropdown-content\"><div data-selectable=\"\" ng-transclude=\"\"></div></div></div>");
2322
+ $templateCache.put("selectize/select.tpl.html","<div class=\"ui-select-container selectize-control single\" ng-class=\"{\'open\': $select.open}\"><div class=\"selectize-input\" ng-class=\"{\'focus\': $select.open, \'disabled\': $select.disabled, \'selectize-focus\' : $select.focus}\" ng-click=\"$select.open && !$select.searchEnabled ? $select.toggle($event) : $select.activate()\"><div class=\"ui-select-match\"></div><input type=\"search\" autocomplete=\"off\" tabindex=\"-1\" class=\"ui-select-search ui-select-toggle\" ng-click=\"$select.toggle($event)\" placeholder=\"{{$select.placeholder}}\" ng-model=\"$select.search\" ng-hide=\"!$select.searchEnabled || (!$select.isEmpty() && !$select.open)\" ng-disabled=\"$select.disabled\" aria-label=\"{{ $select.baseTitle }}\"></div><div class=\"ui-select-choices\"></div><div class=\"ui-select-no-choice\"></div></div>");}]);