Skip to content

Commit

Permalink
feat(chips): ability to disable chip removal (input) (#615)
Browse files Browse the repository at this point in the history
* added chipRemoval feature

* updated chip removal label

* added chipRemoval to code preview in demo

* added chipRemoval to code preview in demo

* fixed linting

* fixed linting

* added unit tests for chip removal, updated readme and remove chipAttrs from app demo
  • Loading branch information
JoshSchoen authored and emoralesb05 committed May 31, 2017
1 parent 363bf24 commit 51ba94d
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 37 deletions.
9 changes: 9 additions & 0 deletions src/app/components/components/chips/chips.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<div class="push">
<div class="md-body-1">Type and select a preset option or press enter:</div>
<td-chips [chipAddition]="chipAddition"
[chipRemoval]="chipRemoval"
[items]="filteredStrings"
[(ngModel)]="stringsModel"
placeholder="Enter autocomplete strings"
Expand All @@ -24,6 +25,7 @@
<td-highlight lang="html">
<![CDATA[
<td-chips [chipAddition]="chipAddition"
[chipRemoval]="chipRemoval"
[items]="filteredStrings"
[(ngModel)]="stringsModel"
placeholder="Enter autocomplete strings"
Expand All @@ -39,6 +41,7 @@
export class ChipsDemoComponent {
readOnly: boolean = false;
chipAddition: boolean = true;
chipRemoval: boolean = true;

strings: string[] = [
'stepper',
Expand Down Expand Up @@ -92,6 +95,12 @@
Chip addition
</md-slide-toggle>
</div>
<md-divider></md-divider>
<div class="pad-sm">
<md-slide-toggle color="accent" [(ngModel)]="chipRemoval">
Chip removal
</md-slide-toggle>
</div>
</md-card-actions>
</md-card>
<md-card>
Expand Down
34 changes: 1 addition & 33 deletions src/app/components/components/chips/chips.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,9 @@ export class ChipsDemoComponent implements OnInit {
@HostBinding('@routeAnimation') routeAnimation: boolean = true;
@HostBinding('class.td-route-animation') classAnimation: boolean = true;

chipsAttrs: Object[] = [{
description: `Enables Autocompletion with the provided list of strings.`,
name: 'items?',
type: 'string[]',
}, {
description: `Disables the chip input and removal.`,
name: 'readOnly?',
type: 'boolean',
}, {
description: `Validates input against the provided search list before adding it to the model.
If it doesnt exist, it cancels the event.`,
name: 'requireMatch?',
type: 'boolean',
}, {
description: `Placeholder for the autocomplete input.`,
name: 'placeholder?',
type: 'string',
}, {
description: `Method to be executed when string is added as chip through the autocomplete.
Sends chip value as event.`,
name: 'add?',
type: 'function',
}, {
description: `Method to be executed when string is removed as chip with the "remove" button.
Sends chip value as event.`,
name: 'remove?',
type: 'function',
}, {
description: `Disables the ability to add chips. If it doesn't exist chipAddition defaults to true.`,
name: 'chipAddition?',
type: 'boolean',
}];

readOnly: boolean = false;
chipAddition: boolean = true;
chipRemoval: boolean = true;

filteringAsync: boolean = false;

Expand Down
2 changes: 2 additions & 0 deletions src/platform/core/chips/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Properties:
| `stacked?` | `boolean` | Set stacked or horizontal chips depending on value. Defaults to false.
| `placeholder?` | `string` | Placeholder for the autocomplete input.
| `chipAddition` | `boolean` | Disables the ability to add chips. When setting readOnly as true, this will be overriden. Defaults to true.
| `chipRemoval` | `boolean` | Disables the ability to remove chips. If it doesn't exist chipRemoval defaults to true. readyOnly must be false for this option to work.
| `debounce` | `string` | Debounce timeout between keypresses. Defaults to 200.
| `add?` | `function` | Method to be executed when a chip is added. Sends chip value as event.
| `remove?` | `function` | Method to be executed when a chip is removed. Sends chip value as event.
Expand Down Expand Up @@ -48,6 +49,7 @@ Example for HTML usage:
[(ngModel)]="model"
[readOnly]="readOnly"
[chipAddition]="chipAddition"
[chipRemoval]="chipRemoval"
(add)="addEvent($event)"
(remove)="removeEvent($event)"
(inputChange)="inputChange($event)"
Expand Down
4 changes: 2 additions & 2 deletions src/platform/core/chips/chips.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="td-chips-wrapper" [class.td-chips-stacked]="stacked">
<ng-template let-chip let-first="first" let-index="index" ngFor [ngForOf]="value">
<md-basic-chip [class.td-chip-disabled]="readOnly"
<md-basic-chip [class.td-chip-disabled]="readOnly || !chipRemoval"
[color]="color"
(keydown)="_chipKeydown($event, index)"
(focus)="setFocusedState()">
Expand All @@ -13,7 +13,7 @@
[ngOutletContext]="{ chip: chip }">
</ng-template>
</div>
<md-icon *ngIf="!readOnly" class="td-chip-removal" (click)="_internalClick = removeChip(index)">
<md-icon *ngIf="!readOnly && chipRemoval" class="td-chip-removal" (click)="_internalClick = removeChip(index)">
cancel
</md-icon>
</div>
Expand Down
215 changes: 214 additions & 1 deletion src/platform/core/chips/chips.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Component, DebugElement } from '@angular/core';
import {
FormControl, FormsModule, ReactiveFormsModule,
} from '@angular/forms';
import { OverlayContainer, MdInputDirective, MdChip, BACKSPACE, ENTER, LEFT_ARROW, RIGHT_ARROW } from '@angular/material';
import { OverlayContainer, MdInputDirective, MdChip, DELETE, BACKSPACE, ENTER, LEFT_ARROW, RIGHT_ARROW } from '@angular/material';
import { By } from '@angular/platform-browser';
import { CovalentChipsModule, TdChipsComponent } from './chips.module';

Expand Down Expand Up @@ -41,6 +41,7 @@ describe('Component: Chips', () => {
TdChipsBasicTestComponent,
TdChipsObjectsRequireMatchTestComponent,
TdChipsStackedTestComponent,
TdChipRemovalTestComponent,
],
providers: [
{provide: OverlayContainer, useFactory: () => {
Expand Down Expand Up @@ -470,6 +471,197 @@ describe('Component: Chips', () => {

});

describe('chip removal usage, requires readOnly to be false: ', () => {
let fixture: ComponentFixture<TdChipRemovalTestComponent>;
let input: DebugElement;
let chips: DebugElement;

beforeEach(() => {
fixture = TestBed.createComponent(TdChipRemovalTestComponent);
fixture.detectChanges();

chips = fixture.debugElement.query(By.directive(TdChipsComponent));
input = chips.query(By.css('input'));
});

it('should not focus input', (done: DoneFn) => {
fixture.componentInstance.chipRemoval = true;
fixture.componentInstance.chipAddition = false;
fixture.detectChanges();
fixture.whenStable().then(() => {
chips.nativeElement.focus();
chips.triggerEventHandler('focus', new Event('focus'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect((<TdChipsComponent>chips.componentInstance)._inputChild.focused).toBeFalsy();
done();
});
});
});

it('should not show cancel button', (done: DoneFn) => {
fixture.componentInstance.chipRemoval = false;
fixture.componentInstance.chipAddition = false;
fixture.detectChanges();
fixture.whenStable().then(() => {
chips.nativeElement.focus();
chips.triggerEventHandler('focus', new Event('focus'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.css('.td-chip-removal')).length).toBe(0);
done();
});
});
});

it('should focus input, then focus first chip and remove first chip by clicking on the remove button', (done: DoneFn) => {
fixture.componentInstance.chipRemoval = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
chips.nativeElement.focus();
chips.triggerEventHandler('focus', new Event('focus'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect((<TdChipsComponent>chips.componentInstance)._inputChild.focused).toBeTruthy();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(chips.componentInstance.value.length).toBe(3);
expect(fixture.debugElement.queryAll(By.directive(MdChip)).length).toBe(3);
fixture.debugElement.queryAll(By.css('.td-chip-removal'))[0]
.triggerEventHandler('click', new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(chips.componentInstance.value.length).toBe(2);
expect(fixture.debugElement.queryAll(By.directive(MdChip)).length).toBe(2);
done();
});
});
});
});
});

it('should focus first chip and remove chip with backspace and delete', (done: DoneFn) => {
fixture.componentInstance.chipRemoval = true;
fixture.componentInstance.chipAddition = false;
fixture.detectChanges();
fixture.whenStable().then(() => {
chips.nativeElement.focus();
fixture.detectChanges();
fixture.whenStable().then(() => {
chips.triggerEventHandler('focus', new Event('focus'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement)
.toBe(document.activeElement);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.debugElement.queryAll(By.directive(MdChip))[0]
.triggerEventHandler('keydown', createFakeKeyboardEvent(BACKSPACE));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(chips.componentInstance.value.length).toBe(2);
expect(fixture.debugElement.queryAll(By.directive(MdChip)).length).toBe(2);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(chips.componentInstance.value.length).toBe(2);
fixture.debugElement.queryAll(By.directive(MdChip))[0]
.triggerEventHandler('keydown', createFakeKeyboardEvent(DELETE));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(chips.componentInstance.value.length).toBe(1);
expect(fixture.debugElement.queryAll(By.directive(MdChip)).length).toBe(1);
done();
});
});
});
});
});
});
});
});

