Skip to content

Commit 72254eb

Browse files
committed
feat(templates): Support disabled options in select menu.
1 parent fc42fdf commit 72254eb

File tree

6 files changed

+199
-15
lines changed

6 files changed

+199
-15
lines changed

Diff for: src/bootstrap/choices.tpl.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<li class="ui-select-choices-group">
55
<div class="divider" ng-show="$select.isGrouped && $index > 0"></div>
66
<div ng-show="$select.isGrouped" class="ui-select-choices-group-label dropdown-header">{{$group.name}}</div>
7-
<div class="ui-select-choices-row" ng-class="{active: $select.isActive(this)}">
7+
<div class="ui-select-choices-row" ng-class="{active: $select.isActive(this), disabled: $select.isDisabled(this)}">
88
<a href="javascript:void(0)" class="ui-select-choices-row-inner"></a>
99
</div>
1010
</li>

Diff for: src/select.css

+7
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@
136136
background-color: #428bca;
137137
}
138138

139+
.ui-select-bootstrap .ui-select-choices-row.disabled>a,
140+
.ui-select-bootstrap .ui-select-choices-row.active.disabled>a {
141+
color: #777;
142+
cursor: not-allowed;
143+
background-color: #fff;
144+
}
145+
139146
/* fix hide/show angular animation */
140147
.ui-select-match.ng-hide-add,
141148
.ui-select-search.ng-hide-add {

Diff for: src/select.js

+31-12
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156
ctrl.resetSearchInput = undefined; // Initialized inside uiSelect directive link function
157157
ctrl.refreshDelay = undefined; // Initialized inside uiSelectChoices directive link function
158158
ctrl.multiple = false; // Initialized inside uiSelect directive link function
159+
ctrl.disableChoiceExpression = undefined; // Initialized inside uiSelect directive link function
159160

160161
ctrl.isEmpty = function() {
161162
return angular.isUndefined(ctrl.selected) || ctrl.selected === null || ctrl.selected === '';
@@ -299,24 +300,40 @@
299300
return ctrl.items.indexOf(itemScope[ctrl.itemProperty]) === ctrl.activeIndex;
300301
};
301302

303+
ctrl.isDisabled = function(itemScope) {
304+
var itemIndex = ctrl.items.indexOf(itemScope[ctrl.itemProperty]);
305+
var isDisabled = false;
306+
var item;
307+
308+
if (itemIndex >= 0 && !angular.isUndefined(ctrl.disableChoiceExpression)) {
309+
item = ctrl.items[itemIndex];
310+
isDisabled = !!(itemScope.$eval(ctrl.disableChoiceExpression)); // force the boolean value
311+
item._uiSelectChoiceDisabled = isDisabled; // store this for later reference
312+
}
313+
314+
return isDisabled;
315+
};
316+
302317
// When the user clicks on an item inside the dropdown
303318
ctrl.select = function(item) {
304319

305-
var locals = {};
306-
locals[ctrl.parserResult.itemName] = item;
320+
if (!item._uiSelectChoiceDisabled) {
321+
var locals = {};
322+
locals[ctrl.parserResult.itemName] = item;
307323

308-
ctrl.onSelectCallback($scope, {
309-
$item: item,
310-
$model: ctrl.parserResult.modelMapper($scope, locals)
311-
});
324+
ctrl.onSelectCallback($scope, {
325+
$item: item,
326+
$model: ctrl.parserResult.modelMapper($scope, locals)
327+
});
312328

313-
if(ctrl.multiple){
314-
ctrl.selected.push(item);
315-
ctrl.sizeSearchInput();
316-
} else {
317-
ctrl.selected = item;
329+
if(ctrl.multiple){
330+
ctrl.selected.push(item);
331+
ctrl.sizeSearchInput();
332+
} else {
333+
ctrl.selected = item;
334+
}
335+
ctrl.close();
318336
}
319-
ctrl.close();
320337
};
321338

322339
// Closes the dropdown
@@ -818,6 +835,8 @@
818835

819836
$select.parseRepeatAttr(attrs.repeat, groupByExp); //Result ready at $select.parserResult
820837

838+
$select.disableChoiceExpression = attrs.uiDisableChoice;
839+
821840
if(groupByExp) {
822841
var groups = element.querySelectorAll('.ui-select-choices-group');
823842
if (groups.length !== 1) throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-group but got '{0}'.", groups.length);

Diff for: src/select2/choices.tpl.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<li class="ui-select-choices-group" ng-class="{'select2-result-with-children': $select.isGrouped}">
33
<div ng-show="$select.isGrouped" class="ui-select-choices-group-label select2-result-label">{{$group.name}}</div>
44
<ul ng-class="{'select2-result-sub': $select.isGrouped, 'select2-result-single': !$select.isGrouped}">
5-
<li class="ui-select-choices-row" ng-class="{'select2-highlighted': $select.isActive(this)}">
5+
<li class="ui-select-choices-row" ng-class="{'select2-highlighted': $select.isActive(this), 'select2-disabled': $select.isDisabled(this)}">
66
<div class="select2-result-label ui-select-choices-row-inner"></div>
77
</li>
88
</ul>

Diff for: src/selectize/choices.tpl.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div class="ui-select-choices-content selectize-dropdown-content">
33
<div class="ui-select-choices-group optgroup">
44
<div ng-show="$select.isGrouped" class="ui-select-choices-group-label optgroup-header">{{$group.name}}</div>
5-
<div class="ui-select-choices-row" ng-class="{active: $select.isActive(this)}">
5+
<div class="ui-select-choices-row" ng-class="{active: $select.isActive(this), disabled: $select.isDisabled(this)}">
66
<div class="option ui-select-choices-row-inner" data-selectable></div>
77
</div>
88
</div>

Diff for: test/select.spec.js

+158
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,164 @@ describe('ui-select tests', function() {
266266
expect(getMatchLabel(el)).toEqual('false');
267267
});
268268

269+
describe('disabled options', function() {
270+
function createUiSelect(attrs) {
271+
var attrsDisabled = '';
272+
if (attrs !== undefined) {
273+
if (attrs.disabled !== undefined) {
274+
attrsDisabled = ' ui-disable-choice="' + attrs.disabled + '"';
275+
} else {
276+
attrsDisabled = '';
277+
}
278+
}
279+
280+
return compileTemplate(
281+
'<ui-select ng-model="selection.selected"> \
282+
<ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \
283+
<ui-select-choices repeat="person in people | filter: $select.search"' + attrsDisabled + '> \
284+
<div ng-bind-html="person.name | highlight: $select.search"></div> \
285+
<div ng-bind-html="person.email | highlight: $select.search"></div> \
286+
</ui-select-choices> \
287+
</ui-select>'
288+
);
289+
}
290+
291+
function disablePerson(opts) {
292+
opts = opts || {};
293+
294+
var key = opts.key || 'people',
295+
disableAttr = opts.disableAttr || 'disabled',
296+
disableBool = opts.disableBool === undefined ? true : opts.disableBool,
297+
matchAttr = opts.match || 'name',
298+
matchVal = opts.matchVal || 'Wladimir';
299+
300+
scope['_' + key] = angular.copy(scope[key]);
301+
scope[key].map(function (model) {
302+
if (model[matchAttr] == matchVal) {
303+
model[disableAttr] = disableBool;
304+
}
305+
return model;
306+
});
307+
}
308+
309+
function resetScope(opts) {
310+
opts = opts || {};
311+
var key = opts.key || 'people';
312+
scope[key] = angular.copy(scope['_' + key]);
313+
}
314+
315+
describe('without disabling expression', function () {
316+
beforeEach(function() {
317+
disablePerson();
318+
this.el = createUiSelect();
319+
});
320+
321+
it('should not allow disabled options to be selected', function() {
322+
clickItem(this.el, 'Wladimir');
323+
324+
expect(getMatchLabel(this.el)).toEqual('Wladimir');
325+
});
326+
327+
it('should set a disabled class on the option', function() {
328+
var option = $(this.el).find('.ui-select-choices-row div:contains("Wladimir")');
329+
var container = option.closest('.ui-select-choices-row');
330+
331+
expect(container.hasClass('disabled')).toBeFalsy();
332+
});
333+
});
334+
335+
describe('disable on truthy property', function () {
336+
beforeEach(function() {
337+
disablePerson({
338+
disableAttr : 'inactive',
339+
disableBool : true,
340+
});
341+
this.el = createUiSelect({
342+
disabled: 'person.inactive'
343+
});
344+
});
345+
346+
it('should allow the user to define the selected option', function () {
347+
expect($(this.el).find('.ui-select-choices').attr('ui-disable-choice')).toBe('person.inactive');
348+
});
349+
350+
it('should not allow disabled options to be selected', function() {
351+
clickItem(this.el, 'Wladimir');
352+
353+
expect(getMatchLabel(this.el)).not.toEqual('Wladimir');
354+
});
355+
356+
it('should set a disabled class on the option', function() {
357+
var option = $(this.el).find('.ui-select-choices-row div:contains("Wladimir")');
358+
var container = option.closest('.ui-select-choices-row');
359+
360+
expect(container.hasClass('disabled')).toBeTruthy();
361+
});
362+
});
363+
364+
describe('disable on inverse property check', function () {
365+
beforeEach(function() {
366+
disablePerson({
367+
disableAttr : 'active',
368+
disableBool : false,
369+
});
370+
this.el = createUiSelect({
371+
disabled: '!person.active'
372+
});
373+
});
374+
375+
it('should allow the user to define the selected option', function () {
376+
expect($(this.el).find('.ui-select-choices').attr('ui-disable-choice')).toBe('!person.active');
377+
});
378+
379+
it('should not allow disabled options to be selected', function() {
380+
clickItem(this.el, 'Wladimir');
381+
382+
expect(getMatchLabel(this.el)).not.toEqual('Wladimir');
383+
});
384+
385+
it('should set a disabled class on the option', function() {
386+
var option = $(this.el).find('.ui-select-choices-row div:contains("Wladimir")');
387+
var container = option.closest('.ui-select-choices-row');
388+
389+
expect(container.hasClass('disabled')).toBeTruthy();
390+
});
391+
});
392+
393+
describe('disable on expression', function () {
394+
beforeEach(function() {
395+
disablePerson({
396+
disableAttr : 'status',
397+
disableBool : 'inactive'
398+
});
399+
this.el = createUiSelect({
400+
disabled: "person.status == 'inactive'"
401+
});
402+
});
403+
404+
it('should allow the user to define the selected option', function () {
405+
expect($(this.el).find('.ui-select-choices').attr('ui-disable-choice')).toBe("person.status == 'inactive'");
406+
});
407+
408+
it('should not allow disabled options to be selected', function() {
409+
clickItem(this.el, 'Wladimir');
410+
411+
expect(getMatchLabel(this.el)).not.toEqual('Wladimir');
412+
});
413+
414+
it('should set a disabled class on the option', function() {
415+
var option = $(this.el).find('.ui-select-choices-row div:contains("Wladimir")');
416+
var container = option.closest('.ui-select-choices-row');
417+
418+
expect(container.hasClass('disabled')).toBeTruthy();
419+
});
420+
});
421+
422+
afterEach(function() {
423+
resetScope();
424+
});
425+
});
426+
269427
describe('choices group', function() {
270428
function getGroupLabel(item) {
271429
return item.parent('.ui-select-choices-group').find('.ui-select-choices-group-label');

0 commit comments

Comments
 (0)