Skip to content

Commit

Permalink
feat: conditional selectability of grid items (#7974)
Browse files Browse the repository at this point in the history
* basic isItemSelectable implementation

* handle select all checkbox

* fix default renderer

* fix more select all cases

* make checkboxes readonly or hidden

* reuse selectable check

* add TS types

* cleanup

* simplify select all logic

* fix select all check

* hide select all when using conditional selection
  • Loading branch information
sissbruecker authored Oct 23, 2024
1 parent c318c01 commit 2bae93a
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 30 deletions.
30 changes: 28 additions & 2 deletions packages/grid/src/vaadin-grid-selection-column-base-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
checkbox.__item = item;
checkbox.__rendererChecked = selected;
checkbox.checked = selected;

const isSelectable = this._grid.__isItemSelectable(item);
checkbox.readonly = !isSelectable;
checkbox.hidden = !isSelectable && !selected;
}

/**
Expand Down Expand Up @@ -245,9 +249,18 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
_onCellKeyDown(e) {
const target = e.composedPath()[0];
// Toggle on Space without having to enter interaction mode first
if (e.keyCode === 32 && (target === this._headerCell || (this._cells.includes(target) && !this.autoSelect))) {
if (e.keyCode !== 32) {
return;
}
if (target === this._headerCell) {
if (this.selectAll) {
this._deselectAll();
} else {
this._selectAll();
}
} else if (this._cells.includes(target) && !this.autoSelect) {
const checkbox = target._content.firstElementChild;
checkbox.checked = !checkbox.checked;
this.__toggleItem(checkbox.__item);
}
}

Expand Down Expand Up @@ -358,6 +371,19 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
*/
_deselectItem(_item) {}

/**
* Toggles the selected state of the given item.
* @param item the item to toggle
* @private
*/
__toggleItem(item) {
if (this._grid._isSelected(item)) {
this._deselectItem(item);
} else {
this._selectItem(item);
}
}

/**
* IOS needs indeterminate + checked at the same time
* @private
Expand Down
25 changes: 17 additions & 8 deletions packages/grid/src/vaadin-grid-selection-column-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ export const GridSelectionColumnMixin = (superClass) =>
super();

this.__boundOnActiveItemChanged = this.__onActiveItemChanged.bind(this);
this.__boundOnDataProviderChanged = this.__onDataProviderChanged.bind(this);
this.__boundUpdateSelectAllVisibility = this.__updateSelectAllVisibility.bind(this);
this.__boundOnSelectedItemsChanged = this.__onSelectedItemsChanged.bind(this);
}

/** @protected */
disconnectedCallback() {
this._grid.removeEventListener('active-item-changed', this.__boundOnActiveItemChanged);
this._grid.removeEventListener('data-provider-changed', this.__boundOnDataProviderChanged);
this._grid.removeEventListener('data-provider-changed', this.__boundUpdateSelectAllVisibility);
this._grid.removeEventListener('is-item-selectable-changed', this.__boundUpdateSelectAllVisibility);
this._grid.removeEventListener('filter-changed', this.__boundOnSelectedItemsChanged);
this._grid.removeEventListener('selected-items-changed', this.__boundOnSelectedItemsChanged);

Expand All @@ -49,7 +50,8 @@ export const GridSelectionColumnMixin = (superClass) =>
super.connectedCallback();
if (this._grid) {
this._grid.addEventListener('active-item-changed', this.__boundOnActiveItemChanged);
this._grid.addEventListener('data-provider-changed', this.__boundOnDataProviderChanged);
this._grid.addEventListener('data-provider-changed', this.__boundUpdateSelectAllVisibility);
this._grid.addEventListener('is-item-selectable-changed', this.__boundUpdateSelectAllVisibility);
this._grid.addEventListener('filter-changed', this.__boundOnSelectedItemsChanged);
this._grid.addEventListener('selected-items-changed', this.__boundOnSelectedItemsChanged);
}
Expand Down Expand Up @@ -111,7 +113,9 @@ export const GridSelectionColumnMixin = (superClass) =>
* @override
*/
_selectItem(item) {
this._grid.selectItem(item);
if (this._grid.__isItemSelectable(item)) {
this._grid.selectItem(item);
}
}

/**
Expand All @@ -123,7 +127,9 @@ export const GridSelectionColumnMixin = (superClass) =>
* @override
*/
_deselectItem(item) {
this._grid.deselectItem(item);
if (this._grid.__isItemSelectable(item)) {
this._grid.deselectItem(item);
}
}

/** @private */
Expand All @@ -132,7 +138,7 @@ export const GridSelectionColumnMixin = (superClass) =>
if (this.autoSelect) {
const item = activeItem || this.__previousActiveItem;
if (item) {
this._grid._toggleItem(item);
this.__toggleItem(item);
}
}
this.__previousActiveItem = activeItem;
Expand Down Expand Up @@ -164,8 +170,11 @@ export const GridSelectionColumnMixin = (superClass) =>
}

