Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 03caf58

Browse files
devversionThomasBurleson
authored andcommitted
feat(chips): md-max-chips to specify a maximum of chips that can be added through user input
Current UX provides no indication of when max chips has been reached. * use ngMessages to show information at max limit condition. Fixes #6864. Closes #6897.
1 parent bb5c105 commit 03caf58

File tree

6 files changed

+132
-8
lines changed

6 files changed

+132
-8
lines changed

src/components/chips/chips.spec.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,71 @@ describe('<md-chips>', function() {
409409
}));
410410
});
411411

412+
describe('md-max-chips', function() {
413+
414+
beforeEach(function() {
415+
// Clear default items to test the max chips functionality
416+
scope.items = [];
417+
});
418+
419+
it('should not add a new chip if the max-chips limit is reached', function() {
420+
var element = buildChips('<md-chips ng-model="items" md-max-chips="1"></md-chips>');
421+
var ctrl = element.controller('mdChips');
422+
423+
element.scope().$apply(function() {
424+
ctrl.chipBuffer = 'Test';
425+
simulateInputEnterKey(ctrl);
426+
});
427+
428+
expect(scope.items.length).toBe(1);
429+
430+
element.scope().$apply(function() {
431+
ctrl.chipBuffer = 'Test 2';
432+
simulateInputEnterKey(ctrl);
433+
});
434+
435+
expect(scope.items.length).not.toBe(2);
436+
});
437+
438+
it('should update the md-max-chips model validator for forms', function() {
439+
var template =
440+
'<form name="form">' +
441+
'<md-chips name="chips" ng-model="items" md-max-chips="1"></md-chips>' +
442+
'</form>';
443+
444+
var element = buildChips(template);
445+
var ctrl = element.find('md-chips').controller('mdChips');
446+
447+
element.scope().$apply(function() {
448+
ctrl.chipBuffer = 'Test';
449+
simulateInputEnterKey(ctrl);
450+
});
451+
452+
expect(scope.form.chips.$error['md-max-chips']).toBe(true);
453+
});
454+
455+
it('should not reset the buffer if the maximum is reached', function() {
456+
var element = buildChips('<md-chips ng-model="items" md-max-chips="1"></md-chips>');
457+
var ctrl = element.controller('mdChips');
458+
459+
element.scope().$apply(function() {
460+
ctrl.chipBuffer = 'Test';
461+
simulateInputEnterKey(ctrl);
462+
});
463+
464+
expect(scope.items.length).toBe(1);
465+
466+
element.scope().$apply(function() {
467+
ctrl.chipBuffer = 'Test 2';
468+
simulateInputEnterKey(ctrl);
469+
});
470+
471+
expect(ctrl.chipBuffer).toBe('Test 2');
472+
expect(scope.items.length).not.toBe(2);
473+
});
474+
475+
});
476+
412477
describe('md-autocomplete', function() {
413478
var AUTOCOMPLETE_CHIPS_TEMPLATE = '\
414479
<md-chips ng-model="items">\

src/components/chips/demoBasicUsage/index.html

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@
33
<md-content class="md-padding" layout="column">
44
<h2 class="md-title">Use a custom chip template.</h2>
55

6-
<md-chips ng-model="ctrl.roFruitNames" readonly="ctrl.readonly">
7-
<md-chip-template>
8-
<strong>{{$chip}}</strong>
9-
<em>(fruit)</em>
10-
</md-chip-template>
11-
</md-chips>
6+
<form name="fruitForm">
7+
<md-chips ng-model="ctrl.roFruitNames" name="fruitName" readonly="ctrl.readonly" md-max-chips="5">
8+
<md-chip-template>
9+
<strong>{{$chip}}</strong>
10+
<em>(fruit)</em>
11+
</md-chip-template>
12+
</md-chips>
13+
14+
<div class="errors" ng-messages="fruitForm.fruitName.$error">
15+
<div ng-message="md-max-chips">The maxmium of chips is reached.</div>
16+
</div>
17+
</form>
18+
1219

1320
<br/>
1421
<h2 class="md-title">Use the default chip template.</h2>

src/components/chips/demoBasicUsage/script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(function () {
22
'use strict';
33
angular
4-
.module('chipsDemo', ['ngMaterial'])
4+
.module('chipsDemo', ['ngMaterial', 'ngMessages'])
55
.controller('BasicDemoCtrl', DemoCtrl);
66

77
function DemoCtrl ($timeout, $q) {

src/components/chips/demoBasicUsage/style.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
.errors {
2+
font-size: 12px;
3+
color: rgb(221,44,0);
4+
margin-top: 10px;
5+
}
6+
17
.custom-chips {
28
.md-chip {
39
position: relative;

src/components/chips/js/chipsController.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ MdChipsCtrl.prototype.inputKeydown = function(event) {
137137
if (this.separatorKeys.indexOf(event.keyCode) !== -1) {
138138
if ((this.hasAutocomplete && this.requireMatch) || !chipBuffer) return;
139139
event.preventDefault();
140+
141+
// Only append the chip and reset the chip buffer if the max chips limit isn't reached.
142+
if (this.items.length >= this.maxChips) return;
143+
140144
this.appendChip(chipBuffer);
141145
this.resetChipBuffer();
142146
}
@@ -242,7 +246,7 @@ MdChipsCtrl.prototype.appendChip = function(newChip) {
242246
var identical = this.items.some(function(item){
243247
return angular.equals(newChip, item);
244248
});
245-
if(identical) return;
249+
if (identical) return;
246250
}
247251

248252
// Check for a null (but not undefined), or existing chip and cancel appending
@@ -251,6 +255,10 @@ MdChipsCtrl.prototype.appendChip = function(newChip) {
251255
// Append the new chip onto our list
252256
var index = this.items.push(newChip);
253257

258+
// Update model validation
259+
this.ngModelCtrl.$setDirty();
260+
this.validateModel();
261+
254262
// If they provide the md-on-add attribute, notify them of the chip addition
255263
if (this.useOnAdd && this.onAdd) {
256264
this.onAdd({ '$chip': newChip, '$index': index });
@@ -350,13 +358,30 @@ MdChipsCtrl.prototype.resetChipBuffer = function() {
350358
}
351359
};
352360

361+
MdChipsCtrl.prototype.hasMaxChips = function() {
362+
if (angular.isString(this.maxChips)) this.maxChips = parseInt(this.maxChips, 10) || 0;
363+
364+
return this.maxChips > 0 && this.items.length >= this.maxChips;
365+
};
366+
367+
/**
368+
* Updates the validity properties for the ngModel.
369+
*/
370+
MdChipsCtrl.prototype.validateModel = function() {
371+
this.ngModelCtrl.$setValidity('md-max-chips', !this.hasMaxChips());
372+
};
373+
353374
/**
354375
* Removes the chip at the given index.
355376
* @param index
356377
*/
357378
MdChipsCtrl.prototype.removeChip = function(index) {
358379
var removed = this.items.splice(index, 1);
359380

381+
// Update model validation
382+
this.ngModelCtrl.$setDirty();
383+
this.validateModel();
384+
360385
if (removed && removed.length && this.useOnRemove && this.onRemove) {
361386
this.onRemove({ '$chip': removed[0], '$index': index });
362387
}

src/components/chips/js/chipsDirective.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
* displayed when there is at least on item in the list
6868
* @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
6969
* the input and delete buttons
70+
* @param {number=} md-max-chips The maximum number of chips allowed to add through user input.
71+
* <br/><br/>The validation property `md-max-chips` can be used when the max chips
72+
* amount is reached.
7073
* @param {expression} md-transform-chip An expression of form `myFunction($chip)` that when called
7174
* expects one of the following return values:
7275
* - an object representing the `$chip` input string
@@ -94,6 +97,23 @@
9497
* </md-chips>
9598
* </hljs>
9699
*
100+
* <h3>Validation</h3>
101+
* When using [ngMessages](https://docs.angularjs.org/api/ngMessages), you can show errors based
102+
* on our custom validators.
103+
* <hljs lang="html">
104+
* <form name="userForm">
105+
* <md-chips
106+
* name="fruits"
107+
* ng-model="myItems"
108+
* placeholder="Add an item"
109+
* md-max-chips="5">
110+
* </md-chips>
111+
* <div ng-messages="userForm.fruits.$error" ng-if="userForm.$dirty">
112+
* <div ng-message="md-max-chips">You reached the maximum amount of chips</div>
113+
* </div>
114+
* </form>
115+
* </hljs>
116+
*
97117
*/
98118

99119

@@ -175,6 +195,7 @@
175195
readonly: '=readonly',
176196
placeholder: '@',
177197
secondaryPlaceholder: '@',
198+
maxChips: '@mdMaxChips',
178199
transformChip: '&mdTransformChip',
179200
onAppend: '&mdOnAppend',
180201
onAdd: '&mdOnAdd',

0 commit comments

Comments
 (0)