package.src.vaadin-grid-column-group-mixin.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grid Show documentation
Show all versions of grid Show documentation
A free, flexible and high-quality Web Component for showing large amounts of tabular data
/**
* @license
* Copyright (c) 2016 - 2023 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
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 { ColumnObserver, updateColumnOrders } from './vaadin-grid-helpers.js';
/**
* A mixin providing common vaadin-grid-column-group functionality.
*
* @polymerMixin
* @mixes ColumnBaseMixin
*/
export const GridColumnGroupMixin = (superClass) =>
class extends ColumnBaseMixin(superClass) {
static get properties() {
return {
/** @private */
_childColumns: {
value() {
return this._getChildColumns(this);
},
},
/**
* Flex grow ratio for the column group as the sum of the ratios of its child columns.
* @attr {number} flex-grow
*/
flexGrow: {
type: Number,
readOnly: true,
sync: true,
},
/**
* Width of the column group as the sum of the widths of its child columns.
*/
width: {
type: String,
readOnly: true,
},
/** @private */
_visibleChildColumns: Array,
/** @private */
_colSpan: Number,
/** @private */
_rootColumns: Array,
};
}
static get observers() {
return [
'_groupFrozenChanged(frozen, _rootColumns)',
'_groupFrozenToEndChanged(frozenToEnd, _rootColumns)',
'_groupHiddenChanged(hidden)',
'_colSpanChanged(_colSpan, _headerCell, _footerCell)',
'_groupOrderChanged(_order, _rootColumns)',
'_groupReorderStatusChanged(_reorderStatus, _rootColumns)',
'_groupResizableChanged(resizable, _rootColumns)',
];
}
/** @protected */
connectedCallback() {
super.connectedCallback();
this._addNodeObserver();
this._updateFlexAndWidth();
}
/** @protected */
disconnectedCallback() {
super.disconnectedCallback();
if (this._observer) {
this._observer.disconnect();
}
}
/**
* @param {string} path
* @param {unknown=} value
* @protected
*/
_columnPropChanged(path, value) {
if (path === 'hidden') {
// Prevent synchronization of the hidden state to child columns.
// If the group is currently auto-hidden, and one column is made visible,
// we don't want the other columns to become visible as well.
this._preventHiddenSynchronization = true;
this._updateVisibleChildColumns(this._childColumns);
this._preventHiddenSynchronization = false;
}
if (/flexGrow|width|hidden|_childColumns/u.test(path)) {
this._updateFlexAndWidth();
}
// Don't unfreeze the frozen group because of a non-frozen child
if (path === 'frozen' && !this.frozen) {
this.frozen = value;
}
// Don't unfreeze the frozen group because of a non-frozen child
if (path === 'lastFrozen' && !this._lastFrozen) {
this._lastFrozen = value;
}
// Don't unfreeze the frozen group because of a non-frozen child
if (path === 'frozenToEnd' && !this.frozenToEnd) {
this.frozenToEnd = value;
}
// Don't unfreeze the frozen group because of a non-frozen child
if (path === 'firstFrozenToEnd' && !this._firstFrozenToEnd) {
this._firstFrozenToEnd = value;
}
}
/** @private */
_groupOrderChanged(order, rootColumns) {
if (rootColumns) {
const _rootColumns = rootColumns.slice(0);
if (!order) {
_rootColumns.forEach((column) => {
column._order = 0;
});
return;
}
// The parent column order number cascades downwards to it's children
// so that the resulting order numbering constructs as follows:
// [ 1000 ]
// [ 1100 ] | [ 1200 ]
// [1110] | [1120] | [1210] | [1220]
// Trailing zeros are counted so we know the level on which we're working on.
const trailingZeros = /(0+)$/u.exec(order).pop().length; // NOSONAR
// In an unlikely situation where a group has more than 9 child columns,
// the child scope must have 1 digit less...
// Log^a_b = Ln(a)/Ln(b)
// Number of digits of a number is equal to floor(Log(number)_10) + 1
const childCountDigits = ~~(Math.log(rootColumns.length) / Math.LN10) + 1;
// Final scope for the child columns needs to mind both factors.
const scope = 10 ** (trailingZeros - childCountDigits);
if (_rootColumns[0] && _rootColumns[0]._order) {
_rootColumns.sort((a, b) => a._order - b._order);
}
updateColumnOrders(_rootColumns, scope, order);
}
}
/** @private */
_groupReorderStatusChanged(reorderStatus, rootColumns) {
if (reorderStatus === undefined || rootColumns === undefined) {
return;
}
rootColumns.forEach((column) => {
column._reorderStatus = reorderStatus;
});
}
/** @private */
_groupResizableChanged(resizable, rootColumns) {
if (resizable === undefined || rootColumns === undefined) {
return;
}
rootColumns.forEach((column) => {
column.resizable = resizable;
});
}
/** @private */
_updateVisibleChildColumns(childColumns) {
this._visibleChildColumns = Array.prototype.filter.call(childColumns, (col) => !col.hidden);
this._colSpan = this._visibleChildColumns.length;
this._updateAutoHidden();
}
/** @protected */
_updateFlexAndWidth() {
if (!this._visibleChildColumns) {
return;
}
if (this._visibleChildColumns.length > 0) {
const width = this._visibleChildColumns
.reduce((prev, curr) => {
prev += ` + ${(curr.width || '0px').replace('calc', '')}`;
return prev;
}, '')
.substring(3);
this._setWidth(`calc(${width})`);
} else {
this._setWidth('0px');
}
this._setFlexGrow(
Array.prototype.reduce.call(this._visibleChildColumns, (prev, curr) => prev + curr.flexGrow, 0),
);
}
/**
* This method is called before the group's frozen value is being propagated to the child columns.
* In case some of the child columns are frozen, while others are not, the non-frozen ones
* will get automatically frozen as well. As this may sometimes be unintended, this method
* shows a warning in the console in such cases.
* @private
*/
__scheduleAutoFreezeWarning(columns, frozenProp) {
if (this._grid) {
// Derive the attribute name from the property name
const frozenAttr = frozenProp.replace(/([A-Z])/gu, '-$1').toLowerCase();
// Check if all the columns have the same frozen value
const firstColumnFrozen = columns[0][frozenProp] || columns[0].hasAttribute(frozenAttr);
const allSameFrozen = columns.every((column) => {
return (column[frozenProp] || column.hasAttribute(frozenAttr)) === firstColumnFrozen;
});
if (!allSameFrozen) {
// Some of the child columns are frozen, some are not. Show a warning.
this._grid.__autoFreezeWarningDebouncer = Debouncer.debounce(
this._grid.__autoFreezeWarningDebouncer,
animationFrame,
() => {
console.warn(
`WARNING: Joining ${frozenProp} and non-${frozenProp} Grid columns inside the same column group! ` +
`This will automatically freeze all the joined columns to avoid rendering issues. ` +
`If this was intentional, consider marking each joined column explicitly as ${frozenProp}. ` +
`Otherwise, exclude the ${frozenProp} columns from the joined group.`,
);
},
);
}
}
}
/** @private */
_groupFrozenChanged(frozen, rootColumns) {
if (rootColumns === undefined || frozen === undefined) {
return;
}
// Don't propagate the default `false` value.
if (frozen !== false) {
this.__scheduleAutoFreezeWarning(rootColumns, 'frozen');
Array.from(rootColumns).forEach((col) => {
col.frozen = frozen;
});
}
}
/** @private */
_groupFrozenToEndChanged(frozenToEnd, rootColumns) {
if (rootColumns === undefined || frozenToEnd === undefined) {
return;
}
// Don't propagate the default `false` value.
if (frozenToEnd !== false) {
this.__scheduleAutoFreezeWarning(rootColumns, 'frozenToEnd');
Array.from(rootColumns).forEach((col) => {
col.frozenToEnd = frozenToEnd;
});
}
}
/** @private */
_groupHiddenChanged(hidden) {
// When initializing the hidden property, only sync hidden state to columns
// if group is actually hidden. Otherwise, we could override a hidden column
// to be visible.
// We always want to run this though if the property is actually changed.
if (hidden || this.__groupHiddenInitialized) {
this._synchronizeHidden();
}
this.__groupHiddenInitialized = true;
}
/** @private */
_updateAutoHidden() {
const wasAutoHidden = this._autoHidden;
this._autoHidden = (this._visibleChildColumns || []).length === 0;
// Only modify hidden state if group was auto-hidden, or becomes auto-hidden
if (wasAutoHidden || this._autoHidden) {
this.hidden = this._autoHidden;
}
}
/** @private */
_synchronizeHidden() {
if (this._childColumns && !this._preventHiddenSynchronization) {
this._childColumns.forEach((column) => {
column.hidden = this.hidden;
});
}
}
/** @private */
_colSpanChanged(colSpan, headerCell, footerCell) {
if (headerCell) {
headerCell.setAttribute('colspan', colSpan);
if (this._grid) {
this._grid._a11yUpdateCellColspan(headerCell, colSpan);
}
}
if (footerCell) {
footerCell.setAttribute('colspan', colSpan);
if (this._grid) {
this._grid._a11yUpdateCellColspan(footerCell, colSpan);
}
}
}
/**
* @param {!GridColumnGroup} el
* @return {!Array}
* @protected
*/
_getChildColumns(el) {
return ColumnObserver.getColumns(el);
}
/** @private */
_addNodeObserver() {
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();
}
/**
* @param {!Node} node
* @return {boolean}
* @protected
*/
_isColumnElement(node) {
return node.nodeType === Node.ELEMENT_NODE && /\bcolumn\b/u.test(node.localName);
}
};