diff --git a/bower.json b/bower.json
index e2d222a4a..beb6f1047 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "ui-select",
- "version": "0.11.1",
+ "version": "0.11.2",
"homepage": "https://github.com/angular-ui/ui-select",
"authors": [
"AngularUI"
diff --git a/dist/select.css b/dist/select.css
index 26498e0f4..d397c9750 100644
--- a/dist/select.css
+++ b/dist/select.css
@@ -1,7 +1,7 @@
/*!
* ui-select
* http://github.com/angular-ui/ui-select
- * Version: 0.11.1 - 2015-03-09T14:30:26.112Z
+ * Version: 0.11.2 - 2015-03-17T04:08:46.478Z
* License: MIT
*/
@@ -44,6 +44,10 @@
display:none;
}
+body > .select2-container.open {
+ z-index: 9999; /* The z-index Select2 applies to the select2-drop */
+}
+
/* Selectize theme */
/* Helper class to show styles when focus */
@@ -104,9 +108,9 @@
border-bottom-right-radius: 0;
}
-.ui-select-bootstrap > .ui-select-match {
+.ui-select-bootstrap > .ui-select-match > .btn{
/* Instead of center because of .btn */
- text-align: left;
+ text-align: left !important;
}
.ui-select-bootstrap > .ui-select-match > .caret {
@@ -124,6 +128,10 @@
margin-top: -1px;
}
+body > .ui-select-bootstrap.open {
+ z-index: 1000; /* Standard Bootstrap dropdown z-index */
+}
+
.ui-select-multiple.ui-select-bootstrap {
height: auto;
padding: 3px 3px 0 3px;
diff --git a/dist/select.js b/dist/select.js
index 027e14489..aa0e5ae57 100644
--- a/dist/select.js
+++ b/dist/select.js
@@ -1,7 +1,7 @@
/*!
* ui-select
* http://github.com/angular-ui/ui-select
- * Version: 0.11.1 - 2015-03-09T14:30:26.109Z
+ * Version: 0.11.2 - 2015-03-17T04:08:46.474Z
* License: MIT
*/
@@ -105,7 +105,8 @@ var uis = angular.module('ui.select', [])
closeOnSelect: true,
generateId: function() {
return latestId++;
- }
+ },
+ appendToBody: false
})
// See Rename minErr and make it accessible from outside https://github.com/angular/angular.js/issues/6913
@@ -143,62 +144,31 @@ var uis = angular.module('ui.select', [])
return function(matchItem, query) {
return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem;
};
-});
-
+})
/**
- * Parses "repeat" attribute.
+ * A read-only equivalent of jQuery's offset function: http://api.jquery.com/offset/
*
- * Taken from AngularJS ngRepeat source code
- * See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L211
- *
- * Original discussion about parsing "repeat" attribute instead of fully relying on ng-repeat:
- * https://github.com/angular-ui/ui-select/commit/5dd63ad#commitcomment-5504697
+ * Taken from AngularUI Bootstrap Position:
+ * See https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js#L70
*/
+.factory('uisOffset',
+ ['$document', '$window',
+ function ($document, $window) {
-uis.service('RepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinErr, $parse) {
- var self = this;
-
- /**
- * Example:
- * expression = "address in addresses | filter: {street: $select.search} track by $index"
- * itemName = "address",
- * source = "addresses | filter: {street: $select.search}",
- * trackByExp = "$index",
- */
- self.parse = function(expression) {
-
- var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
-
- if (!match) {
- throw uiSelectMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
- expression);
- }
-
+ return function(element) {
+ var boundingClientRect = element[0].getBoundingClientRect();
return {
- itemName: match[2], // (lhs) Left-hand side,
- source: $parse(match[3]),
- trackByExp: match[4],
- modelMapper: $parse(match[1] || match[2])
+ width: boundingClientRect.width || element.prop('offsetWidth'),
+ height: boundingClientRect.height || element.prop('offsetHeight'),
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
};
-
- };
-
- self.getGroupNgRepeatExpression = function() {
- return '$group in $select.groups';
- };
-
- self.getNgRepeatExpression = function(itemName, source, trackByExp, grouped) {
- var expression = itemName + ' in ' + (grouped ? '$group.items' : source);
- if (trackByExp) {
- expression += ' track by ' + trackByExp;
- }
- return expression;
};
}]);
uis.directive('uiSelectChoices',
- ['uiSelectConfig', 'RepeatParser', 'uiSelectMinErr', '$compile',
+ ['uiSelectConfig', 'uisRepeatParser', 'uiSelectMinErr', '$compile',
function(uiSelectConfig, RepeatParser, uiSelectMinErr, $compile) {
return {
@@ -271,7 +241,7 @@ uis.directive('uiSelectChoices',
* put as much logic in the controller (instead of the link functions) as possible so it can be easily tested.
*/
uis.controller('uiSelectCtrl',
- ['$scope', '$element', '$timeout', '$filter', 'RepeatParser', 'uiSelectMinErr', 'uiSelectConfig',
+ ['$scope', '$element', '$timeout', '$filter', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig',
function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr, uiSelectConfig) {
var ctrl = this;
@@ -279,37 +249,41 @@ uis.controller('uiSelectCtrl',
var EMPTY_SEARCH = '';
ctrl.placeholder = uiSelectConfig.placeholder;
+ ctrl.searchEnabled = uiSelectConfig.searchEnabled;
+ ctrl.sortable = uiSelectConfig.sortable;
+ ctrl.refreshDelay = uiSelectConfig.refreshDelay;
+
+ ctrl.removeSelected = false; //If selected item(s) should be removed from dropdown list
+ ctrl.closeOnSelect = true; //Initialized inside uiSelect directive link function
ctrl.search = EMPTY_SEARCH;
- ctrl.activeIndex = 0;
- ctrl.activeMatchIndex = -1;
- ctrl.items = [];
- ctrl.selected = undefined;
+
+ ctrl.activeIndex = 0; //Dropdown of choices
+ ctrl.items = []; //All available choices
+
ctrl.open = false;
ctrl.focus = false;
- ctrl.focusser = undefined; //Reference to input element used to handle focus events
ctrl.disabled = false;
- ctrl.searchEnabled = uiSelectConfig.searchEnabled;
- ctrl.sortable = uiSelectConfig.sortable;
+ ctrl.selected = undefined;
+
+ ctrl.focusser = undefined; //Reference to input element used to handle focus events
ctrl.resetSearchInput = true;
ctrl.multiple = undefined; // Initialized inside uiSelect directive link function
- ctrl.refreshDelay = uiSelectConfig.refreshDelay;
ctrl.disableChoiceExpression = undefined; // Initialized inside uiSelectChoices directive link function
ctrl.tagging = {isActivated: false, fct: undefined};
ctrl.taggingTokens = {isActivated: false, tokens: undefined};
ctrl.lockChoiceExpression = undefined; // Initialized inside uiSelectMatch directive link function
- ctrl.closeOnSelect = true; // Initialized inside uiSelect directive link function
ctrl.clickTriggeredSelect = false;
ctrl.$filter = $filter;
+ ctrl.searchInput = $element.querySelectorAll('input.ui-select-search');
+ 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 === '';
};
- var _searchInput = $element.querySelectorAll('input.ui-select-search');
- if (_searchInput.length !== 1) {
- throw uiSelectMinErr('searchInput', "Expected 1 input.ui-select-search but got '{0}'.", _searchInput.length);
- }
-
// Most of the time the user does not want to empty the search input when in typeahead mode
function _resetSearchInput() {
if (ctrl.resetSearchInput || (ctrl.resetSearchInput === undefined && uiSelectConfig.resetSearchInput)) {
@@ -325,9 +299,10 @@ uis.controller('uiSelectCtrl',
ctrl.activate = function(initSearchValue, avoidReset) {
if (!ctrl.disabled && !ctrl.open) {
if(!avoidReset) _resetSearchInput();
- ctrl.focusser.prop('disabled', true); //Will reactivate it on .close()
+
+ $scope.$broadcast('uis:activate');
+
ctrl.open = true;
- ctrl.activeMatchIndex = -1;
ctrl.activeIndex = ctrl.activeIndex >= ctrl.items.length ? 0 : ctrl.activeIndex;
@@ -340,7 +315,7 @@ uis.controller('uiSelectCtrl',
// Give it time to appear before focus
$timeout(function() {
ctrl.search = initSearchValue || ctrl.search;
- _searchInput[0].focus();
+ ctrl.searchInput[0].focus();
});
}
};
@@ -375,16 +350,29 @@ uis.controller('uiSelectCtrl',
ctrl.items = items;
}
- var setItemsFn = groupByExp ? updateGroups : setPlainItems;
+ ctrl.setItemsFn = groupByExp ? updateGroups : setPlainItems;
ctrl.parserResult = RepeatParser.parse(repeatAttr);
ctrl.isGrouped = !!groupByExp;
ctrl.itemProperty = ctrl.parserResult.itemName;
+ ctrl.refreshItems = function (data){
+ data = data || ctrl.parserResult.source($scope);
+ var selectedItems = ctrl.selected;
+ //TODO should implement for single mode removeSelected
+ if ((angular.isArray(selectedItems) && !selectedItems.length) || !ctrl.removeSelected) {
+ ctrl.setItemsFn(data);
+ }else{
+ if ( data !== undefined ) {
+ var filteredItems = data.filter(function(i) {return selectedItems.indexOf(i) < 0;});
+ ctrl.setItemsFn(filteredItems);
+ }
+ }
+ };
+
// See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L259
$scope.$watchCollection(ctrl.parserResult.source, function(items) {
-
if (items === undefined || items === null) {
// If the user specifies undefined or null => reset the collection
// Special case: items can be undefined if the user did not initialized the collection on the scope
@@ -394,36 +382,14 @@ uis.controller('uiSelectCtrl',
if (!angular.isArray(items)) {
throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items);
} else {
- if (ctrl.multiple){
- //Remove already selected items (ex: while searching)
- var filteredItems = items.filter(function(i) {return ctrl.selected.indexOf(i) < 0;});
- setItemsFn(filteredItems);
- }else{
- setItemsFn(items);
- }
+ //Remove already selected items (ex: while searching)
+ //TODO Should add a test
+ ctrl.refreshItems(items);
ctrl.ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
-
}
}
-
});
- if (ctrl.multiple){
- //Remove already selected items
- $scope.$watchCollection('$select.selected', function(selectedItems){
- var data = ctrl.parserResult.source($scope);
- if (!selectedItems.length) {
- setItemsFn(data);
- }else{
- if ( data !== undefined ) {
- var filteredItems = data.filter(function(i) {return selectedItems.indexOf(i) < 0;});
- setItemsFn(filteredItems);
- }
- }
- ctrl.sizeSearchInput();
- });
- }
-
};
var _refreshDelayPromise;
@@ -534,16 +500,11 @@ uis.controller('uiSelectCtrl',
}
}
+ $scope.$broadcast('uis:select', item);
+
var locals = {};
locals[ctrl.parserResult.itemName] = item;
- if(ctrl.multiple) {
- ctrl.selected.push(item);
- ctrl.sizeSearchInput();
- } else {
- ctrl.selected = item;
- }
-
$timeout(function(){
ctrl.onSelectCallback($scope, {
$item: item,
@@ -551,7 +512,7 @@ uis.controller('uiSelectCtrl',
});
});
- if (!ctrl.multiple || ctrl.closeOnSelect) {
+ if (ctrl.closeOnSelect) {
ctrl.close(skipFocusser);
}
if ($event && $event.type === 'click') {
@@ -567,17 +528,13 @@ uis.controller('uiSelectCtrl',
if (ctrl.ngModel && ctrl.ngModel.$setTouched) ctrl.ngModel.$setTouched();
_resetSearchInput();
ctrl.open = false;
- if (!ctrl.multiple){
- $timeout(function(){
- ctrl.focusser.prop('disabled', false);
- if (!skipFocusser) ctrl.focusser[0].focus();
- },0,false);
- }
+
+ $scope.$broadcast('uis:close', skipFocusser);
+
};
ctrl.setFocus = function(){
- if (!ctrl.focus && !ctrl.multiple) ctrl.focusser[0].focus();
- if (!ctrl.focus && ctrl.multiple) _searchInput[0].focus();
+ if (!ctrl.focus) ctrl.focusInput[0].focus();
};
ctrl.clear = function($event) {
@@ -608,39 +565,11 @@ uis.controller('uiSelectCtrl',
return isLocked;
};
- // Remove item from multiple select
- ctrl.removeChoice = function(index){
- var removedChoice = ctrl.selected[index];
-
- // if the choice is locked, can't remove it
- if(removedChoice._uiSelectChoiceLocked) return;
-
- var locals = {};
- locals[ctrl.parserResult.itemName] = removedChoice;
-
- ctrl.selected.splice(index, 1);
- ctrl.activeMatchIndex = -1;
- ctrl.sizeSearchInput();
-
- // Give some time for scope propagation.
- $timeout(function(){
- ctrl.onRemoveCallback($scope, {
- $item: removedChoice,
- $model: ctrl.parserResult.modelMapper($scope, locals)
- });
- });
- };
-
- ctrl.getPlaceholder = function(){
- //Refactor single?
- if(ctrl.multiple && ctrl.selected.length) return;
- return ctrl.placeholder;
- };
-
var sizeWatch = null;
ctrl.sizeSearchInput = function() {
- var input = _searchInput[0],
- container = _searchInput.parent().parent()[0],
+
+ var input = ctrl.searchInput[0],
+ container = ctrl.searchInput.parent().parent()[0],
calculateContainerWidth = function() {
// Return the container width only if the search input is visible
return container.clientWidth * !!input.offsetParent;
@@ -651,11 +580,11 @@ uis.controller('uiSelectCtrl',
}
var inputWidth = containerWidth - input.offsetLeft - 10;
if (inputWidth < 50) inputWidth = containerWidth;
- _searchInput.css('width', inputWidth+'px');
+ ctrl.searchInput.css('width', inputWidth+'px');
return true;
};
- _searchInput.css('width', '10px');
+ ctrl.searchInput.css('width', '10px');
$timeout(function() { //Give tags time to render correctly
if (sizeWatch === null && !updateIfVisible(calculateContainerWidth())) {
sizeWatch = $scope.$watch(calculateContainerWidth, function(containerWidth) {
@@ -698,68 +627,8 @@ uis.controller('uiSelectCtrl',
return processed;
}
- // Handles selected options in "multiple" mode
- function _handleMatchSelection(key){
- var caretPosition = _getCaretPosition(_searchInput[0]),
- length = ctrl.selected.length,
- // none = -1,
- first = 0,
- last = length-1,
- curr = ctrl.activeMatchIndex,
- next = ctrl.activeMatchIndex+1,
- prev = ctrl.activeMatchIndex-1,
- newIndex = curr;
-
- if(caretPosition > 0 || (ctrl.search.length && key == KEY.RIGHT)) return false;
-
- ctrl.close();
-
- function getNewActiveMatchIndex(){
- switch(key){
- case KEY.LEFT:
- // Select previous/first item
- if(~ctrl.activeMatchIndex) return prev;
- // Select last item
- else return last;
- break;
- case KEY.RIGHT:
- // Open drop-down
- if(!~ctrl.activeMatchIndex || curr === last){
- ctrl.activate();
- return false;
- }
- // Select next/last item
- else return next;
- break;
- case KEY.BACKSPACE:
- // Remove selected item and select previous/first
- if(~ctrl.activeMatchIndex){
- ctrl.removeChoice(curr);
- return prev;
- }
- // Select last item
- else return last;
- break;
- case KEY.DELETE:
- // Remove selected item and select next item
- if(~ctrl.activeMatchIndex){
- ctrl.removeChoice(ctrl.activeMatchIndex);
- return curr;
- }
- else return false;
- }
- }
-
- newIndex = getNewActiveMatchIndex();
-
- if(!ctrl.selected.length || newIndex === false) ctrl.activeMatchIndex = -1;
- else ctrl.activeMatchIndex = Math.min(last,Math.max(first,newIndex));
-
- return true;
- }
-
// Bind to keyboard shortcuts
- _searchInput.on('keydown', function(e) {
+ ctrl.searchInput.on('keydown', function(e) {
var key = e.which;
@@ -769,15 +638,11 @@ uis.controller('uiSelectCtrl',
// }
$scope.$apply(function() {
- var processed = false;
- var tagged = false;
- if(ctrl.multiple && KEY.isHorizontalMovement(key)){
- processed = _handleMatchSelection(key);
- }
+ var tagged = false;
- if (!processed && (ctrl.items.length > 0 || ctrl.tagging.isActivated)) {
- processed = _handleDropDownSelection(key);
+ if (ctrl.items.length > 0 || ctrl.tagging.isActivated) {
+ _handleDropDownSelection(key);
if ( ctrl.taggingTokens.isActivated ) {
for (var i = 0; i < ctrl.taggingTokens.tokens.length; i++) {
if ( ctrl.taggingTokens.tokens[i] === KEY.MAP[e.keyCode] ) {
@@ -789,7 +654,7 @@ uis.controller('uiSelectCtrl',
}
if ( tagged ) {
$timeout(function() {
- _searchInput.triggerHandler('tagged');
+ ctrl.searchInput.triggerHandler('tagged');
var newItem = ctrl.search.replace(KEY.MAP[e.keyCode],'').trim();
if ( ctrl.tagging.fct ) {
newItem = ctrl.tagging.fct( newItem );
@@ -800,12 +665,6 @@ uis.controller('uiSelectCtrl',
}
}
- if (processed && key != KEY.TAB) {
- //TODO Check si el tab selecciona aun correctamente
- //Crear test
- e.preventDefault();
- e.stopPropagation();
- }
});
if(KEY.isVerticalMovement(key) && ctrl.items.length > 0){
@@ -815,7 +674,7 @@ uis.controller('uiSelectCtrl',
});
// If tagging try to split by tokens and add items
- _searchInput.on('paste', function (e) {
+ ctrl.searchInput.on('paste', function (e) {
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
@@ -832,166 +691,12 @@ uis.controller('uiSelectCtrl',
}
});
- _searchInput.on('keyup', function(e) {
- if ( ! KEY.isVerticalMovement(e.which) ) {
- $scope.$evalAsync( function () {
- ctrl.activeIndex = ctrl.taggingLabel === false ? -1 : 0;
- });
- }
- // Push a "create new" item into array if there is a search string
- if ( ctrl.tagging.isActivated && ctrl.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
- ctrl.activeIndex = ctrl.taggingLabel === false ? -1 : 0;
- // taggingLabel === false bypasses all of this
- if (ctrl.taggingLabel === false) return;
-
- var items = angular.copy( ctrl.items );
- var stashArr = angular.copy( ctrl.items );
- var newItem;
- var item;
- var hasTag = false;
- var dupeIndex = -1;
- var tagItems;
- var tagItem;
-
- // case for object tagging via transform `ctrl.tagging.fct` function
- if ( ctrl.tagging.fct !== undefined) {
- tagItems = ctrl.$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 = ctrl.tagging.fct(ctrl.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, ctrl.tagging.fct(ctrl.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 ctrl.items array and store them
- tagItems = ctrl.$filter('filter')(items,function (item) {
- return item.match(ctrl.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 = ctrl.search+' '+ctrl.taggingLabel;
- if ( _findApproxDupe(ctrl.selected, ctrl.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(ctrl.selected)) ) {
- // if there is a tag from prev iteration, strip it / queue the change
- // and return early
- if ( hasTag ) {
- items = stashArr;
- $scope.$evalAsync( function () {
- ctrl.activeIndex = 0;
- ctrl.items = items;
- });
- }
- return;
- }
- if ( _findCaseInsensitiveDupe(stashArr) ) {
- // if there is a tag from prev iteration, strip it
- if ( hasTag ) {
- ctrl.items = stashArr.slice(1,stashArr.length);
- }
- return;
- }
- }
- if ( hasTag ) dupeIndex = _findApproxDupe(ctrl.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 () {
- ctrl.activeIndex = 0;
- ctrl.items = items;
- });
- }
- });
-
- _searchInput.on('tagged', function() {
+ ctrl.searchInput.on('tagged', function() {
$timeout(function() {
_resetSearchInput();
});
});
- _searchInput.on('blur', function() {
- $timeout(function() {
- ctrl.activeMatchIndex = -1;
- });
- });
-
- function _findCaseInsensitiveDupe(arr) {
- if ( arr === undefined || ctrl.search === undefined ) {
- return false;
- }
- var hasDupe = arr.filter( function (origItem) {
- if ( ctrl.search.toUpperCase() === undefined || origItem === undefined ) {
- return false;
- }
- return origItem.toUpperCase() === ctrl.search.toUpperCase();
- }).length > 0;
-
- return hasDupe;
- }
-
- function _findApproxDupe(haystack, needle) {
- var dupeIndex = -1;
- if(angular.isArray(haystack)) {
- var tempArr = angular.copy(haystack);
- for (var i = 0; i