it('should focus around the chips going left', (done: DoneFn) => {
fixture.componentInstance.chipRemoval = true;
fixture.componentInstance.chipAddition = false;
fixture.detectChanges();
fixture.whenStable().then(() => {
chips.nativeElement.focus();
fixture.detectChanges();
fixture.whenStable().then(() => {
chips.triggerEventHandler('focus', new Event('focus'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement)
.toBe(document.activeElement);
fixture.debugElement.queryAll(By.directive(MdChip))[0]
.triggerEventHandler('keydown', createFakeKeyboardEvent(LEFT_ARROW));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(MdChip))[2].nativeElement)
.toBe(document.activeElement);
fixture.debugElement.queryAll(By.directive(MdChip))[2]
.triggerEventHandler('keydown', createFakeKeyboardEvent(LEFT_ARROW));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(MdChip))[1].nativeElement)
.toBe(document.activeElement);
fixture.debugElement.queryAll(By.directive(MdChip))[1]
.triggerEventHandler('keydown', createFakeKeyboardEvent(LEFT_ARROW));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement)
.toBe(document.activeElement);
done();
});
});
});
});
});
});
});

it('should focus around the chips going right', (done: DoneFn) => {
fixture.componentInstance.chipRemoval = true;
fixture.componentInstance.chipAddition = false;
fixture.detectChanges();
fixture.whenStable().then(() => {
chips.nativeElement.focus();
fixture.detectChanges();
fixture.whenStable().then(() => {
chips.triggerEventHandler('focus', new Event('focus'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement)
.toBe(document.activeElement);
fixture.debugElement.queryAll(By.directive(MdChip))[0]
.triggerEventHandler('keydown', createFakeKeyboardEvent(RIGHT_ARROW));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(MdChip))[1].nativeElement)
.toBe(document.activeElement);
fixture.debugElement.queryAll(By.directive(MdChip))[1]
.triggerEventHandler('keydown', createFakeKeyboardEvent(RIGHT_ARROW));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(MdChip))[2].nativeElement)
.toBe(document.activeElement);
fixture.debugElement.queryAll(By.directive(MdChip))[2]
.triggerEventHandler('keydown', createFakeKeyboardEvent(RIGHT_ARROW));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement)
.toBe(document.activeElement);
done();
});
});
});
});
});
});
});

});

