|
1 | 1 | /*!
|
2 | 2 | * ui-select
|
3 | 3 | * http://github.com/angular-ui/ui-select
|
4 |
| - * Version: 0.8.3 - 2014-10-20T16:06:30.607Z |
| 4 | + * Version: 0.8.3 - 2014-10-21T21:55:48.787Z |
5 | 5 | * License: MIT
|
6 | 6 | */
|
7 | 7 |
|
|
149 | 149 | * put as much logic in the controller (instead of the link functions) as possible so it can be easily tested.
|
150 | 150 | */
|
151 | 151 | .controller('uiSelectCtrl',
|
152 |
| - ['$scope', '$element', '$timeout', 'RepeatParser', 'uiSelectMinErr', |
153 |
| - function($scope, $element, $timeout, RepeatParser, uiSelectMinErr) { |
| 152 | + ['$scope', '$element', '$timeout', '$filter', 'RepeatParser', 'uiSelectMinErr', |
| 153 | + function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr) { |
154 | 154 |
|
155 | 155 | var ctrl = this;
|
156 | 156 |
|
|
171 | 171 | ctrl.refreshDelay = undefined; // Initialized inside uiSelectChoices directive link function
|
172 | 172 | ctrl.multiple = false; // Initialized inside uiSelect directive link function
|
173 | 173 | ctrl.disableChoiceExpression = undefined; // Initialized inside uiSelect directive link function
|
| 174 | + ctrl.$filter = $filter; |
174 | 175 |
|
175 | 176 | ctrl.isEmpty = function() {
|
176 | 177 | return angular.isUndefined(ctrl.selected) || ctrl.selected === null || ctrl.selected === '';
|
|
280 | 281 | if (!selectedItems.length) {
|
281 | 282 | setItemsFn(data);
|
282 | 283 | }else{
|
283 |
| - var filteredItems = data.filter(function(i) {return selectedItems.indexOf(i) < 0;}); |
284 |
| - setItemsFn(filteredItems); |
| 284 | + if ( data !== undefined ) { |
| 285 | + var filteredItems = data.filter(function(i) {return selectedItems.indexOf(i) < 0;}); |
| 286 | + setItemsFn(filteredItems); |
| 287 | + } |
285 | 288 | }
|
286 | 289 | ctrl.sizeSearchInput();
|
287 | 290 | });
|
|
317 | 320 |
|
318 | 321 | ctrl.isActive = function(itemScope) {
|
319 | 322 | var itemIndex = ctrl.items.indexOf(itemScope[ctrl.itemProperty]);
|
320 |
| - if ( ctrl.open && ( ctrl.items.indexOf(itemScope[ctrl.itemProperty]) === ctrl.activeIndex ) ) { |
321 |
| - var item = ctrl.items[itemIndex]; |
322 |
| - if ( typeof item === 'undefined' ) { |
323 |
| - return ctrl.open && false; |
324 |
| - } |
325 |
| - } |
326 | 323 | return ctrl.open && itemIndex === ctrl.activeIndex;
|
327 | 324 | };
|
328 | 325 |
|
|
343 | 340 | return isDisabled;
|
344 | 341 | };
|
345 | 342 |
|
346 |
| - // When the user clicks on an item inside the dropdown |
| 343 | + // When the user selects an item with ENTER or clicks the dropdown |
347 | 344 | ctrl.select = function(item, skipFocusser) {
|
348 | 345 |
|
| 346 | + if ( ! ctrl.items && ! ctrl.search ) return; |
| 347 | + |
349 | 348 | if (!item || !item._uiSelectChoiceDisabled) {
|
350 |
| - if(ctrl.tagging.isActivated && !item && ctrl.search.length > 0) { |
351 |
| - // create new item on the fly |
352 |
| - item = ctrl.tagging.fct !== undefined ? ctrl.tagging.fct(ctrl.search) : ctrl.search; |
| 349 | + if(ctrl.tagging.isActivated) { |
| 350 | + // if taggingLabel is disabled, we pull from ctrl.search val |
| 351 | + if ( ctrl.taggingLabel === false ) { |
| 352 | + if ( ctrl.activeIndex < 0 ) { |
| 353 | + item = ctrl.tagging.fct !== undefined ? ctrl.tagging.fct(ctrl.search) : ctrl.search; |
| 354 | + if ( ! angular.equals( ctrl.items[0], item ) ) { |
| 355 | + item = item; |
| 356 | + } else { |
| 357 | + return; |
| 358 | + } |
| 359 | + } else { |
| 360 | + // keyboard nav happened first, user selected from dropdown |
| 361 | + item = ctrl.items[ctrl.activeIndex]; |
| 362 | + } |
| 363 | + } else { |
| 364 | + // tagging always operates at index zero, taggingLabel === false pushes |
| 365 | + // the ctrl.search value without having it injected |
| 366 | + if ( ctrl.activeIndex === 0 ) { |
| 367 | + // ctrl.tagging pushes items to ctrl.items, so we only have empty val |
| 368 | + // for `item` if it is a detected duplicate |
| 369 | + if ( item === undefined ) return; |
| 370 | + // create new item on the fly |
| 371 | + if ( ctrl.taggingLabel && ctrl.taggingLabel.length > 0 ) {} |
| 372 | + item = ctrl.tagging.fct !== undefined ? ctrl.tagging.fct(ctrl.search) : item.replace(ctrl.taggingLabel,''); |
| 373 | + } |
| 374 | + } |
| 375 | + // search ctrl.selected for dupes potentially caused by tagging and return early if found |
| 376 | + if ( ctrl.selected && ctrl.selected.filter( function (selection) { return angular.equals(selection, item); }).length > 0 ) { |
| 377 | + ctrl.close(skipFocusser); |
| 378 | + return; |
| 379 | + } |
353 | 380 | }
|
354 | 381 |
|
355 | 382 | var locals = {};
|
|
549 | 576 | if ( ctrl.taggingTokens.isActivated ) {
|
550 | 577 | for (var i = 0; i < ctrl.taggingTokens.tokens.length; i++) {
|
551 | 578 | if ( ctrl.taggingTokens.tokens[i] === KEY.MAP[e.keyCode] ) {
|
552 |
| - ctrl.select(null, true); |
553 |
| - _searchInput.triggerHandler('tagged'); |
| 579 | + // make sure there is a new value to push via tagging |
| 580 | + if ( ctrl.search.length > 0 ) { |
| 581 | + ctrl.select(null, true); |
| 582 | + _searchInput.triggerHandler('tagged'); |
| 583 | + } |
554 | 584 | }
|
555 | 585 | }
|
556 | 586 | }
|
|
570 | 600 |
|
571 | 601 | });
|
572 | 602 |
|
| 603 | + _searchInput.on('keyup', function(e) { |
| 604 | + // Push a "create new" item into array if there is a search string |
| 605 | + if ( ctrl.tagging.isActivated && ctrl.search.length > 0 ) { |
| 606 | + |
| 607 | + // return early with these keys |
| 608 | + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || KEY.isVerticalMovement(e.which) ) { |
| 609 | + return; |
| 610 | + } |
| 611 | + // always reset the activeIndex to the first item when tagging |
| 612 | + ctrl.activeIndex = ctrl.taggingLabel === false ? -1 : 0; |
| 613 | + // taggingLabel === false bypasses all of this |
| 614 | + if (ctrl.taggingLabel === false) return; |
| 615 | + |
| 616 | + var items = angular.copy( ctrl.items ); |
| 617 | + var stashArr = angular.copy( ctrl.items ); |
| 618 | + var newItem; |
| 619 | + var item; |
| 620 | + var hasTag = false; |
| 621 | + var dupeIndex = -1; |
| 622 | + var tagItems; |
| 623 | + var tagItem; |
| 624 | + |
| 625 | + // case for object tagging via transform `ctrl.tagging.fct` function |
| 626 | + if ( ctrl.tagging.fct !== undefined) { |
| 627 | + tagItems = ctrl.$filter('filter')(items,{'isTag': true}); |
| 628 | + if ( tagItems.length > 0 ) { |
| 629 | + tagItem = tagItems[0]; |
| 630 | + } |
| 631 | + // remove the first element, if it has the `isTag` prop we generate a new one with each keyup, shaving the previous |
| 632 | + if ( items.length > 0 && tagItem ) { |
| 633 | + hasTag = true; |
| 634 | + items = items.slice(1,items.length); |
| 635 | + stashArr = stashArr.slice(1,stashArr.length); |
| 636 | + } |
| 637 | + newItem = ctrl.tagging.fct(ctrl.search); |
| 638 | + newItem.isTag = true; |
| 639 | + // verify the the tag doesn't match the value of an existing item |
| 640 | + if ( stashArr.filter( function (origItem) { return angular.equals( origItem, ctrl.tagging.fct(ctrl.search) ); } ).length > 0 ) { |
| 641 | + return; |
| 642 | + } |
| 643 | + // handle newItem string and stripping dupes in tagging string context |
| 644 | + } else { |
| 645 | + // find any tagging items already in the ctrl.items array and store them |
| 646 | + tagItems = ctrl.$filter('filter')(items,function (item) { |
| 647 | + return item.match(ctrl.taggingLabel); |
| 648 | + }); |
| 649 | + if ( tagItems.length > 0 ) { |
| 650 | + tagItem = tagItems[0]; |
| 651 | + } |
| 652 | + item = items[0]; |
| 653 | + // remove existing tag item if found (should only ever be one tag item) |
| 654 | + if ( item !== undefined && items.length > 0 && tagItem ) { |
| 655 | + hasTag = true; |
| 656 | + items = items.slice(1,items.length); |
| 657 | + stashArr = stashArr.slice(1,stashArr.length); |
| 658 | + } |
| 659 | + newItem = ctrl.search+' '+ctrl.taggingLabel; |
| 660 | + if ( _findApproxDupe(ctrl.selected, ctrl.search) > -1 ) { |
| 661 | + return; |
| 662 | + } |
| 663 | + // verify the the tag doesn't match the value of an existing item from |
| 664 | + // the searched data set |
| 665 | + if ( stashArr.filter( function (origItem) { return origItem.toUpperCase() === ctrl.search.toUpperCase(); }).length > 0 ) { |
| 666 | + // if there is a tag from prev iteration, strip it / queue the change |
| 667 | + // and return early |
| 668 | + if ( hasTag ) { |
| 669 | + items = stashArr; |
| 670 | + $scope.$evalAsync( function () { |
| 671 | + ctrl.activeIndex = 0; |
| 672 | + ctrl.items = items; |
| 673 | + }); |
| 674 | + } |
| 675 | + return; |
| 676 | + } |
| 677 | + if ( ctrl.selected.filter( function (selection) { return selection.toUpperCase() === ctrl.search.toUpperCase(); } ).length > 0 ) { |
| 678 | + // if there is a tag from prev iteration, strip it |
| 679 | + if ( hasTag ) { |
| 680 | + ctrl.items = stashArr.slice(1,stashArr.length); |
| 681 | + } |
| 682 | + return; |
| 683 | + } |
| 684 | + } |
| 685 | + if ( hasTag ) dupeIndex = _findApproxDupe(ctrl.selected, newItem); |
| 686 | + // dupe found, shave the first item |
| 687 | + if ( dupeIndex > -1 ) { |
| 688 | + items = items.slice(dupeIndex+1,items.length-1); |
| 689 | + } else { |
| 690 | + items = []; |
| 691 | + items.push(newItem); |
| 692 | + items = items.concat(stashArr); |
| 693 | + } |
| 694 | + $scope.$evalAsync( function () { |
| 695 | + ctrl.activeIndex = 0; |
| 696 | + ctrl.items = items; |
| 697 | + }); |
| 698 | + } |
| 699 | + }); |
| 700 | + |
573 | 701 | _searchInput.on('tagged', function() {
|
574 | 702 | $timeout(function() {
|
575 | 703 | _resetSearchInput();
|
|
582 | 710 | });
|
583 | 711 | });
|
584 | 712 |
|
| 713 | + function _findApproxDupe(haystack, needle) { |
| 714 | + var tempArr = angular.copy(haystack); |
| 715 | + var dupeIndex = -1; |
| 716 | + for (var i = 0; i <tempArr.length; i++) { |
| 717 | + // handle the simple string version of tagging |
| 718 | + if ( ctrl.tagging.fct === undefined ) { |
| 719 | + // search the array for the match |
| 720 | + if ( tempArr[i]+' '+ctrl.taggingLabel === needle ) { |
| 721 | + dupeIndex = i; |
| 722 | + } |
| 723 | + // handle the object tagging implementation |
| 724 | + } else { |
| 725 | + var mockObj = tempArr[i]; |
| 726 | + mockObj.isTag = true; |
| 727 | + if ( angular.equals(mockObj, needle) ) { |
| 728 | + dupeIndex = i; |
| 729 | + } |
| 730 | + } |
| 731 | + } |
| 732 | + return dupeIndex; |
| 733 | + } |
| 734 | + |
585 | 735 | function _getCaretPosition(el) {
|
586 | 736 | if(angular.isNumber(el.selectionStart)) return el.selectionStart;
|
587 | 737 | // selectionStart is not supported in IE8 and we don't want hacky workarounds so we compromise
|
|
611 | 761 | }
|
612 | 762 |
|
613 | 763 | $scope.$on('$destroy', function() {
|
614 |
| - _searchInput.off('keydown blur'); |
| 764 | + _searchInput.off('keyup keydown tagged blur'); |
615 | 765 | });
|
616 | 766 | }])
|
617 | 767 |
|
|
813 | 963 | }
|
814 | 964 | });
|
815 | 965 |
|
| 966 | + attrs.$observe('taggingLabel', function() { |
| 967 | + if(attrs.tagging !== undefined && attrs.taggingLabel !== undefined) |
| 968 | + { |
| 969 | + // check eval for FALSE, in this case, we disable the labels |
| 970 | + // associated with tagging |
| 971 | + if ( attrs.taggingLabel === 'false' ) { |
| 972 | + $select.taggingLabel = false; |
| 973 | + } else { |
| 974 | + $select.taggingLabel = attrs.taggingLabel !== undefined ? attrs.taggingLabel : '(new)'; |
| 975 | + } |
| 976 | + } |
| 977 | + }); |
| 978 | + |
816 | 979 | attrs.$observe('taggingTokens', function() {
|
817 | 980 | if(attrs.tagging !== undefined && attrs.taggingTokens !== undefined)
|
818 | 981 | {
|
|
0 commit comments