carbon-components
Version:
Carbon Components is a component library for IBM Cloud
301 lines (250 loc) • 10.7 kB
JavaScript
import settings from '../../globals/js/settings';
import mixin from '../../globals/js/misc/mixin';
import createComponent from '../../globals/js/mixins/create-component';
import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
import eventedState from '../../globals/js/mixins/evented-state';
import eventMatches from '../../globals/js/misc/event-matches';
class DataTableV2 extends mixin(createComponent, initComponentBySearch, eventedState) {
/**
* Data Table
* @extends CreateComponent
* @extends InitComponentBySearch
* @extends EventedState
* @param {HTMLElement} element The root element of tables
* @param {Object} [options] the... options
* @param {string} [options.selectorInit] selector initialization
* @param {string} [options.selectorExpandCells] css selector for expand
* @param {string} [options.expandableRow] css selector for expand
* @param {string} [options.selectorParentRows] css selector for rows housing expansion
* @param {string} [options.selectorTableBody] root css for table body
* @param {string} [options.eventTrigger] selector for event bubble capture points
* @param {string} [options.eventParentContainer] used find the bubble container
*/
constructor(element, options) {
super(element, options);
this.container = element.parentNode;
this.toolbarEl = this.element.querySelector(this.options.selectorToolbar);
this.batchActionEl = this.element.querySelector(this.options.selectorActions);
this.countEl = this.element.querySelector(this.options.selectorCount);
this.cancelEl = this.element.querySelector(this.options.selectorActionCancel);
this.tableHeaders = this.element.querySelectorAll('th');
this.tableBody = this.element.querySelector(this.options.selectorTableBody);
this.expandCells = [];
this.expandableRows = [];
this.parentRows = [];
this.refreshRows();
this.element.addEventListener('mouseover', evt => {
const eventElement = eventMatches(evt, this.options.selectorChildRow);
if (eventElement) {
this._expandableHoverToggle(eventElement, true);
}
});
this.element.addEventListener('click', evt => {
const eventElement = eventMatches(evt, this.options.eventTrigger);
if (eventElement) {
this._toggleState(eventElement, evt);
}
});
this.element.addEventListener('keydown', this._keydownHandler);
this.state = {
checkboxCount: 0,
};
}
_sortToggle = detail => {
const { element, previousValue } = detail;
[...this.tableHeaders].forEach(header => {
const sortEl = header.querySelector(this.options.selectorTableSort);
if (sortEl !== null && sortEl !== element) {
sortEl.classList.remove(this.options.classTableSortActive);
sortEl.classList.remove(this.options.classTableSortAscending);
}
});
if (!previousValue || previousValue === 'descending') {
element.dataset.previousValue = 'ascending';
element.classList.add(this.options.classTableSortActive);
element.classList.add(this.options.classTableSortAscending);
} else {
element.dataset.previousValue = 'descending';
element.classList.add(this.options.classTableSortActive);
element.classList.remove(this.options.classTableSortAscending);
}
};
_selectToggle = detail => {
const { element } = detail;
const checked = element.checked;
// increment the count
this.state.checkboxCount += checked ? 1 : -1;
this.countEl.textContent = this.state.checkboxCount;
const row = element.parentNode.parentNode;
row.classList.toggle(this.options.classTableSelected);
// toggle on/off batch action bar
this._actionBarToggle(this.state.checkboxCount > 0);
};
_selectAllToggle = detail => {
const checked = detail.element.checked;
const inputs = [...this.element.querySelectorAll(this.options.selectorCheckbox)];
this.state.checkboxCount = checked ? inputs.length - 1 : 0;
inputs.forEach(item => {
item.checked = checked;
const row = item.parentNode.parentNode;
if (checked && row) {
row.classList.add(this.options.classTableSelected);
} else {
row.classList.remove(this.options.classTableSelected);
}
});
this._actionBarToggle(this.state.checkboxCount > 0);
if (this.batchActionEl) {
this.countEl.textContent = this.state.checkboxCount;
}
};
_actionBarCancel = () => {
const inputs = [...this.element.querySelectorAll(this.options.selectorCheckbox)];
const row = [...this.element.querySelectorAll(this.options.selectorTableSelected)];
row.forEach(item => {
item.classList.remove(this.options.classTableSelected);
});
inputs.forEach(item => {
item.checked = false;
});
this.state.checkboxCount = 0;
this._actionBarToggle(false);
if (this.batchActionEl) {
this.countEl.textContent = this.state.checkboxCount;
}
};
_actionBarToggle = toggleOn => {
const transition = evt => {
this.batchActionEl.removeEventListener('transitionend', transition);
if (evt.target.matches(this.options.selectorActions)) {
if (this.batchActionEl.dataset.active === 'false') {
this.batchActionEl.setAttribute('tabIndex', -1);
} else {
this.batchActionEl.setAttribute('tabIndex', 0);
}
}
};
if (toggleOn) {
this.batchActionEl.dataset.active = true;
this.batchActionEl.classList.add(this.options.classActionBarActive);
} else if (this.batchActionEl) {
this.batchActionEl.dataset.active = false;
this.batchActionEl.classList.remove(this.options.classActionBarActive);
}
if (this.batchActionEl) {
this.batchActionEl.addEventListener('transitionend', transition);
}
};
_expandableRowsInit = expandableRows => {
expandableRows.forEach(item => {
item.classList.remove(this.options.classExpandableRowHidden);
this.tableBody.removeChild(item);
});
};
_rowExpandToggle = detail => {
const element = detail.element;
const parent = eventMatches(detail.initialEvt, this.options.eventParentContainer);
const index = this.expandCells.indexOf(element);
if (element.dataset.previousValue === undefined || element.dataset.previousValue === 'expanded') {
element.dataset.previousValue = 'collapsed';
parent.classList.add(this.options.classExpandableRow);
this.tableBody.insertBefore(this.expandableRows[index], this.parentRows[index + 1]);
} else {
parent.classList.remove(this.options.classExpandableRow);
this.tableBody.removeChild(parent.nextElementSibling);
element.dataset.previousValue = 'expanded';
}
};
_expandableHoverToggle = element => {
element.previousElementSibling.classList.add(this.options.classExpandableRowHover);
const mouseout = () => {
element.previousElementSibling.classList.remove(this.options.classExpandableRowHover);
element.removeEventListener('mouseout', mouseout);
};
element.addEventListener('mouseout', mouseout);
};
_toggleState = (element, evt) => {
const data = element.dataset;
const label = data.label ? data.label : '';
const previousValue = data.previousValue ? data.previousValue : '';
const initialEvt = evt;
this.changeState({
group: data.event,
element,
label,
previousValue,
initialEvt,
});
};
_keydownHandler = evt => {
if (evt.which === 27) {
this._actionBarCancel();
}
};
_changeState(detail, callback) {
this[this.constructor.eventHandlers[detail.group]](detail);
callback();
}
refreshRows = () => {
const newExpandCells = [...this.element.querySelectorAll(this.options.selectorExpandCells)];
const newExpandableRows = [...this.element.querySelectorAll(this.options.selectorExpandableRows)];
const newParentRows = [...this.element.querySelectorAll(this.options.selectorParentRows)];
// check if this is a refresh or the first time
if (this.parentRows.length > 0) {
const diffParentRows = newParentRows.filter(newRow => !this.parentRows.some(oldRow => oldRow === newRow));
// check if there are expandable rows
if (newExpandableRows.length > 0) {
const diffExpandableRows = diffParentRows.map(newRow => newRow.nextElementSibling);
const mergedExpandableRows = [...this.expandableRows, ...diffExpandableRows];
this._expandableRowsInit(diffExpandableRows);
this.expandableRows = mergedExpandableRows;
}
} else if (newExpandableRows.length > 0) {
this._expandableRowsInit(newExpandableRows);
this.expandableRows = newExpandableRows;
}
this.expandCells = newExpandCells;
this.parentRows = newParentRows;
};
static components = new WeakMap();
// UI Events
static eventHandlers = {
expand: '_rowExpandToggle',
sort: '_sortToggle',
select: '_selectToggle',
'select-all': '_selectAllToggle',
'action-bar-cancel': '_actionBarCancel',
};
static get options() {
const { prefix } = settings;
return {
selectorInit: '[data-table-v2]',
selectorToolbar: `.${prefix}--table--toolbar`,
selectorActions: `.${prefix}--batch-actions`,
selectorCount: '[data-items-selected]',
selectorActionCancel: `.${prefix}--batch-summary__cancel`,
selectorCheckbox: `.${prefix}--checkbox`,
selectorExpandCells: `.${prefix}--table-expand-v2`,
selectorExpandableRows: `.${prefix}--expandable-row-v2`,
selectorParentRows: `.${prefix}--parent-row-v2`,
selectorChildRow: '[data-child-row]',
selectorTableBody: 'tbody',
selectorTableSort: `.${prefix}--table-sort-v2`,
selectorTableSelected: `.${prefix}--data-table-v2--selected`,
classExpandableRow: `${prefix}--expandable-row-v2`,
classExpandableRowHidden: `${prefix}--expandable-row--hidden-v2`,
classExpandableRowHover: `${prefix}--expandable-row--hover-v2`,
classTableSortAscending: `${prefix}--table-sort-v2--ascending`,
classTableSortActive: `${prefix}--table-sort-v2--active`,
classActionBarActive: `${prefix}--batch-actions--active`,
classTableSelected: `${prefix}--data-table-v2--selected`,
eventBeforeExpand: 'data-table-v2-beforetoggleexpand',
eventAfterExpand: 'data-table-v2-aftertoggleexpand',
eventBeforeSort: 'data-table-v2-beforetogglesort',
eventAfterSort: 'data-table-v2-aftertogglesort',
eventTrigger: '[data-event]',
eventParentContainer: '[data-parent-row]',
};
}
}
export default DataTableV2;