angular-ui-select-rails 0.12.1.1 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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>");}]);