Skip to content

Commit

Permalink
fix(selection): row selection always be kept, closes #295 again (#399)
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding committed Feb 6, 2020
1 parent e830aad commit 5e8f1df
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 81 deletions.
5 changes: 3 additions & 2 deletions src/app/examples/grid-localization.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ <h2>{{title}}</h2>
</div>

<div class="col-sm-12">
<angular-slickgrid gridId="grid12" (onAngularGridCreated)="angularGridReady($event)"
[columnDefinitions]="columnDefinitions" [gridOptions]="gridOptions" [dataset]="dataset">
<angular-slickgrid gridId="grid12" [dataset]="dataset" [gridOptions]="gridOptions"
[columnDefinitions]="columnDefinitions" (onAngularGridCreated)="angularGridReady($event)"
(onGridStateChanged)="gridStateChanged($event)">
</angular-slickgrid>
</div>
</div>
16 changes: 15 additions & 1 deletion src/app/examples/grid-localization.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
Filters,
Formatter,
Formatters,
GridOption
GridOption,
GridStateChange
} from './../modules/angular-slickgrid';

const NB_ITEMS = 1500;
Expand Down Expand Up @@ -139,6 +140,13 @@ export class GridLocalizationComponent implements OnInit {
enableFiltering: true,
enableTranslate: true,
i18n: this.translate,
checkboxSelector: {
// you can toggle these 2 properties to show the "select all" checkbox in different location
hideInFilterHeaderRow: false,
hideInColumnTitleRow: true
},
enableCheckboxSelector: true,
enableRowSelection: true,
showCustomFooter: true, // display some metrics in the bottom custom footer
customFooterOptions: {
metricTexts: {
Expand Down Expand Up @@ -237,6 +245,12 @@ export class GridLocalizationComponent implements OnInit {
});
}

/** Dispatched event of a Grid State Changed event */
gridStateChanged(gridStateChanges: GridStateChange) {
console.log('Grid State changed:: ', gridStateChanges);
console.log('Grid State changed:: ', gridStateChanges.change);
}

switchLanguage() {
const nextLanguage = (this.selectedLanguage === 'en') ? 'fr' : 'en';
this.translate.use(nextLanguage).subscribe(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,40 +306,6 @@ describe('GridStateService', () => {
expect(output).toBeNull();
});

it('should return the current row selections in the grid when "enableCheckboxSelector" flag is enabled', () => {
const selectedGridRows = [2];
const selectedRowIds = [99];
const mockRowItems = [{ id: 99 }];
const gridOptionsMock = { enableCheckboxSelector: true } as GridOption;
jest.spyOn(dataViewStub, 'getFilteredItems').mockReturnValue(mockRowItems);
jest.spyOn(gridStub, 'getSelectedRows').mockReturnValue(selectedGridRows);
jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock);
const sharedSpy = jest.spyOn(dataViewStub, 'mapRowsToIds').mockReturnValue(selectedRowIds);

service.selectedRowDataContextIds = selectedRowIds;
const output = service.getCurrentRowSelections();

expect(sharedSpy).toHaveBeenCalled();
expect(output).toEqual({ gridRowIndexes: selectedGridRows, dataContextIds: selectedRowIds, filteredDataContextIds: selectedRowIds });
});

it('should return the current row selections in the grid when "enableRowSelection" flag is enabled', () => {
const selectedGridRows = [2];
const selectedRowIds = [99];
const mockRowItems = [{ id: 99 }];
const gridOptionsMock = { enableRowSelection: true } as GridOption;
jest.spyOn(dataViewStub, 'getFilteredItems').mockReturnValue(mockRowItems);
jest.spyOn(gridStub, 'getSelectedRows').mockReturnValue(selectedGridRows);
jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock);
const sharedSpy = jest.spyOn(dataViewStub, 'mapRowsToIds').mockReturnValue(selectedRowIds);

service.selectedRowDataContextIds = selectedRowIds;
const output = service.getCurrentRowSelections();

expect(sharedSpy).toHaveBeenCalled();
expect(output).toEqual({ gridRowIndexes: selectedGridRows, dataContextIds: selectedRowIds, filteredDataContextIds: selectedRowIds });
});

it('should call "getCurrentGridState" method and return the Row Selection when either "enableCheckboxSelector" or "enableRowSelection" flag is enabled', () => {
const selectedGridRows = [2];
const selectedRowIds = [99];
Expand All @@ -365,7 +331,7 @@ describe('GridStateService', () => {
expect(output).toEqual({ columns: columnMock, filters: filterMock, sorters: sorterMock, rowSelection: selectionMock } as GridState);
});

it('should call the "mapIdsToRows" from the DataView and get the data IDs from the "selectedRowDataContextIds" array when Pagination is enabled', () => {
it('should call the "mapIdsToRows" from the DataView and get the data IDs from the "selectedRowDataContextIds" array', () => {
const mockRowIndexes = [3, 44];
const mockRowIds = [333, 444];
const mockRowItems = [{ id: 333 }, { id: 444 }];
Expand Down
40 changes: 6 additions & 34 deletions src/app/modules/angular-slickgrid/services/gridState.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,17 +230,9 @@ export class GridStateService {
getCurrentRowSelections(requestRefreshFilteredRow = true): CurrentRowSelection | null {
if (this._grid && this._gridOptions && this._dataView && this.hasRowSelectionEnabled()) {
if (this._grid.getSelectedRows && this._dataView.mapRowsToIds) {
let gridRowIndexes: number[] = [];
let dataContextIds: Array<number | string> | undefined = [];
let filteredDataContextIds: Array<number | string> | undefined = [];

if (this._gridOptions.enablePagination) {
gridRowIndexes = this._dataView.mapIdsToRows(this._selectedRowDataContextIds || []); // note that this will return only what is visible in current page
dataContextIds = this._selectedRowDataContextIds;
} else {
gridRowIndexes = this._grid.getSelectedRows() || [];
dataContextIds = this._dataView.mapRowsToIds(gridRowIndexes) || [];
}
const gridRowIndexes: number[] = this._dataView.mapIdsToRows(this._selectedRowDataContextIds || []); // note that this will return only what is visible in current page
const dataContextIds: Array<number | string> | undefined = this._selectedRowDataContextIds;

// user might request to refresh the filtered selection dataset
// typically always True, except when "reEvaluateRowSelectionAfterFilterChange" is called and we don't need to refresh the filtered dataset twice
Expand Down Expand Up @@ -415,34 +407,14 @@ export class GridStateService {

/**
* Bind a Grid Event of Row Selection change to a Grid State change event
* @param event name
* @param grid
*/
private bindSlickGridRowSelectionToGridStateChange() {
if (this._gridOptions && this._gridOptions.enablePagination) {
// when using pagination, the process is much more complex so let's do it in a separate method
this.bindSlickGridRowSelectionWithPaginationToGridStateChange();
} else {
// without Pagination, we can simply get the getSelectedRows() and mapRowsToIds() and we're done
this._eventHandler.subscribe(this._grid.onSelectedRowsChanged, () => {
const currentSelectedRowIndexes = this._grid.getSelectedRows();
this._selectedRowDataContextIds = this._dataView.mapRowsToIds(currentSelectedRowIndexes);
const filteredDataContextIds = this.refreshFilteredRowSelections();
const newValues = { gridRowIndexes: currentSelectedRowIndexes, dataContextIds: this._selectedRowDataContextIds, filteredDataContextIds } as CurrentRowSelection;
this.onGridStateChanged.next({ change: { newValues, type: GridStateType.rowSelection }, gridState: this.getCurrentGridState({ requestRefreshRowFilteredRow: false }) });
});
}
}

/**
* When using Pagination, we can't just use the getSelectedRows() since this will only return the selection in current page which is not enough.
* For the row selection, we can't just use the getSelectedRows() since this will only return the visible rows shown in the UI which is not enough.
* The process is much more complex, what we have to do instead is the following
* 1. when changing a row selection, we'll add the new selection if it's not yet in the global array of selected IDs
* 2. when deleting a row selection, we'll remove the selection from our global array of selected IDs (unless it came from a page change)
* 3. before we change page, we'll keep track with a flag (this flag will be used to skip any deletion when we're changing page)
* 4. after the page is changed, we'll do an extra (and delayed) check to make sure that what we have in our global array of selected IDs is displayed on screen
* 3. if we use Pagination and we change page, we'll keep track with a flag (this flag will be used to skip any deletion when we're changing page)
* 4. after the Page or DataView is changed or updated, we'll do an extra (and delayed) check to make sure that what we have in our global array of selected IDs is displayed on screen
*/
private bindSlickGridRowSelectionWithPaginationToGridStateChange() {
private bindSlickGridRowSelectionToGridStateChange() {
if (this._grid && this._gridOptions && this._dataView) {
this._eventHandler.subscribe(this._dataView.onBeforePagingInfoChanged, () => {
this._wasRecheckedAfterPageChange = false; // reset the page check flag, to skip deletions on page change (used in code below)
Expand Down
150 changes: 141 additions & 9 deletions test/cypress/integration/example12.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ function removeExtraSpaces(textS) {
}

describe('Example 12: Localization (i18n)', () => {
const fullEnglishTitles = ['Title', 'Description', 'Duration', 'Start', 'Finish', 'Completed', 'Completed'];
const fullFrenchTitles = ['Titre', 'Description', 'Durée', 'Début', 'Fin', 'Terminé', 'Terminé'];
const fullEnglishTitles = ['', 'Title', 'Description', 'Duration', 'Start', 'Finish', 'Completed', 'Completed'];
const fullFrenchTitles = ['', 'Titre', 'Description', 'Durée', 'Début', 'Fin', 'Terminé', 'Terminé'];

beforeEach(() => {
cy.restoreLocalStorage();

// create a console.log spy for later use
cy.window().then((win) => {
cy.spy(win.console, "log");
});
});

afterEach(() => {
Expand All @@ -36,8 +41,8 @@ describe('Example 12: Localization (i18n)', () => {
.find('.slick-custom-footer')
.find('.right-footer')
.should($span => {
const now = new Date();
const text = removeExtraSpaces($span.text()); // remove all white spaces
const now = new Date();
const dateFormatted = moment(now).format('YYYY-MM-DD hh:mm a');
expect(text).to.eq(`Last Update ${dateFormatted} | 1500 of 1500 items`);
});
Expand All @@ -59,8 +64,7 @@ describe('Example 12: Localization (i18n)', () => {
if (index > tasks.length - 1) {
return;
}
cy.wrap($row).children('.slick-cell')
.first()
cy.wrap($row).children('.slick-cell:nth(1)')
.should('contain', tasks[index]);
});
});
Expand Down Expand Up @@ -122,8 +126,7 @@ describe('Example 12: Localization (i18n)', () => {
if (index > tasks.length - 1) {
return;
}
cy.wrap($row).children('.slick-cell')
.first()
cy.wrap($row).children('.slick-cell:nth(1)')
.should('contain', tasks[index]);
});
});
Expand Down Expand Up @@ -158,13 +161,13 @@ describe('Example 12: Localization (i18n)', () => {
}

// get full cell width of the first cell, then return
cy.wrap($row).children('.slick-cell:nth(2)')
cy.wrap($row).children('.slick-cell:nth(3)')
.first()
.then(($cell) => fullCellWidth = $cell.width());


cy.wrap($row)
.children('.slick-cell:nth(2)')
.children('.slick-cell:nth(3)')
.children()
.should('have.css', 'background-color', 'rgb(255, 0, 0)')
.should(($el) => {
Expand All @@ -176,4 +179,133 @@ describe('Example 12: Localization (i18n)', () => {
});
});
});

describe('Row Selection', () => {
it('should switch locale back to English and reset all Filters', () => {
cy.get('[data-test=language-button]')
.click();

cy.get('[data-test=selected-locale]')
.should('contain', 'en.json');

cy.get('#grid12')
.find('button.slick-gridmenu-button')
.trigger('click')
.click();

cy.get(`.slick-gridmenu:visible`)
.find('.slick-gridmenu-item')
.first()
.find('span')
.contains('Clear all Filters')
.click();
});

it('should hover over the Title column and click on "Sort Descending" command', () => {
cy.get('#slickGridContainer-grid12')
.find('.slick-header-column:nth(1)')
.trigger('mouseover')
.children('.slick-header-menubutton')
.should('be.hidden')
.invoke('show')
.click();

cy.get('.slick-header-menu')
.should('be.visible')
.children('.slick-header-menuitem:nth-child(2)')
.children('.slick-header-menucontent')
.should('contain', 'Sort Descending')
.click();

cy.get('.slick-row')
.children('.slick-cell:nth(1)')
.first()
.should('contain', 'Task 1499');
});

it('should select the row with "Task 1497" and expect the Grid State to be called with it in the console', () => {
cy.get('#slickGridContainer-grid12').as('grid12');

cy.get('#grid12')
.contains('Task 1497')
.parent()
.children('.slick-cell-checkboxsel')
.find('input[type=checkbox]')
.click({ force: true });

cy.window().then((win) => {
expect(win.console.log).to.have.callCount(2);
expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: { gridRowIndexes: [2], dataContextIds: [1497], filteredDataContextIds: [1497] }, type: 'rowSelection' });
});
});

it('should scroll to bottom of the grid then select "Task 4"', () => {
cy.get('#slickGridContainer-grid12').as('grid12');

cy.get('@grid12')
.find('.slick-viewport-top.slick-viewport-left')
.scrollTo('bottom')
.wait(10);

cy.get('#grid12')
.contains('Task 4')
.parent()
.children('.slick-cell-checkboxsel')
.find('input[type=checkbox]')
.click({ force: true });

cy.window().then((win) => {
expect(win.console.log).to.have.callCount(2);
expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: { gridRowIndexes: [1495, 2], dataContextIds: [1497, 4], filteredDataContextIds: [1497, 4] }, type: 'rowSelection' });
});
});

it('should filter the Tasks column with number 4 and expect only "Task 4" visible in the grid', () => {
cy.get('#slickGridContainer-grid12').as('grid12');

cy.get('.grid-canvas')
.find('.slick-row')
.should('be.visible');

cy.get('input.filter-title')
.type('4');

cy.get('@grid12')
.find('.slick-row')
.children()
.filter('.slick-cell-checkboxsel.selected.true')
.should('have.length', 1);

cy.get('@grid12')
.find('.slick-row')
.children()
.filter('.slick-cell.selected.true:nth(1)')
.contains('Task 4');
});

it('should scroll back to the top and expect to see "Task 1497" still selected', () => {
cy.get('#slickGridContainer-grid12').as('grid12');

cy.get('.grid-canvas')
.find('.slick-row')
.should('be.visible');

cy.get('@grid12')
.find('.slick-viewport-top.slick-viewport-left')
.scrollTo('top')
.wait(10);

cy.get('@grid12')
.find('.slick-row')
.children()
.filter('.slick-cell-checkboxsel.selected.true')
.should('have.length', 1);

cy.get('@grid12')
.find('.slick-row')
.children()
.filter('.slick-cell.selected.true:nth(1)')
.contains('Task 1497');
});
});
});

0 comments on commit 5e8f1df

Please sign in to comment.