// TODO

// more requireMatch usage
Expand Down Expand Up @@ -583,3 +775,24 @@ class TdChipsStackedTestComponent {
];
selectedItems: string[] = this.items.slice(0, 3);
}

@Component({
template: `
<td-chips [items]="items" [(ngModel)]="selectedItems" [chipRemoval]="chipRemoval"
[chipAddition]="chipAddition">
</td-chips>`,
})
class TdChipRemovalTestComponent {
chipRemoval: boolean = true;
chipAddition: boolean = true;
items: string[] = [
'steak',
'pizza',
'tacos',
'sandwich',
'chips',
'pasta',
'sushi',
];
selectedItems: string[] = this.items.slice(0, 3);
}
22 changes: 21 additions & 1 deletion src/platform/core/chips/chips.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class TdChipsComponent implements ControlValueAccessor, DoCheck, OnInit,
private _readOnly: boolean = false;
private _color: 'primary' | 'accent' | 'warn' = 'primary';
private _chipAddition: boolean = true;
private _chipRemoval: boolean = true;
private _focused: boolean = false;
private _tabIndex: number = 0;

Expand Down Expand Up @@ -166,6 +167,19 @@ export class TdChipsComponent implements ControlValueAccessor, DoCheck, OnInit,
return this.chipAddition && !this.readOnly;
}

/**
* chipRemoval?: boolean
* Disables the ability to remove chips. If it doesn't exist chip remmoval defaults to true.
* When setting readOnly as true, this will be overriden to false.
*/
@Input('chipRemoval')
set chipRemoval(chipRemoval: boolean) {
this._chipRemoval = chipRemoval;
}
get chipRemoval(): boolean {
return this._chipRemoval;
}

/**
* placeholder?: string
* Placeholder for the autocomplete input.
Expand Down Expand Up @@ -520,7 +534,13 @@ export class TdChipsComponent implements ControlValueAccessor, DoCheck, OnInit,
case BACKSPACE:
/** Check to see if not in [readOnly] state to delete a chip */
if (!this.readOnly) {
this.removeChip(index);
/**
* Checks [chipRemoval] state to delete a chips
* To enable [chipRemoval] the [readOnly] state must be true.
*/
if (this.chipRemoval) {
this.removeChip(index);
}
}
break;
case UP_ARROW:
Expand Down

0 comments on commit 51ba94d

Please sign in to comment.