Skip to content

Commit 4304e88

Browse files
devversionThomasBurleson
authored andcommitted
feat(chips): add functionality to disable removing.
- By default - chips are writable **and** deletable - `readonly="true"`- chips are readonly and not deletable - `readonly="true" md-removable="true"` - chips are readonly, and deletable - `readonly="true" md-removable="false"` - chips are readonly, not deletable - `md-removable="false"`- chips are writable, not deletable Closes angular#5796. Fixes angular#3820. Closes angular#5799
1 parent 8758488 commit 4304e88

File tree

6 files changed

+173
-17
lines changed

6 files changed

+173
-17
lines changed

src/components/chips/chips.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,18 @@ $contact-chip-name-width: rem(12) !default;
6666

6767
&:not(.md-readonly) {
6868
cursor: text;
69+
}
70+
71+
&.md-removable {
6972

70-
md-chip:not(.md-readonly) {
73+
md-chip {
7174
@include rtl-prop(padding-right, padding-left, $chip-remove-padding-right);
7275

7376
._md-chip-content {
7477
@include rtl-prop(padding-right, padding-left, rem(0.4));
7578
}
7679
}
80+
7781
}
7882

7983
md-chip {

src/components/chips/chips.spec.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ describe('<md-chips>', function() {
1818
'<md-chips ng-model="items" readonly="true">' +
1919
' <md-autocomplete md-items="item in [\'hi\', \'ho\', \'he\']"></md-autocomplete>' +
2020
'</md-chips>';
21+
var CHIP_NOT_REMOVABLE_TEMPLATE =
22+
'<md-chips ng-model="items" readonly="true" md-removable="false"></md-chips>';
2123

2224
afterEach(function() {
2325
attachedElements.forEach(function(element) {
@@ -200,6 +202,106 @@ describe('<md-chips>', function() {
200202
expect(scope.selectChip).toHaveBeenCalled();
201203
expect(scope.selectChip.calls.mostRecent().args[0]).toBe('Grape');
202204
});
205+
206+
describe('when removable', function() {
207+
208+
it('should not append the input div when not removable and readonly is enabled', function() {
209+
var element = buildChips(CHIP_NOT_REMOVABLE_TEMPLATE);
210+
var wrap = element.children();
211+
var controller = element.controller("mdChips");
212+
213+
expect(wrap.hasClass("md-removable")).toBe(false);
214+
expect(controller.removable).toBe(false);
215+
216+
var containers = wrap[0].querySelectorAll(".md-chip-input-container");
217+
218+
expect(containers.length).toBe(0);
219+
220+
var removeContainer = wrap[0].querySelector('._md-chip-remove-container');
221+
expect(removeContainer).not.toBeTruthy();
222+
});
223+
224+
it('should not remove chip through the backspace/delete key when removable is set to false', inject(function($mdConstant) {
225+
var element = buildChips(CHIP_NOT_REMOVABLE_TEMPLATE);
226+
var wrap = element.find('md-chips-wrap');
227+
var controller = element.controller("mdChips");
228+
var chips = getChipElements(element);
229+
230+
expect(wrap.hasClass("md-removable")).toBe(false);
231+
expect(controller.removable).toBe(false);
232+
233+
controller.selectChip(0);
234+
235+
wrap.triggerHandler({
236+
type: 'keydown',
237+
keyCode: $mdConstant.KEY_CODE.BACKSPACE
238+
});
239+
240+
var updatedChips = getChipElements(element);
241+
242+
expect(chips.length).toBe(updatedChips.length);
243+
}));
244+
245+
it('should remove a chip by default through the backspace/delete key', inject(function($mdConstant) {
246+
var element = buildChips(BASIC_CHIP_TEMPLATE);
247+
var wrap = element.find('md-chips-wrap');
248+
var controller = element.controller("mdChips");
249+
var chips = getChipElements(element);
250+
251+
controller.selectChip(0);
252+
253+
wrap.triggerHandler({
254+
type: 'keydown',
255+
keyCode: $mdConstant.KEY_CODE.BACKSPACE
256+
});
257+
258+
var updatedChips = getChipElements(element);
259+
260+
expect(chips.length).not.toBe(updatedChips.length);
261+
}));
262+
263+
it('should set removable to true by default', function() {
264+
var element = buildChips(BASIC_CHIP_TEMPLATE);
265+
var wrap = element.children();
266+
var controller = element.controller('mdChips');
267+
268+
expect(wrap.hasClass('md-removable')).toBe(true);
269+
// The controller variable is kept undefined by default, to allow us to difference between the default value
270+
// and a user-provided value.
271+
expect(controller.removable).toBe(undefined);
272+
273+
var containers = wrap[0].querySelectorAll("._md-chip-input-container");
274+
expect(containers.length).not.toBe(0);
275+
276+
var removeContainer = wrap[0].querySelector('._md-chip-remove-container');
277+
expect(removeContainer).toBeTruthy();
278+
});
279+
280+
it('should append dynamically the remove button', function() {
281+
var template = '<md-chips ng-model="items" readonly="true" md-removable="removable"></md-chips>';
282+
283+
scope.removable = false;
284+
285+
var element = buildChips(template);
286+
var wrap = element.children();
287+
var controller = element.controller("mdChips");
288+
289+
expect(wrap.hasClass("md-removable")).toBe(false);
290+
expect(controller.removable).toBe(false);
291+
292+
var containers = wrap[0].querySelectorAll("._md-chip-remove-container");
293+
expect(containers.length).toBe(0);
294+
295+
scope.$apply('removable = true');
296+
297+
expect(wrap.hasClass("md-removable")).toBe(true);
298+
expect(controller.removable).toBe(true);
299+
300+
containers = wrap[0].querySelector("._md-chip-remove-container");
301+
expect(containers).toBeTruthy();
302+
});
303+
304+
});
203305

204306
describe('when readonly', function() {
205307
var element, ctrl;
@@ -248,6 +350,32 @@ describe('<md-chips>', function() {
248350

249351
expect($exceptionHandler.errors).toEqual([]);
250352
});
353+
354+
it('should disable removing when `md-removable` is not defined', function() {
355+
element = buildChips(
356+
'<md-chips ng-model="items" readonly="isReadonly" md-removable="isRemovable"></md-chips>'
357+
);
358+
359+
var wrap = element.find('md-chips-wrap');
360+
ctrl = element.controller('mdChips');
361+
362+
expect(element.find('md-chips-wrap')).not.toHaveClass('md-readonly');
363+
364+
scope.$apply('isReadonly = true');
365+
366+
expect(element.find('md-chips-wrap')).toHaveClass('md-readonly');
367+
368+
expect(ctrl.removable).toBeUndefined();
369+
370+
var removeContainer = wrap[0].querySelector('._md-chip-remove-container');
371+
expect(removeContainer).toBeFalsy();
372+
373+
scope.$apply('isRemovable = true');
374+
375+
removeContainer = wrap[0].querySelector('._md-chip-remove-container');
376+
expect(removeContainer).toBeTruthy();
377+
});
378+
251379
});
252380

253381
it('should disallow duplicate object chips', function() {

src/components/chips/demoBasicUsage/index.html

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
<h2 class="md-title">Use a custom chip template.</h2>
55

66
<form name="fruitForm">
7-
<md-chips ng-model="ctrl.roFruitNames" name="fruitName" readonly="ctrl.readonly" md-max-chips="5">
7+
<md-chips ng-model="ctrl.roFruitNames" name="fruitName" readonly="ctrl.readonly"
8+
md-removable="ctrl.removable" md-max-chips="5">
89
<md-chip-template>
910
<strong>{{$chip}}</strong>
1011
<em>(fruit)</em>
@@ -16,24 +17,26 @@ <h2 class="md-title">Use a custom chip template.</h2>
1617
</div>
1718
</form>
1819

19-
2020
<br/>
2121
<h2 class="md-title">Use the default chip template.</h2>
2222

23-
<md-chips ng-model="ctrl.fruitNames" readonly="ctrl.readonly"></md-chips>
23+
<md-chips ng-model="ctrl.fruitNames" readonly="ctrl.readonly" md-removable="ctrl.removable"></md-chips>
2424

2525

2626
<br/>
2727
<h2 class="md-title">Make chips editable.</h2>
2828

29-
<md-chips ng-model="ctrl.editableFruitNames" readonly="ctrl.readonly" md-enable-chip-edit="true"></md-chips>
29+
<md-chips ng-model="ctrl.editableFruitNames" readonly="ctrl.readonly" md-removable="ctrl.removable"
30+
md-enable-chip-edit="true"></md-chips>
3031

3132
<br/>
33+
3234
<h2 class="md-title">Use Placeholders and override hint texts.</h2>
3335

3436
<md-chips
3537
ng-model="ctrl.tags"
3638
readonly="ctrl.readonly"
39+
md-removable="ctrl.removable"
3740
placeholder="Enter a tag"
3841
delete-button-label="Remove Tag"
3942
delete-hint="Press delete to remove tag"
@@ -44,7 +47,7 @@ <h2 class="md-title">Display an ordered set of objects as chips (with custom tem
4447
<p>Note: the variables <code>$chip</code> and <code>$index</code> are available in custom chip templates.</p>
4548

4649
<md-chips class="custom-chips" ng-model="ctrl.vegObjs" readonly="ctrl.readonly"
47-
md-transform-chip="ctrl.newVeg($chip)">
50+
md-transform-chip="ctrl.newVeg($chip)" md-removable="ctrl.removable">
4851
<md-chip-template>
4952
<span>
5053
<strong>[{{$index}}] {{$chip.name}}</strong>
@@ -58,6 +61,12 @@ <h2 class="md-title">Display an ordered set of objects as chips (with custom tem
5861

5962
<br/>
6063
<md-checkbox ng-model="ctrl.readonly">Readonly</md-checkbox>
61-
64+
<md-checkbox ng-model="ctrl.removable">
65+
Removable
66+
<span ng-if="ctrl.removable === undefined">(Currently is <code>undefined</code>)</span>
67+
</md-checkbox>
68+
<p class="md-caption">
69+
<b>Note</b>: When md-removable is undefined, readonly automatically sets md-removable to false.
70+
</p>
6271
</md-content>
6372
</div>

src/components/chips/demoBasicUsage/style.scss

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,13 @@
4040
}
4141
}
4242
}
43-
&:not(.md-readonly) {
44-
md-chip-template {
45-
padding-right: 5px;
46-
}
43+
}
44+
45+
// Show custom padding for the custom delete button, which needs more space.
46+
md-chips-wrap.md-removable {
47+
md-chip md-chip-template {
48+
padding-right: 5px;
4749
}
4850
}
51+
4952
}

src/components/chips/js/chipsController.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ function MdChipsCtrl ($scope, $mdConstant, $log, $element, $timeout, $mdUtil) {
9696
* after selecting a chip from the list.
9797
* @type {boolean}
9898
*/
99-
this.useOnSelect = false;
10099
}
101100

102101
/**
@@ -179,11 +178,16 @@ MdChipsCtrl.prototype.updateChipContents = function(chipIndex, chipContents){
179178
* Returns true if a chip is currently being edited. False otherwise.
180179
* @return {boolean}
181180
*/
182-
MdChipsCtrl.prototype.isEditingChip = function(){
181+
MdChipsCtrl.prototype.isEditingChip = function() {
183182
return !!this.$element[0].getElementsByClassName('_md-chip-editing').length;
184183
};
185184

186185

186+
MdChipsCtrl.prototype.isRemovable = function() {
187+
return this.readonly ? this.removable :
188+
angular.isDefined(this.removable) ? this.removable : true;
189+
};
190+
187191
/**
188192
* Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
189193
* keys switch which chips is active
@@ -198,6 +202,8 @@ MdChipsCtrl.prototype.chipKeydown = function (event) {
198202
case this.$mdConstant.KEY_CODE.DELETE:
199203
if (this.selectedChip < 0) return;
200204
event.preventDefault();
205+
// Cancel the delete action only after the event cancel. Otherwise the page will go back.
206+
if (!this.isRemovable()) return;
201207
this.removeAndSelectAdjacentChip(this.selectedChip);
202208
break;
203209
case this.$mdConstant.KEY_CODE.LEFT_ARROW:

src/components/chips/js/chipsDirective.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,12 @@
8686
* @param {string=} placeholder Placeholder text that will be forwarded to the input.
8787
* @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
8888
* displayed when there is at least one item in the list
89+
* @param {boolean=} md-removable Enables or disables the deletion of chips through the
90+
* removal icon or the Delete/Backspace key. Defaults to true.
8991
* @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
9092
* the input and delete buttons. If no `ng-model` is provided, the chips will automatically be
91-
* marked as readonly.
93+
* marked as readonly.<br/><br/>
94+
* When `md-removable` is not defined, the `md-remove` behavior will be overwritten and disabled.
9295
* @param {string=} md-enable-chip-edit Set this to "true" to enable editing of chip contents. The user can
9396
* go into edit mode with pressing "space", "enter", or double clicking on the chip. Chip edit is only
9497
* supported for chips with basic template.
@@ -145,7 +148,9 @@
145148
var MD_CHIPS_TEMPLATE = '\
146149
<md-chips-wrap\
147150
ng-keydown="$mdChipsCtrl.chipKeydown($event)"\
148-
ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus(), \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly}"\
151+
ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus(), \
152+
\'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly,\
153+
\'md-removable\': $mdChipsCtrl.isRemovable() }"\
149154
class="md-chips">\
150155
<md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
151156
index="{{$index}}"\
@@ -156,7 +161,7 @@
156161
ng-click="!$mdChipsCtrl.readonly && $mdChipsCtrl.focusChip($index)"\
157162
ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)"\
158163
md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div>\
159-
<div ng-if="!$mdChipsCtrl.readonly"\
164+
<div ng-if="$mdChipsCtrl.isRemovable()"\
160165
class="_md-chip-remove-container"\
161166
md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\
162167
</md-chip>\
@@ -183,7 +188,7 @@
183188
var CHIP_REMOVE_TEMPLATE = '\
184189
<button\
185190
class="_md-chip-remove"\
186-
ng-if="!$mdChipsCtrl.readonly"\
191+
ng-if="$mdChipsCtrl.isRemovable()"\
187192
ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
188193
type="button"\
189194
aria-hidden="true"\
@@ -218,6 +223,7 @@
218223
compile: compile,
219224
scope: {
220225
readonly: '=readonly',
226+
removable: '=mdRemovable',
221227
placeholder: '@',
222228
mdEnableChipEdit: '@',
223229
secondaryPlaceholder: '@',

0 commit comments

Comments
 (0)