3
votes

I'm using a paginated version of SlickGrid in conjunction with the CheckboxSelection-Plugin. Unfortunately, the row selection gets lost when one leaves the current grid page and isn't restored when that page is accessed again. This behaviour can also be reproduced with SlickGrid example #4. (click the little light-bulb-icon in the grid footer to turn on pagination)

I've informed SlickGrid's author about this, but he doesn't see this behaviour as a bug, as SlickGrid is more a grid framework than a just-add-data-solution.

Does anyone have an idea on how to implement reliable selection persistence in SlickGrid, that works for both paginated and non-paginated grids?

4

4 Answers

2
votes

Finally I had some success by adapting the grid.onSelectedRowsChanged callback in Example 4. The key difference is that I don't reset the global selectedRowIds array each time the selection changes, since grid.getSelectedRows() only gives me the selected row indices on the current page. Instead I first add all newly selected rows to the selectedRowIds array and then iterate over it to check if one of its current-page-entries has been deselected.

grid.onSelectedRowsChanged.subscribe(function (e) {
    var selRows = grid.getSelectedRows();
    // add all newly selected rows to the selection
    for (var i = 0, l = selRows.length; i < l; i++) {
        var item = dataView.getItem(selRows[i]);
        if (item) {
            var rowId = item.id;
            if (selectedRowIds.indexOf(rowId) < 0) {
                selectedRowIds.push(rowId);
            }
        }
    }

    //check if a previously selected row has been deselected
    for (var i = 0, l = selectedRowIds.length; i < l; i++) {
        var row = dataView.getRowById(selectedRowIds[i]); // see if the selected row is   available on the current page
        if (row && selRows.indexOf(row) < 0) {
            selectedRowIds.splice(i, 1); //remove row from array
        }
    }
   });
1
votes

I know this is an old question but I had to implement this and the proposed solution didn't cover all use case (for example, the proposed solution by @fbuchinger removes some selection if user goes to last page, without doing any selections, and comes back to first page).

To deal with this issue, I had to do a much more complex implementation and I even had to add a new DataView event onBeforePagingInfoChanged and also modified the SlickGrid event onSelectedRowsChanged to also return previous row selections (on top of current selections). These changes were made in the 6pac/SlickGrid fork and were released under version 2.4.18

The steps I used are the following when using Pagination, we can't just use the getSelectedRows() since this will only return the selection in current page which is not enough, we need to keep a global array with data object IDs (not the row indexes since that will keep changing in a page), to keep both the grid indexes and the data IDs, we'll alternate between dataView.mapIdsToRows() and dataView.mapRowsToIds().

The steps of the code shown below are 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
// global variables
_selectedRowDataContextIds = [];     // used with row selection
_wasRecheckedAfterPageChange = true; // used with row selection & pagination

