diff --git a/e2e/list-view-grid.e2e-spec.ts b/e2e/list-view-grid.e2e-spec.ts index 3d36539..08aa052 100644 --- a/e2e/list-view-grid.e2e-spec.ts +++ b/e2e/list-view-grid.e2e-spec.ts @@ -9,9 +9,13 @@ import { } from '@skyux-sdk/e2e'; describe('List view grid', () => { - it('should match the previous screenshot', (done) => { - SkyHostBrowser.get('/visual/list-view-grid'); + + beforeEach(() => { + SkyHostBrowser.get('visual/list-view-grid'); SkyHostBrowser.setWindowBreakpoint('lg'); + }); + + it('should match the previous screenshot', (done) => { SkyHostBrowser.scrollTo('#screenshot-list-view-grid'); expect('#screenshot-list-view-grid').toMatchBaselineScreenshot(done, { screenshotName: 'list-view-grid-lg' @@ -19,7 +23,6 @@ describe('List view grid', () => { }); it('should match the previous screenshot (screen: xs)', (done) => { - SkyHostBrowser.get('/visual/list-view-grid'); SkyHostBrowser.setWindowBreakpoint('xs'); SkyHostBrowser.scrollTo('#screenshot-list-view-grid'); expect('#screenshot-list-view-grid').toMatchBaselineScreenshot(done, { @@ -28,8 +31,6 @@ describe('List view grid', () => { }); it('should match the previous screenshot with column chooser open', (done) => { - SkyHostBrowser.get('/visual/list-view-grid'); - SkyHostBrowser.setWindowBreakpoint('lg'); SkyHostBrowser.scrollTo('#screenshot-list-view-grid'); element(by.css('[sky-cmp-id="column-chooser"] button')).click(); @@ -40,7 +41,6 @@ describe('List view grid', () => { }); it('should match the previous screenshot with column chooser open (screen: xs)', (done) => { - SkyHostBrowser.get('/visual/list-view-grid'); SkyHostBrowser.setWindowBreakpoint('xs'); SkyHostBrowser.scrollTo('#screenshot-list-view-grid'); @@ -52,8 +52,6 @@ describe('List view grid', () => { }); it('should match the previous screenshot with a highlighted row', (done) => { - SkyHostBrowser.get('/visual/list-view-grid'); - SkyHostBrowser.setWindowBreakpoint('lg'); SkyHostBrowser.scrollTo('#screenshot-list-view-grid'); element(by.css('#highlight-row-button')).click(); @@ -64,7 +62,6 @@ describe('List view grid', () => { }); it('should match the previous screenshot with a highlighted row (screen: xs)', (done) => { - SkyHostBrowser.get('/visual/list-view-grid'); SkyHostBrowser.setWindowBreakpoint('xs'); SkyHostBrowser.scrollTo('#screenshot-list-view-grid'); @@ -74,4 +71,21 @@ describe('List view grid', () => { screenshotName: 'list-view-grid-highlighted-row-xs' }); }); + + it('should match the previous screenshot with multiselect enabled', (done) => { + SkyHostBrowser.scrollTo('#screenshot-list-view-grid-with-multiselect'); + + expect('#screenshot-list-view-grid-with-multiselect').toMatchBaselineScreenshot(done, { + screenshotName: 'list-view-grid-with-multiselect' + }); + }); + + it('should match the previous screenshot with multiselect enabled (screen: xs)', (done) => { + SkyHostBrowser.setWindowBreakpoint('xs'); + SkyHostBrowser.scrollTo('#screenshot-list-view-grid-with-multiselect'); + + expect('#screenshot-list-view-grid-with-multiselect').toMatchBaselineScreenshot(done, { + screenshotName: 'list-view-grid-with-multiselect-xs' + }); + }); }); diff --git a/package.json b/package.json index e7e051e..356b722 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@skyux/grids": "^3.0.0", "@skyux/i18n": "^3.5.0", "@skyux/indicators": "^3.0.0", - "@skyux/list-builder": "^3.0.1", + "@skyux/list-builder": "^3.2.1", "@skyux/list-builder-view-checklist": "^3.0.0", "@skyux/list-builder-common": "^3.0.0", "@skyux/modals": "^3.0.0", @@ -36,8 +36,8 @@ }, "dependencies": {}, "devDependencies": { - "@blackbaud/skyux": "2.43.0", - "@blackbaud/skyux-builder": "1.32.1", + "@blackbaud/skyux": "2.45.0", + "@blackbaud/skyux-builder": "1.33.0", "@skyux-sdk/builder-plugin-skyux": "1.0.0-rc.6" } } diff --git a/screenshots-baseline/list-View-Grid-Column-Chooser-Lg-chrome-1044x788-dpr-1.png b/screenshots-baseline/list-View-Grid-Column-Chooser-Lg-chrome-1044x788-dpr-1.png deleted file mode 100644 index be84934..0000000 Binary files a/screenshots-baseline/list-View-Grid-Column-Chooser-Lg-chrome-1044x788-dpr-1.png and /dev/null differ diff --git a/screenshots-baseline/list-View-Grid-Column-Chooser-Xs-chrome-480x788-dpr-1.png b/screenshots-baseline/list-View-Grid-Column-Chooser-Xs-chrome-480x788-dpr-1.png deleted file mode 100644 index fdb666c..0000000 Binary files a/screenshots-baseline/list-View-Grid-Column-Chooser-Xs-chrome-480x788-dpr-1.png and /dev/null differ diff --git a/screenshots-baseline/list-View-Grid-Column-Chooser-Xs-chrome-481x788-dpr-1.png b/screenshots-baseline/list-View-Grid-Column-Chooser-Xs-chrome-481x788-dpr-1.png deleted file mode 100644 index f1d9143..0000000 Binary files a/screenshots-baseline/list-View-Grid-Column-Chooser-Xs-chrome-481x788-dpr-1.png and /dev/null differ diff --git a/screenshots-baseline/list-View-Grid-Highlighted-Row-Lg-chrome-1044x788-dpr-1.png b/screenshots-baseline/list-View-Grid-Highlighted-Row-Lg-chrome-1044x788-dpr-1.png deleted file mode 100644 index ae05e87..0000000 Binary files a/screenshots-baseline/list-View-Grid-Highlighted-Row-Lg-chrome-1044x788-dpr-1.png and /dev/null differ diff --git a/screenshots-baseline/list-View-Grid-Highlighted-Row-Xs-chrome-480x788-dpr-1.png b/screenshots-baseline/list-View-Grid-Highlighted-Row-Xs-chrome-480x788-dpr-1.png deleted file mode 100644 index 22a9b41..0000000 Binary files a/screenshots-baseline/list-View-Grid-Highlighted-Row-Xs-chrome-480x788-dpr-1.png and /dev/null differ diff --git a/screenshots-baseline/list-View-Grid-Highlighted-Row-Xs-chrome-481x788-dpr-1.png b/screenshots-baseline/list-View-Grid-Highlighted-Row-Xs-chrome-481x788-dpr-1.png deleted file mode 100644 index 607f977..0000000 Binary files a/screenshots-baseline/list-View-Grid-Highlighted-Row-Xs-chrome-481x788-dpr-1.png and /dev/null differ diff --git a/screenshots-baseline/list-View-Grid-Lg-chrome-1044x788-dpr-1.png b/screenshots-baseline/list-View-Grid-Lg-chrome-1044x788-dpr-1.png deleted file mode 100644 index 2022cc7..0000000 Binary files a/screenshots-baseline/list-View-Grid-Lg-chrome-1044x788-dpr-1.png and /dev/null differ diff --git a/screenshots-baseline/list-View-Grid-Xs-chrome-480x788-dpr-1.png b/screenshots-baseline/list-View-Grid-Xs-chrome-480x788-dpr-1.png deleted file mode 100644 index 9327bcc..0000000 Binary files a/screenshots-baseline/list-View-Grid-Xs-chrome-480x788-dpr-1.png and /dev/null differ diff --git a/screenshots-baseline/list-View-Grid-Xs-chrome-481x788-dpr-1.png b/screenshots-baseline/list-View-Grid-Xs-chrome-481x788-dpr-1.png deleted file mode 100644 index ae0f239..0000000 Binary files a/screenshots-baseline/list-View-Grid-Xs-chrome-481x788-dpr-1.png and /dev/null differ diff --git a/src/app/public/modules/list-view-grid/fixtures/list-view-grid.component.fixture.html b/src/app/public/modules/list-view-grid/fixtures/list-view-grid.component.fixture.html index b535f8f..94d5a85 100644 --- a/src/app/public/modules/list-view-grid/fixtures/list-view-grid.component.fixture.html +++ b/src/app/public/modules/list-view-grid/fixtures/list-view-grid.component.fixture.html @@ -1,12 +1,14 @@ boolean; public showNgIfCol: boolean = false; @@ -42,4 +52,8 @@ export class ListViewGridTestComponent implements OnInit { this.asyncDescription.next('Column1 Description'); }, 100); } + + public multiselectSelectionChange(multiselectSelectionChange: SkyGridSelectedRowsModelChange) { + console.log(multiselectSelectionChange); + } } diff --git a/src/app/public/modules/list-view-grid/list-view-grid.component.html b/src/app/public/modules/list-view-grid/list-view-grid.component.html index e3a6d0e..1c914bd 100644 --- a/src/app/public/modules/list-view-grid/list-view-grid.component.html +++ b/src/app/public/modules/list-view-grid/list-view-grid.component.html @@ -1,19 +1,25 @@
+ #skyGrid + > - + +
diff --git a/src/app/public/modules/list-view-grid/list-view-grid.component.spec.ts b/src/app/public/modules/list-view-grid/list-view-grid.component.spec.ts index 682b70e..897f4f7 100644 --- a/src/app/public/modules/list-view-grid/list-view-grid.component.spec.ts +++ b/src/app/public/modules/list-view-grid/list-view-grid.component.spec.ts @@ -105,7 +105,19 @@ describe('List View Grid Component', () => { component = fixture.componentInstance; })); - function setupTest() { + function getSelectInputs() { + return element.queryAll(By.css('.sky-grid-multiselect-cell input')); + } + + function clickSelectInputByIndex(id: number) { + const selectInputs = getSelectInputs(); + selectInputs[id].nativeElement.click(); + fixture.detectChanges(); + } + + function setupTest(enableMultiselect: boolean = false) { + component.enableMultiselect = enableMultiselect; + fixture.detectChanges(); let items = [ @@ -407,6 +419,44 @@ describe('List View Grid Component', () => { }); }); + describe('multiselect', () => { + it('should send action to the dispatcher when multiselect is enabled', fakeAsync(() => { + const spy = spyOn(dispatcher, 'toolbarShowMultiselectToolbar'); + + setupTest(true); // enable multiselect + flush(); + tick(110); // wait for async heading + fixture.detectChanges(); + + expect(spy).toHaveBeenCalledWith(true); + })); + + it('should send actions to the dispatcher on multiselectSelectionChange', fakeAsync(() => { + const spy = spyOn(dispatcher, 'setSelected').and.callThrough(); + + setupTest(true); // enable multiselect + flush(); + tick(110); // wait for async heading + fixture.detectChanges(); + + // Select first row. + clickSelectInputByIndex(0); + fixture.detectChanges(); + + // Expect dispatcher to send action. + expect(spy).toHaveBeenCalledWith(['1'], true); + + // Deselect first row. + spy.calls.reset(); + flush(); + clickSelectInputByIndex(0); + fixture.detectChanges(); + + // Expect dispatcher to send action. + expect(spy).toHaveBeenCalledWith(['1'], false); + })); + }); + describe('nonstandard setup', () => { it('should respect the hidden property when not hidden columns and displayed columns', fakeAsync(() => { diff --git a/src/app/public/modules/list-view-grid/list-view-grid.component.ts b/src/app/public/modules/list-view-grid/list-view-grid.component.ts index c06db3b..d52b1d2 100644 --- a/src/app/public/modules/list-view-grid/list-view-grid.component.ts +++ b/src/app/public/modules/list-view-grid/list-view-grid.component.ts @@ -32,13 +32,15 @@ import { SkyGridColumnComponent, SkyGridColumnHeadingModelChange, SkyGridColumnDescriptionModelChange, - SkyGridColumnModel + SkyGridColumnModel, + SkyGridSelectedRowsModelChange } from '@skyux/grids'; import { ListSearchModel, ListStateDispatcher, - ListState + ListState, + ListSelectedModel } from '@skyux/list-builder/modules/list/state'; import { @@ -103,6 +105,12 @@ export class SkyListViewGridComponent @Input() public rowHighlightedId: string; + @Input() + public enableMultiselect: boolean = false; + + @Input() + public multiselectRowId: string; + @Output() public selectedColumnIdsChange = new EventEmitter>(); @@ -121,6 +129,8 @@ export class SkyListViewGridComponent public currentSearchText: Observable; + private multiselectSelectedIds: string[] = []; + /* tslint:disable */ @Input('search') private searchFunction: (data: any, searchText: string) => boolean; @@ -141,6 +151,23 @@ export class SkyListViewGridComponent } public ngAfterContentInit() { + + // Watch for selection changes and update multiselectSelectedIds for local comparison. + this.state.map(s => s.selected.item) + .takeUntil(this.ngUnsubscribe) + .distinctUntilChanged(this.selectedMapEqual) + .subscribe((items: ListSelectedModel) => { + const selectedIds: string[] = []; + + items.selectedIdMap.forEach((isSelected, id) => { + if (items.selectedIdMap.get(id) === true) { + selectedIds.push(id); + } + }); + + this.multiselectSelectedIds = selectedIds; + }); + if (this.columnComponents.length === 0) { throw new Error('Grid view requires at least one sky-grid-column to render.'); } @@ -232,6 +259,10 @@ export class SkyListViewGridComponent this.gridDispatcher.next(new ListViewGridColumnsLoadAction(columnModels, true)); this.handleColumnChange(); + + if (this.enableMultiselect) { + this.dispatcher.toolbarShowMultiselectToolbar(true); + } } public ngOnDestroy() { @@ -239,6 +270,27 @@ export class SkyListViewGridComponent this.ngUnsubscribe.complete(); } + public onMultiselectSelectionChange(event: SkyGridSelectedRowsModelChange): void { + this.state.map(s => s.items.items) + .take(1) + .subscribe((items: ListItemModel[]) => { + const newItemIds = this.arrayIntersection(items.map(i => i.id), this.multiselectSelectedIds); + const newIds = items.filter(i => i.isSelected).map(i => i.id); + + // Check for deselected ids & send message to dispatcher. + const deselectedIds = this.arrayDiff(newItemIds, newIds); + if (deselectedIds.length > 0) { + this.dispatcher.setSelected(deselectedIds, false); + } + + // Check for selected ids & send message to dispatcher. + const selectedIds = this.arrayDiff(newIds, newItemIds); + if (selectedIds.length > 0) { + this.dispatcher.setSelected(selectedIds, true); + } + }); + } + public columnIdsChanged(selectedColumnIds: Array) { this.selectedColumnIds .take(1) @@ -388,6 +440,36 @@ export class SkyListViewGridComponent return true; } + private selectedMapEqual(prev: ListSelectedModel, next: ListSelectedModel): boolean { + if (prev.selectedIdMap.size !== next.selectedIdMap.size) { + return false; + } + + let keys: string[] = []; + next.selectedIdMap.forEach((value, key) => { + keys.push(key); + }); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + + const value = next.selectedIdMap.get(key); + if (value !== prev.selectedIdMap.get(key)) { + return false; + } + } + + return true; + } + + private arrayDiff(arrA: Array, arrB: Array): Array { + return arrA.filter(i => arrB.indexOf(i) < 0); + } + + private arrayIntersection(arrA: Array, arrB: Array): Array { + return arrA.filter(value => -1 !== arrB.indexOf(value)); + } + private arraysEqual(arrayA: any[], arrayB: any[]) { return arrayA.length === arrayB.length && arrayA.every((value, index) => diff --git a/src/app/visual/list-view-grid/list-view-grid-visual.component.html b/src/app/visual/list-view-grid/list-view-grid-visual.component.html index f09e130..b3f26e5 100644 --- a/src/app/visual/list-view-grid/list-view-grid-visual.component.html +++ b/src/app/visual/list-view-grid/list-view-grid-visual.component.html @@ -1,5 +1,11 @@ -
- +
+ + [locked]="true" + > + > + > @@ -39,3 +48,47 @@

+ +
+ + + + + + + + + + + + + + + +