/** @private */
__onDataProviderChanged() {
this._selectAllHidden = !Array.isArray(this._grid.items);
__updateSelectAllVisibility() {
// Hide select all checkbox when we can not easily determine the select all checkbox state:
// - When using a custom data provider
// - When using conditional selection, where users may not select all items
this._selectAllHidden = !Array.isArray(this._grid.items) || !!this._grid.isItemSelectable;
}

/**
Expand Down
21 changes: 21 additions & 0 deletions packages/grid/src/vaadin-grid-selection-mixin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,25 @@ export declare class SelectionMixinClass<TItem> {
* @param item The item object
*/
deselectItem(item: TItem): void;

/**
* A function to check whether a specific item in the grid may be
* selected or deselected by the user. Used by the selection column to
* conditionally enable to disable checkboxes for individual items. This
* function does not prevent programmatic selection/deselection of
* items. Changing the function does not modify the currently selected
* items.
*
* Configuring this function hides the select all checkbox of the grid
* selection column, which means users can not select or deselect all
* items anymore, nor do they get feedback on whether all items are
* selected or not.
*
* Receives an item instance and should return a boolean indicating
* whether users may change the selection state of that item.
*
* @param item The item object
* @return Whether the item is selectable
*/
isItemSelectable: (item: TItem) => boolean;
}
56 changes: 40 additions & 16 deletions packages/grid/src/vaadin-grid-selection-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ export const SelectionMixin = (superClass) =>
sync: true,
},

/**
* A function to check whether a specific item in the grid may be
* selected or deselected by the user. Used by the selection column to
* conditionally enable to disable checkboxes for individual items. This
* function does not prevent programmatic selection/deselection of
* items. Changing the function does not modify the currently selected
* items.
*
* Configuring this function hides the select all checkbox of the grid
* selection column, which means users can not select or deselect all
* items anymore, nor do they get feedback on whether all items are
* selected or not.
*
* Receives an item instance and should return a boolean indicating
* whether users may change the selection state of that item.
*
* @type {(item: !GridItem) => boolean}
*/
isItemSelectable: {
type: Function,
notify: true,
},

/**
* Set of selected item ids
* @private
Expand All @@ -34,7 +57,7 @@ export const SelectionMixin = (superClass) =>
}

static get observers() {
return ['__selectedItemsChanged(itemIdPath, selectedItems)'];
return ['__selectedItemsChanged(itemIdPath, selectedItems, isItemSelectable)'];
}

/**
Expand All @@ -46,6 +69,22 @@ export const SelectionMixin = (superClass) =>
return this.__selectedKeys.has(this.getItemId(item));
}

/**
* Determines whether the selection state of an item may be changed by the
* user.
*
* @private
*/
__isItemSelectable(item) {
// Item is selectable by default if isItemSelectable is not configured
if (!this.isItemSelectable || !item) {
return true;
}

// Otherwise, check isItemSelectable function
return this.isItemSelectable(item);
}

/**
* Selects the given item.
*
Expand All @@ -70,21 +109,6 @@ export const SelectionMixin = (superClass) =>
}
}

/**
* Toggles the selected state of the given item.
*
* @method toggle
* @param {!GridItem} item The item object
* @protected
*/
_toggleItem(item) {
if (!this._isSelected(item)) {
this.selectItem(item);
} else {
this.deselectItem(item);
}
}

/** @private */
__selectedItemsChanged() {
this.requestContentUpdate();
Expand Down
16 changes: 12 additions & 4 deletions packages/grid/test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,24 @@ export const getContainerCellContent = (container, row, col) => {
return getCellContent(getContainerCell(container, row, col));
};

export const getHeaderCellContent = (grid, row, col) => {
export const getHeaderCell = (grid, row, col) => {
const container = grid.$.header;
return getContainerCellContent(container, row, col);
return getContainerCell(container, row, col);
};

export const getBodyCellContent = (grid, row, col) => {
export const getHeaderCellContent = (grid, row, col) => {
return getCellContent(getHeaderCell(grid, row, col));
};

export const getBodyCell = (grid, row, col) => {
const physicalItems = getPhysicalItems(grid);
const physicalRow = physicalItems.find((item) => item.index === row);
const cells = getRowCells(physicalRow);
return getCellContent(cells[col]);
return cells[col];
};

export const getBodyCellContent = (grid, row, col) => {
return getCellContent(getBodyCell(grid, row, col));
};

export const fire = (type, detail, options) => {
Expand Down
3 changes: 3 additions & 0 deletions packages/grid/test/selectable-provider-lit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import '../theme/lumo/lit-all-imports.js';
import '../src/lit-all-imports.js';
import './selectable-provider.common.js';
2 changes: 2 additions & 0 deletions packages/grid/test/selectable-provider-polymer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import '../all-imports.js';
import './selectable-provider.common.js';
Loading

0 comments on commit 2bae93a

Please sign in to comment.