diff --git a/.gitignore b/.gitignore index ecf32cc69..81cfebba5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /.idea /.tmp .DS_Store +public diff --git a/dist/select.css b/dist/select.css index 4cce226ba..3eeca494b 100644 --- a/dist/select.css +++ b/dist/select.css @@ -1,7 +1,7 @@ /*! * ui-select * http://github.com/angular-ui/ui-select - * Version: 0.13.2 - 2015-10-09T15:34:24.045Z + * Version: 0.13.2 - 2016-01-31T11:42:51.520Z * License: MIT */ diff --git a/dist/select.js b/dist/select.js index 6349bae30..e5f37c0d1 100644 --- a/dist/select.js +++ b/dist/select.js @@ -1,12 +1,12 @@ /*! * ui-select * http://github.com/angular-ui/ui-select - * Version: 0.13.2 - 2015-10-09T15:34:24.040Z + * Version: 0.13.2 - 2016-01-31T11:42:51.513Z * License: MIT */ -(function () { +(function () { "use strict"; var KEY = { @@ -143,7 +143,7 @@ var uis = angular.module('ui.select', []) } return function(matchItem, query) { - return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + return query && matchItem ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; }; }) @@ -284,7 +284,7 @@ uis.controller('uiSelectCtrl', if (ctrl.searchInput.length !== 1) { throw uiSelectMinErr('searchInput', "Expected 1 input.ui-select-search but got '{0}'.", ctrl.searchInput.length); } - + ctrl.isEmpty = function() { return angular.isUndefined(ctrl.selected) || ctrl.selected === null || ctrl.selected === ''; }; @@ -388,7 +388,7 @@ uis.controller('uiSelectCtrl', //If collection is an Object, convert it to Array var originalSource = ctrl.parserResult.source; - + //When an object is used as source, we better create an array and use it as 'source' var createArrayFromObject = function(){ var origSrc = originalSource($scope); @@ -434,7 +434,7 @@ uis.controller('uiSelectCtrl', ctrl.items = []; } else { if (!angular.isArray(items)) { - throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items); + throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items); } else { //Remove already selected items (ex: while searching) //TODO Should add a test @@ -459,12 +459,17 @@ uis.controller('uiSelectCtrl', // Debounce // See https://github.com/angular-ui/bootstrap/blob/0.10.0/src/typeahead/typeahead.js#L155 // FYI AngularStrap typeahead does not have debouncing: https://github.com/mgcrea/angular-strap/blob/v2.0.0-rc.4/src/typeahead/typeahead.js#L177 - if (_refreshDelayPromise) { - $timeout.cancel(_refreshDelayPromise); + if (ctrl.refreshDelay > 0) { + if (_refreshDelayPromise) { + $timeout.cancel(_refreshDelayPromise); + } + _refreshDelayPromise = $timeout(function () { + $scope.$eval(refreshAttr); + }, ctrl.refreshDelay); } - _refreshDelayPromise = $timeout(function() { + else { $scope.$eval(refreshAttr); - }, ctrl.refreshDelay); + } } }; @@ -544,7 +549,12 @@ uis.controller('uiSelectCtrl', } } // search ctrl.selected for dupes potentially caused by tagging and return early if found - if ( ctrl.selected && angular.isArray(ctrl.selected) && ctrl.selected.filter( function (selection) { return angular.equals(selection, item); }).length > 0 ) { + if (ctrl.selected && angular.isArray(ctrl.selected) && ctrl.selected.filter(function (selection) { + return ctrl.isNewTagDuplication !== angular.noop ? ctrl.isNewTagDuplication($scope, { + newItem: item, + existingItem: angular.copy(selection) + }) : angular.equals(angular.copy(selection), item); + }).length > 0) { ctrl.close(skipFocusser); return; } @@ -565,6 +575,9 @@ uis.controller('uiSelectCtrl', if (ctrl.closeOnSelect) { ctrl.close(skipFocusser); } + else { + _resetSearchInput(); + } if ($event && $event.type === 'click') { ctrl.clickTriggeredSelect = true; } @@ -735,6 +748,7 @@ uis.controller('uiSelectCtrl', var data = e.originalEvent.clipboardData.getData('text/plain'); if (data && data.length > 0 && ctrl.taggingTokens.isActivated && ctrl.tagging.fct) { var items = data.split(ctrl.taggingTokens.tokens[0]); // split by first token only + items = items.filter(Boolean); if (items && items.length > 0) { angular.forEach(items, function (item) { var newItem = ctrl.tagging.fct(item); @@ -756,6 +770,9 @@ uis.controller('uiSelectCtrl', // See https://github.com/ivaynberg/select2/blob/3.4.6/select2.js#L1431 function _ensureHighlightVisible() { + if (!ctrl.open) { + return; + } var container = $element.querySelectorAll('.ui-select-choices-content'); var choices = container.querySelectorAll('.ui-select-choices-row'); if (choices.length < 1) { @@ -834,6 +851,7 @@ uis.directive('uiSelect', $select.onSelectCallback = $parse(attrs.onSelect); $select.onRemoveCallback = $parse(attrs.onRemove); + $select.isNewTagDuplication = $parse(attrs.isNewTagDuplication); //Limit the number of selections allowed $select.limit = (angular.isDefined(attrs.limit)) ? parseInt(attrs.limit, 10) : undefined; @@ -1041,6 +1059,8 @@ uis.directive('uiSelect', element[0].style.left = ''; element[0].style.top = ''; element[0].style.width = originalWidth; + + $select.setFocus(); } // Hold on to a reference to the .ui-select-dropdown element for direction support. @@ -1088,7 +1108,7 @@ uis.directive('uiSelect', } // Hide the dropdown so there is no flicker until $timeout is done executing. - dropdown[0].style.opacity = 0; + //dropdown[0].style.opacity = 0; // Delay positioning the dropdown until all choices have been added so its height is correct. $timeout(function(){ @@ -1181,7 +1201,7 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec $select = $scope.$select, ngModel; - //Wait for link fn to inject it + //Wait for link fn to inject it $scope.$evalAsync(function(){ ngModel = $scope.ngModel; }); ctrl.activeMatchIndex = -1; @@ -1193,7 +1213,7 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec ctrl.refreshComponent = function(){ //Remove already selected items - //e.g. When user clicks on a selection, the selected array changes and + //e.g. When user clicks on a selection, the selected array changes and //the dropdown should remove that item $select.refreshItems(); $select.sizeSearchInput(); @@ -1241,6 +1261,7 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec var $select = ctrls[0]; var ngModel = scope.ngModel = ctrls[1]; var $selectMultiple = scope.$selectMultiple; + var _refreshDelayKeyUpPromise; //$select.selected = raw selected objects (ignoring any property binding) @@ -1292,7 +1313,7 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec }; if (!inputValue) return resultMultiple; //If ngModel was undefined for (var k = inputValue.length - 1; k >= 0; k--) { - //Check model array of currently selected items + //Check model array of currently selected items if (!checkFnMultiple($select.selected, inputValue[k])){ //Check model array of all items available if (!checkFnMultiple(data, inputValue[k])){ @@ -1303,8 +1324,8 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec } return resultMultiple; }); - - //Watch for external model changes + + //Watch for external model changes scope.$watchCollection(function(){ return ngModel.$modelValue; }, function(newValue, oldValue) { if (oldValue != newValue){ ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters @@ -1425,107 +1446,121 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec } $select.searchInput.on('keyup', function(e) { - - if ( ! KEY.isVerticalMovement(e.which) ) { - scope.$evalAsync( function () { - $select.activeIndex = $select.taggingLabel === false ? -1 : 0; - }); - } - // Push a "create new" item into array if there is a search string - if ( $select.tagging.isActivated && $select.search.length > 0 ) { - - // return early with these keys - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || KEY.isVerticalMovement(e.which) ) { - return; - } - // always reset the activeIndex to the first item when tagging - $select.activeIndex = $select.taggingLabel === false ? -1 : 0; - // taggingLabel === false bypasses all of this - if ($select.taggingLabel === false) return; - - var items = angular.copy( $select.items ); - var stashArr = angular.copy( $select.items ); - var newItem; - var item; - var hasTag = false; - var dupeIndex = -1; - var tagItems; - var tagItem; - - // case for object tagging via transform `$select.tagging.fct` function - if ( $select.tagging.fct !== undefined) { - tagItems = $select.$filter('filter')(items,{'isTag': true}); - if ( tagItems.length > 0 ) { - tagItem = tagItems[0]; - } - // remove the first element, if it has the `isTag` prop we generate a new one with each keyup, shaving the previous - if ( items.length > 0 && tagItem ) { - hasTag = true; - items = items.slice(1,items.length); - stashArr = stashArr.slice(1,stashArr.length); - } - newItem = $select.tagging.fct($select.search); - newItem.isTag = true; - // verify the the tag doesn't match the value of an existing item - if ( stashArr.filter( function (origItem) { return angular.equals( origItem, $select.tagging.fct($select.search) ); } ).length > 0 ) { - return; - } - newItem.isTag = true; - // handle newItem string and stripping dupes in tagging string context - } else { - // find any tagging items already in the $select.items array and store them - tagItems = $select.$filter('filter')(items,function (item) { - return item.match($select.taggingLabel); + function doKeyUp () { + if ( ! KEY.isVerticalMovement(e.which) ) { + scope.$evalAsync( function () { + $select.activeIndex = $select.taggingLabel === false ? -1 : 0; }); - if ( tagItems.length > 0 ) { - tagItem = tagItems[0]; - } - item = items[0]; - // remove existing tag item if found (should only ever be one tag item) - if ( item !== undefined && items.length > 0 && tagItem ) { - hasTag = true; - items = items.slice(1,items.length); - stashArr = stashArr.slice(1,stashArr.length); - } - newItem = $select.search+' '+$select.taggingLabel; - if ( _findApproxDupe($select.selected, $select.search) > -1 ) { + } + // Push a "create new" item into array if there is a search string + if ( $select.tagging.isActivated && $select.search.length > 0 ) { + + // return early with these keys + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || KEY.isVerticalMovement(e.which) ) { return; } - // verify the the tag doesn't match the value of an existing item from - // the searched data set or the items already selected - if ( _findCaseInsensitiveDupe(stashArr.concat($select.selected)) ) { - // if there is a tag from prev iteration, strip it / queue the change - // and return early - if ( hasTag ) { - items = stashArr; - scope.$evalAsync( function () { - $select.activeIndex = 0; - $select.items = items; - }); + // always reset the activeIndex to the first item when tagging + $select.activeIndex = $select.taggingLabel === false ? -1 : 0; + // taggingLabel === false bypasses all of this + if ($select.taggingLabel === false) return; + + var items = angular.copy( $select.items ); + var stashArr = angular.copy( $select.items ); + var newItem; + var item; + var hasTag = false; + var dupeIndex = -1; + var tagItems; + var tagItem; + + // case for object tagging via transform `$select.tagging.fct` function + if ( $select.tagging.fct !== undefined) { + tagItems = $select.$filter('filter')(items,{'isTag': true}); + if ( tagItems.length > 0 ) { + tagItem = tagItems[0]; } - return; - } - if ( _findCaseInsensitiveDupe(stashArr) ) { - // if there is a tag from prev iteration, strip it - if ( hasTag ) { - $select.items = stashArr.slice(1,stashArr.length); + // remove the first element, if it has the `isTag` prop we generate a new one with each keyup, shaving the previous + if ( items.length > 0 && tagItem ) { + hasTag = true; + items = items.slice(1,items.length); + stashArr = stashArr.slice(1,stashArr.length); + } + newItem = $select.tagging.fct($select.search); + newItem.isTag = true; + // verify the the tag doesn't match the value of an existing item + if ( stashArr.filter( function (origItem) { return angular.equals( origItem, $select.tagging.fct($select.search) ); } ).length > 0 ) { + $select.items = stashArr.filter( function (origItem) { return !origItem.isTag; } ); + return; + } + newItem.isTag = true; + // handle newItem string and stripping dupes in tagging string context + } else { + // find any tagging items already in the $select.items array and store them + tagItems = $select.$filter('filter')(items,function (item) { + return item.match($select.taggingLabel); + }); + if ( tagItems.length > 0 ) { + tagItem = tagItems[0]; + } + item = items[0]; + // remove existing tag item if found (should only ever be one tag item) + if ( item !== undefined && items.length > 0 && tagItem ) { + hasTag = true; + items = items.slice(1,items.length); + stashArr = stashArr.slice(1,stashArr.length); + } + newItem = $select.search+' '+$select.taggingLabel; + if ( _findApproxDupe($select.selected, $select.search) > -1 ) { + return; + } + // verify the the tag doesn't match the value of an existing item from + // the searched data set or the items already selected + if ( _findCaseInsensitiveDupe(stashArr.concat($select.selected)) ) { + // if there is a tag from prev iteration, strip it / queue the change + // and return early + if ( hasTag ) { + items = stashArr; + scope.$evalAsync( function () { + $select.activeIndex = 0; + $select.items = items; + }); + } + return; + } + if ( _findCaseInsensitiveDupe(stashArr) ) { + // if there is a tag from prev iteration, strip it + if ( hasTag ) { + $select.items = stashArr.slice(1,stashArr.length); + } + return; } - return; } + if ( hasTag ) dupeIndex = _findApproxDupe($select.selected, newItem); + // dupe found, shave the first item + if ( dupeIndex > -1 ) { + items = items.slice(dupeIndex+1,items.length-1); + } else { + items = []; + items.push(newItem); + items = items.concat(stashArr); + } + scope.$evalAsync( function () { + $select.activeIndex = 0; + $select.items = items; + }); } - if ( hasTag ) dupeIndex = _findApproxDupe($select.selected, newItem); - // dupe found, shave the first item - if ( dupeIndex > -1 ) { - items = items.slice(dupeIndex+1,items.length-1); - } else { - items = []; - items.push(newItem); - items = items.concat(stashArr); + } + + if ($select.refreshDelay > 0) { + if (_refreshDelayKeyUpPromise) { + $timeout.cancel(_refreshDelayKeyUpPromise); } - scope.$evalAsync( function () { - $select.activeIndex = 0; - $select.items = items; - }); + _refreshDelayKeyUpPromise = $timeout(function () { + doKeyUp(); + }, $select.refreshDelay); + } + else { + doKeyUp(); } }); function _findCaseInsensitiveDupe(arr) { @@ -1865,7 +1900,7 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE // if (isObjectCollection){ //00000000000000000000000000000111111111000000000000000222222222222220033333333333333333333330000444444444444444444000000000000000556666660000077777777777755000000000000000000000088888880000000 - match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(([\w\.]+)?\s*(|\s*[\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); + match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(([\w\.]+)?\s*(|\s*[\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); // 1 Alias // 2 Item @@ -1899,7 +1934,7 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE expression += ' track by ' + this.trackByExp; } return expression; - } + } }; }; @@ -1911,16 +1946,16 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE }]); }()); -angular.module("ui.select").run(["$templateCache", function($templateCache) {$templateCache.put("bootstrap/choices.tpl.html","