function bindSlickgridEventsForRowSelectionWithPagination() {
this._eventHandler.subscribe(this._dataView.onBeforePagingInfoChanged, () => {
        this._wasRecheckedAfterPageChange = false; // reset the page check flag, to skip deletions on page change (used in code below)
      });

      this._eventHandler.subscribe(this._dataView.onPagingInfoChanged, () => {
        // when user changes page, the selected row indexes might not show up
        // we can check to make sure it is but it has to be in a delay so it happens after the first "onSelectedRowsChanged" is triggered
        setTimeout(() => {
          const shouldBeSelectedRowIndexes = this._dataView.mapIdsToRows(this._selectedRowDataContextIds);
          const currentSelectedRowIndexes = this._grid.getSelectedRows();
          if (!isequal(shouldBeSelectedRowIndexes, currentSelectedRowIndexes)) {
            this._grid.setSelectedRows(shouldBeSelectedRowIndexes);
          }
        });
      });

      this._eventHandler.subscribe(this._grid.onSelectedRowsChanged, (e, args) => {
        if (Array.isArray(args.rows) && Array.isArray(args.previousSelectedRows)) {
          const newSelectedRows = args.rows;
          const prevSelectedRows = args.previousSelectedRows;

          const newSelectAdditions = newSelectedRows.filter((i) => prevSelectedRows.indexOf(i) < 0);
          const newSelectDeletions = prevSelectedRows.filter((i) => newSelectedRows.indexOf(i) < 0);

          // deletion might happen when user is changing page, if that is the case then skip the deletion since it's only a visual deletion
          // if it's not a page change (when the flag is true), then proceed with the deletion in our global array of selected IDs
          if (this._wasRecheckedAfterPageChange && newSelectDeletions.length > 0) {
            const toDeleteDataIds = this._dataView.mapRowsToIds(newSelectDeletions);
            toDeleteDataIds.forEach((removeId) => this._selectedRowDataContextIds.splice(this._selectedRowDataContextIds.indexOf(removeId), 1));
          }

          // if we have newly added selected row(s), let's update our global array of selected IDs
          if (newSelectAdditions.length > 0) {
            const toAddDataIds = this._dataView.mapRowsToIds(newSelectAdditions) || [];
            toAddDataIds.forEach((dataId) => {
              if (this._selectedRowDataContextIds.indexOf(dataId) === -1) {
                this._selectedRowDataContextIds.push(dataId);
              }
            });
          }
          this._wasRecheckedAfterPageChange = true;

          // form our full selected row IDs, let's make sure these indexes are selected in the grid, if not then let's call a reselect
          // this could happen if the previous step was a page change
          const shouldBeSelectedRowIndexes = this._dataView.mapIdsToRows(this._selectedRowDataContextIds);
          const currentSelectedRowIndexes = this._grid.getSelectedRows();
          if (!isequal(shouldBeSelectedRowIndexes, currentSelectedRowIndexes)) {
            this._grid.setSelectedRows(shouldBeSelectedRowIndexes);
          }


          const newSelections = { gridRowIndexes: this._grid.getSelectedRows(), dataContextIds: this._selectedRowDataContextIds };
        }
      });
}

So in this code, the variable this._selectedRowDataContextIds has the full set of selected data object IDs. It's a complex approach but that is what I found so far to be working with a grid Pagination.

Note that there's only 1 lodash function isequal that is used in the code to compare 2 arrays, everything else is written in ES6 and TypeScript (though I removed the types to be more readable)

0
votes

Proposal:

  1. Add an array of rows before grid definition.

    var selectedRows = [];
    
  2. In the pagging event, before all obtain and store selected rows if the row aren't in array yet:

    function AddRows(grid.getSelectedRows()){//your comparision here};
    
  3. In paggin event set selected rows:

    grid.setSelectedRows(selectedRows ); 
    
  4. Prevent deselect. If a row is deselect, get the row index in array, and remove the row from array:

    function GetRowSelectedIndex(row);
    var rowIndex = GetRowSelectedIndex(row);
    selectedRows.slice(rowIndex,1);
    
0
votes

it works fine. Just modified the above one.

                var selRows = grid.getSelectedRows();
                var item = {};
                for (var i = 0; i < selRows.length; i++) {
                    item = dataView.getItem(selRows[i]);
                    if (item != undefined && $.inArray(item.id, selectedRowIds) < 0 ) {
                        selectedRowIds.push(item.id);
                    }
                }
                var removeIds = [];
                var row;
                for (var i = 0; i < selectedRowIds.length; i++) {
                    for (var j = 0; j < selectedRowIds.length; j++) {
                        row = dataView.getRowById(selectedRowIds[j]);
                        if(row != undefined && $.inArray(row, removeIds) < 0){
                            removeIds.push(row);
                            break;
                        }
                    }
                }
                for (var i = 0; i < removeIds.length; i++) {
                    if (removeIds[i] != undefined && $.inArray(removeIds[i], selRows) < 0) {
                        item = dataView.getItem(removeIds[i]);
                        selectedRowIds = $.grep(selectedRowIds, function( a ) { return a !== item.id; });
                    }
                }