Skip to content
This repository was archived by the owner on Oct 2, 2019. It is now read-only.

fix(uiSelectMultiple): ensure first item can selected #1713

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 75 additions & 68 deletions src/uiSelectController.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,73 +369,88 @@ uis.controller('uiSelectCtrl',


// When the user selects an item with ENTER or clicks the dropdown
ctrl.select = function(item, skipFocusser, $event) {
if (item === undefined || !_isItemDisabled(item)) {

if ( ! ctrl.items && ! ctrl.search && ! ctrl.tagging.isActivated) return;

if (!item || !_isItemDisabled(item)) {
if(ctrl.tagging.isActivated) {
// if taggingLabel is disabled and item is undefined we pull from ctrl.search
if ( ctrl.taggingLabel === false ) {
if ( ctrl.activeIndex < 0 ) {
if (item === undefined) {
item = ctrl.tagging.fct !== undefined ? ctrl.tagging.fct(ctrl.search) : ctrl.search;
}
if (!item || angular.equals( ctrl.items[0], item ) ) {
return;
}
} else {
// keyboard nav happened first, user selected from dropdown
item = ctrl.items[ctrl.activeIndex];
}
} else {
// tagging always operates at index zero, taggingLabel === false pushes
// the ctrl.search value without having it injected
if ( ctrl.activeIndex === 0 ) {
// ctrl.tagging pushes items to ctrl.items, so we only have empty val
// for `item` if it is a detected duplicate
if ( item === undefined ) return;

// create new item on the fly if we don't already have one;
// use tagging function if we have one
if ( ctrl.tagging.fct !== undefined && typeof item === 'string' ) {
item = ctrl.tagging.fct(item);
if (!item) return;
// if item type is 'string', apply the tagging label
} else if ( typeof item === 'string' ) {
// trim the trailing space
item = item.replace(ctrl.taggingLabel,'').trim();
}
}
}
// search ctrl.selected for dupes potentially caused by tagging and return early if found
if (_isItemSelected(item)) {
ctrl.close(skipFocusser);
return;
}
}

$scope.$broadcast('uis:select', item);
ctrl.select = function (item, skipFocusser, $event) {

var locals = {};
locals[ctrl.parserResult.itemName] = item;
if (angular.isDefined(item) && _isItemDisabled(item)) return;

$timeout(function(){
ctrl.onSelectCallback($scope, {
$item: item,
$model: ctrl.parserResult.modelMapper($scope, locals)
});
});
if (!ctrl.items && !ctrl.search && !ctrl.tagging.isActivated) return;

if (ctrl.closeOnSelect) {
ctrl.close(skipFocusser);
var dummyTaggingFunc = function(i) { return i; };
var taggingFunc = angular.isDefined(ctrl.tagging.fct) ? ctrl.tagging.fct
: dummyTaggingFunc;

var minActiveIndex = ctrl.taggingLabel === false ? -1 : 0;

// The ctrl.activeIndex might not be set (eg. if the user has clicked on an entry)
// check to see if the item exists, if so use its index as the active index
var itemIndex = minActiveIndex;
if(ctrl.activeIndex === minActiveIndex) {
var idx = ctrl.items.indexOf(item);
if(idx > -1) {
itemIndex = idx;
}
}

if(ctrl.tagging.isActivated && itemIndex === minActiveIndex) {

if(ctrl.taggingLabel !== false) {
// If item is undefined it'll remain undefined
// and be ignore later
item = _getCleanedTag(item);
} else {
if(angular.isUndefined(item)) {
item = taggingFunc(ctrl.search);
}
}
if ($event && $event.type === 'click') {
ctrl.clickTriggeredSelect = true;

// tagging function aborted selection
// or tagged item was a duplicate
if(!item) return;
}

// search ctrl.selected for dupes potentially caused by tagging and return early if found
if (_isItemSelected(item)) {
ctrl.close(skipFocusser);
return;
}

function _getCleanedTag(tagItem) {

if(angular.isString(tagItem)) {
if(taggingFunc === dummyTaggingFunc) {
// if there isn't a tagging function ensure tagging
// label is removed from value
tagItem = tagItem.replace(ctrl.taggingLabel, '').trim();
} else {
tagItem = ctrl.tagging.fct(tagItem);
}
}

return tagItem;
}

// if we don't have an item by now something has gone wrong
if (!item) return;

$scope.$broadcast('uis:select', item);

var locals = {};
locals[ctrl.parserResult.itemName] = item;

$timeout(function(){
ctrl.onSelectCallback($scope, {
$item: item,
$model: ctrl.parserResult.modelMapper($scope, locals)
});
});

if (ctrl.closeOnSelect) {
ctrl.close(skipFocusser);
}
if ($event && $event.type === 'click') {
ctrl.clickTriggeredSelect = true;
}

};

// Closes the dropdown
Expand All @@ -453,14 +468,6 @@ uis.controller('uiSelectCtrl',
if (!ctrl.focus) ctrl.focusInput[0].focus();
};

ctrl.clear = function($event) {
ctrl.select(undefined);
$event.stopPropagation();
$timeout(function() {
ctrl.focusser[0].focus();
}, 0, false);
};

// Toggle dropdown
ctrl.toggle = function(e) {
if (ctrl.open) {
Expand Down
8 changes: 8 additions & 0 deletions src/uiSelectSingleDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ uis.directive('uiSelectSingle', ['$timeout','$compile', function($timeout, $comp
focusser.prop('disabled', true); //Will reactivate it on .close()
});

$select.clear = function($event) {
$select.selected = undefined;
$event.stopPropagation();
$timeout(function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you save this $timeout to a variable so that onDestroy it can be cancelled?

$select.focusser[0].focus();
}, 0, false);
};

//Idea from: https://github.com/ivaynberg/select2/blob/79b5bf6db918d7560bdd959109b7bcfb47edaf43/select2.js#L1954
var focusser = angular.element("<input ng-disabled='$select.disabled' class='ui-select-focusser ui-select-offscreen' type='text' id='{{ $select.focusserId }}' aria-label='{{ $select.focusserTitle }}' aria-haspopup='true' role='button' />");
$compile(focusser)(scope);
Expand Down
43 changes: 42 additions & 1 deletion test/select.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ describe('ui-select tests', function() {
});
};

scope.simple = {
colors: ['Red', 'Green', 'Blue', 'Yellow', 'Magenta', 'Maroon', 'Umbra', 'Turquoise'],
selected: ['Red']
};

scope.people = [
{ name: 'Adam', email: '[email protected]', group: 'Foo', age: 12 },
Expand Down Expand Up @@ -620,7 +624,7 @@ describe('ui-select tests', function() {

expect($(el).scope().$select.selected).not.toBeDefined();
});

it('should allow tagging if the attribute says so', function() {
var el = createUiSelect({tagging: true});
clickMatch(el);
Expand Down Expand Up @@ -1402,6 +1406,43 @@ describe('ui-select tests', function() {
expect($(el).scope().$select.selected).toEqual('idontexist');
});

it('should allow tagging with simple string and no label if attribute says so', function () {

var el = compileTemplate(
'<ui-select multiple tagging tagging-label="false" ng-model="simple.selected"> \
<ui-select-match placeholder="Pick one...">{{$select.selected}}</ui-select-match> \
<ui-select-choices repeat="color in simple.color | filter: $select.search"> \
<div ng-bind-html="color | highlight: $select.search"></div> \
</ui-select-choices> \
</ui-select>'
);

clickMatch(el);

$(el).scope().$select.select("I don't exist");

expect($(el).scope().$select.selected).toEqual(["Red", "I don't exist"]);
expect(scope.simple.selected).toEqual(['Red', "I don't exist"])
});

it('should not prevent selecting the first item when tagging with simple string and no label', function () {

var el = compileTemplate(
'<ui-select multiple tagging tagging-label="false" ng-model="simple.selected"> \
<ui-select-match placeholder="Pick one...">{{$item}}</ui-select-match> \
<ui-select-choices repeat="color in simple.colors | filter: $select.search"> \
<div ng-bind-html="color | highlight: $select.search"></div> \
</ui-select-choices> \
</ui-select>'
);

// ensure test source array has not changed
expect(scope.simple.colors[1]).toBe('Green');
clickItem(el, 'Green');

expect(scope.simple.selected).toEqual(['Red', 'Green']);
});

it('should allow creating tag on ENTER in multiple select mode with tagging enabled, no labels', function() {

scope.taggingFunc = function (name) {
Expand Down