Skip to content

Commit

Permalink
refactor: replace flattened nodes observer with grid column observer (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tomivirkki authored Oct 24, 2023
1 parent 2767402 commit c99bf7b
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 51 deletions.
36 changes: 15 additions & 21 deletions packages/grid/src/vaadin-grid-column-group-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
* Copyright (c) 2016 - 2023 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
import { animationFrame } from '@vaadin/component-base/src/async.js';
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
import { ColumnBaseMixin } from './vaadin-grid-column-mixin.js';
import { updateColumnOrders } from './vaadin-grid-helpers.js';
import { ColumnObserver, updateColumnOrders } from './vaadin-grid-helpers.js';

/**
* A mixin providing common vaadin-grid-column-group functionality.
Expand Down Expand Up @@ -331,29 +330,24 @@ export const GridColumnGroupMixin = (superClass) =>
* @protected
*/
_getChildColumns(el) {
return FlattenedNodesObserver.getFlattenedNodes(el).filter(this._isColumnElement);
return ColumnObserver.getColumns(el);
}

/** @private */
_addNodeObserver() {
this._observer = new FlattenedNodesObserver(this, (info) => {
if (
info.addedNodes.filter(this._isColumnElement).length > 0 ||
info.removedNodes.filter(this._isColumnElement).length > 0
) {
// Prevent synchronization of the hidden state to child columns.
// If the group is currently auto-hidden, and a visible column is added,
// we don't want the other columns to become visible as well.
this._preventHiddenSynchronization = true;
this._rootColumns = this._getChildColumns(this);
this._childColumns = this._rootColumns;
this._updateVisibleChildColumns(this._childColumns);
this._preventHiddenSynchronization = false;

// Update the column tree
if (this._grid && this._grid._debounceUpdateColumnTree) {
this._grid._debounceUpdateColumnTree();
}
this._observer = new ColumnObserver(this, () => {
// Prevent synchronization of the hidden state to child columns.
// If the group is currently auto-hidden, and a visible column is added,
// we don't want the other columns to become visible as well.
this._preventHiddenSynchronization = true;
this._rootColumns = this._getChildColumns(this);
this._childColumns = this._rootColumns;
this._updateVisibleChildColumns(this._childColumns);
this._preventHiddenSynchronization = false;

// Update the column tree
if (this._grid && this._grid._debounceUpdateColumnTree) {
this._grid._debounceUpdateColumnTree();
}
});
this._observer.flush();
Expand Down
26 changes: 11 additions & 15 deletions packages/grid/src/vaadin-grid-dynamic-columns-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
* Copyright (c) 2016 - 2023 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
import { microTask, timeOut } from '@vaadin/component-base/src/async.js';
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
import { updateCellState } from './vaadin-grid-helpers.js';
import { ColumnObserver, updateCellState } from './vaadin-grid-helpers.js';

function arrayEquals(arr1, arr2) {
if (!arr1 || !arr2 || arr1.length !== arr2.length) {
Expand Down Expand Up @@ -58,7 +57,7 @@ export const DynamicColumnsMixin = (superClass) =>
* @protected
*/
_getChildColumns(el) {
return FlattenedNodesObserver.getFlattenedNodes(el).filter(this._isColumnElement);
return ColumnObserver.getColumns(el);
}

/** @private */
Expand All @@ -77,7 +76,7 @@ export const DynamicColumnsMixin = (superClass) =>

/** @private */
_getColumnTree() {
const rootColumns = FlattenedNodesObserver.getFlattenedNodes(this).filter(this._isColumnElement);
const rootColumns = ColumnObserver.getColumns(this);
const columnTree = [rootColumns];

let c = rootColumns;
Expand Down Expand Up @@ -116,17 +115,14 @@ export const DynamicColumnsMixin = (superClass) =>

/** @private */
_addNodeObserver() {
this._observer = new FlattenedNodesObserver(this, (info) => {
const hasColumnElements = (nodeCollection) => nodeCollection.filter(this._isColumnElement).length > 0;
if (hasColumnElements(info.addedNodes) || hasColumnElements(info.removedNodes)) {
const allRemovedCells = info.removedNodes.flatMap((c) => c._allCells);
const filterNotConnected = (element) =>
allRemovedCells.filter((cell) => cell && cell._content.contains(element)).length;

this.__removeSorters(this._sorters.filter(filterNotConnected));
this.__removeFilters(this._filters.filter(filterNotConnected));
this._debounceUpdateColumnTree();
}
this._observer = new ColumnObserver(this, (_addedColumns, removedColumns) => {
const allRemovedCells = removedColumns.flatMap((c) => c._allCells);
const filterNotConnected = (element) =>
allRemovedCells.filter((cell) => cell && cell._content.contains(element)).length;

this.__removeSorters(this._sorters.filter(filterNotConnected));
this.__removeFilters(this._filters.filter(filterNotConnected));
this._debounceUpdateColumnTree();

this._debouncerCheckImports = Debouncer.debounce(
this._debouncerCheckImports,
Expand Down
94 changes: 94 additions & 0 deletions packages/grid/src/vaadin-grid-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Copyright (c) 2016 - 2023 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { microTask } from '@vaadin/component-base/src/async.js';
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
import { addValueToAttribute, removeValueFromAttribute } from '@vaadin/component-base/src/dom-utils.js';

/**
Expand Down Expand Up @@ -170,3 +172,95 @@ export function updateCellState(cell, attribute, value, part, oldPart) {
// Add new part to the cell attribute
updatePart(cell, value, part || `${attribute}-cell`);
}

/**
* A helper for observing flattened child column list of an element.
*/
export class ColumnObserver {
constructor(host, callback) {
this.__host = host;
this.__callback = callback;
this.__currentSlots = [];

this.__onMutation = this.__onMutation.bind(this);
this.__observer = new MutationObserver(this.__onMutation);
this.__observer.observe(host, {
childList: true,
});

// The observer callback is invoked once initially.
this.__initialCallDebouncer = Debouncer.debounce(this.__initialCallDebouncer, microTask, () => this.__onMutation());
}

disconnect() {
this.__observer.disconnect();
this.__initialCallDebouncer.cancel();
this.__toggleSlotChangeListeners(false);
}

flush() {
this.__onMutation();
}

__toggleSlotChangeListeners(add) {
this.__currentSlots.forEach((slot) => {
if (add) {
slot.addEventListener('slotchange', this.__onMutation);
} else {
slot.removeEventListener('slotchange', this.__onMutation);
}
});
}

__onMutation() {
// Detect if this is the initial call
const initialCall = !this.__currentColumns;
this.__currentColumns ||= [];

// Detect added and removed columns or if the columns order has changed
const columns = ColumnObserver.getColumns(this.__host);
const addedColumns = columns.filter((column) => !this.__currentColumns.includes(column));
const removedColumns = this.__currentColumns.filter((column) => !columns.includes(column));
const orderChanged = this.__currentColumns.some((column, index) => column !== columns[index]);
this.__currentColumns = columns;

// Update the list of child slots and toggle their slotchange listeners
this.__toggleSlotChangeListeners(false);
this.__currentSlots = [...this.__host.children].filter((child) => child instanceof HTMLSlotElement);
this.__toggleSlotChangeListeners(true);

// Invoke the callback if there are changes in the child columns or if this is the initial call
const invokeCallback = initialCall || addedColumns.length || removedColumns.length || orderChanged;
if (invokeCallback) {
this.__callback(addedColumns, removedColumns);
}
}

/**
* Default filter for column elements.
*/
static __isColumnElement(node) {
return node.nodeType === Node.ELEMENT_NODE && /\bcolumn\b/u.test(node.localName);
}

static getColumns(host) {
const columns = [];

// A temporary workaround for backwards compatibility
const isColumnElement = host._isColumnElement || ColumnObserver.__isColumnElement;

[...host.children].forEach((child) => {
if (isColumnElement(child)) {
// The child is a column element, add it to the list
columns.push(child);
} else if (child instanceof HTMLSlotElement) {
// The child is a slot, add all assigned column elements to the list
[...child.assignedElements({ flatten: true })]
.filter((assignedElement) => isColumnElement(assignedElement))
.forEach((assignedElement) => columns.push(assignedElement));
}
});

return columns;
}
}
Loading

0 comments on commit c99bf7b

Please sign in to comment.