@syncfusion/ej2-treegrid
Version:
Essential JS 2 TreeGrid Component
1,000 lines (999 loc) • 45.8 kB
JavaScript
import { isNullOrUndefined, removeClass } from '@syncfusion/ej2-base';
import { createCheckBox } from '@syncfusion/ej2-buttons';
import { parentsUntil, getObject } from '@syncfusion/ej2-grids';
import * as events from '../base/constant';
import { getParentData, isRemoteData, isCheckboxcolumn } from '../utils';
/**
* TreeGrid Selection module
*
* @hidden
*/
var Selection = /** @class */ (function () {
/**
* Creates an instance of Selection.
*
* @param {TreeGrid} parent - The TreeGrid instance this selection module is associated with.
*/
function Selection(parent) {
this.headerCheckboxFrameEl = null;
this.checkboxColIndexCache = -2;
this.parentSelectionCounters = {};
this.selectedUidMap = new Map(); // Quick lookup for whether an item is selected
this.totalSelectableCount = 0;
this.headerSelectionState = 'uncheck';
this.checkedItemCount = 0;
this.visibleUidIndex = {};
this.parent = parent;
this.selectedItems = [];
this.selectedIndexes = []; // Initialize here
this.filteredList = [];
this.searchingRecords = [];
this.addEventListener();
}
/**
* Gets the module name.
*
* @returns {string} The name of the module ('selection').
*/
Selection.prototype.getModuleName = function () { return 'selection'; };
/**
* Builds a map from visible record uniqueID to its visible index.
* This map is crucial for finding the *current visible index* of a record.
*
* @returns {void}
*/
Selection.prototype.buildVisibleUidMap = function () {
this.visibleUidIndex = {};
var view = this.parent.grid.currentViewData;
if (!view) {
return;
}
for (var i = 0, len = view.length; i < len; i++) {
var rec = view[parseInt(i.toString(), 10)];
if (rec && rec.uniqueID) {
this.visibleUidIndex[rec.uniqueID] = i; // Map uid -> visible row index
}
}
};
/**
* Adds required event listeners for selection handling.
*
* @returns {void}
*/
Selection.prototype.addEventListener = function () {
this.parent.on('dataBoundArg', this.headerCheckbox, this);
this.parent.on('columnCheckbox', this.columnCheckbox, this);
this.parent.on('updateGridActions', this.updateGridActions, this);
this.parent.grid.on('colgroup-refresh', this.headerCheckbox, this);
this.parent.on('checkboxSelection', this.checkboxSelection, this);
};
/**
* Removes previously added event listeners.
*
* @returns {void}
*/
Selection.prototype.removeEventListener = function () {
if (this.parent.isDestroyed) {
return;
}
this.parent.off('dataBoundArg', this.headerCheckbox);
this.parent.off('columnCheckbox', this.columnCheckbox);
this.parent.grid.off('colgroup-refresh', this.headerCheckbox);
this.parent.off('checkboxSelection', this.checkboxSelection);
this.parent.off('updateGridActions', this.updateGridActions);
};
/**
* Destroys the selection module and clears internal caches.
*
* @returns {void}
*/
Selection.prototype.destroy = function () {
this.resetSelectionCaches();
this.removeEventListener();
};
/**
* Handles checkbox click events from the DOM and dispatches selection logic.
*
* @param {Object} args - Event args containing the click target.
* @returns {void}
*/
Selection.prototype.checkboxSelection = function (args) {
var _a;
var target = getObject('target', args);
var checkWrap = parentsUntil(target, 'e-checkbox-wrapper');
var checkBox;
if (checkWrap && checkWrap.querySelectorAll('.e-treecheckselect').length > 0) {
checkBox = checkWrap.querySelector('input[type="checkbox"]');
var rowIndex = [];
if (this.parent.frozenRows) {
rowIndex.push(parseInt(target.closest('tr').getAttribute('aria-rowindex'), 10) - 1);
}
else {
rowIndex.push(target.closest('tr').rowIndex);
}
this.selectCheckboxes(rowIndex);
var newCheckState = checkBox.nextElementSibling.classList.contains('e-check');
this.triggerChkChangeEvent(checkBox, newCheckState, target.closest('tr'));
}
else if (checkWrap && checkWrap.querySelectorAll('.e-treeselectall').length > 0 && this.parent.autoCheckHierarchy) {
var frame = checkWrap.querySelector('.e-frame');
var currentStateIsUncheck = !frame.classList.contains('e-check') && !frame.classList.contains('e-stop');
var targetState = currentStateIsUncheck; // If currently uncheck, target state is to check all.
this.headerSelection(targetState);
checkBox = checkWrap.querySelector('input[type="checkbox"]');
this.triggerChkChangeEvent(checkBox, targetState, target.closest('tr'));
}
if (!isNullOrUndefined(this.parent['parentQuery']) && this.parent.selectionSettings.persistSelection
&& this.parent['columnModel'].filter(function (col) { return col.type === 'checkbox'; }).length > 0
&& isRemoteData(this.parent)) {
if (this.parent['parentQuery'].length > 0) {
(_a = this.parent.query.queries).push.apply(_a, this.parent['parentQuery']);
this.parent['parentQuery'] = [];
}
}
};
/**
* Triggers the checkboxChange event with the appropriate arguments.
*
* @param {HTMLInputElement} checkBox - The checkbox input element that changed.
* @param {boolean} checkState - The new checked state.
* @param {HTMLTableRowElement} rowElement - The row element where the change occurred.
* @returns {void}
*/
Selection.prototype.triggerChkChangeEvent = function (checkBox, checkState, rowElement) {
var data = this.parent.getCurrentViewRecords()[rowElement.rowIndex];
var args = {
checked: checkState, target: checkBox, rowElement: rowElement,
rowData: checkBox.classList.contains('e-treeselectall')
? this.parent.getCheckedRecords() : data
};
this.parent.trigger(events.checkboxChange, args);
};
/**
* Determines the index of the checkbox column in the header.
*
* @returns {number} The index of the checkbox column, or -1 if not found.
*/
Selection.prototype.getCheckboxcolumnIndex = function () {
if (this.checkboxColIndexCache !== -2) {
return this.checkboxColIndexCache;
}
var mappingUid;
var columnIndex = -1;
var stackedHeader = 'stackedHeader';
var columnModel = 'columnModel';
var columns = this.parent["" + stackedHeader] ? this.parent["" + columnModel] : (this.parent.columns);
for (var col = 0; col < columns.length; col++) {
if (columns[parseInt(col.toString(), 10)].showCheckbox) {
mappingUid = columns[parseInt(col.toString(), 10)].uid;
break;
}
}
var headerDivs = this.parent.getHeaderContent().querySelectorAll('.e-headercelldiv');
for (var j = 0; j < headerDivs.length; j++) {
var headercell = headerDivs[parseInt(j.toString(), 10)];
if (headercell.getAttribute('data-mappinguid') === mappingUid) {
columnIndex = j;
break;
}
}
this.checkboxColIndexCache = isNullOrUndefined(columnIndex) ? -1 : columnIndex;
return this.checkboxColIndexCache;
};
/**
* Renders and initializes the header checkbox element.
*
* @returns {void}
*/
Selection.prototype.headerCheckbox = function () {
this.buildVisibleUidMap();
this.totalSelectableCount = this.countSelectableRecords(this.resolveHeaderSelectionList(true)); // Use all flatData for initial count
this.columnIndex = this.getCheckboxcolumnIndex();
if (this.columnIndex > -1) {
var headerElement = this.parent.getHeaderContent().querySelectorAll('.e-headercelldiv')[this.columnIndex];
if (headerElement && headerElement.querySelectorAll('.e-treeselectall').length === 0) {
var value = false; // Initial state can be false.
var rowChkBox = this.parent.createElement('input', { className: 'e-treeselectall', attrs: { 'type': 'checkbox' } });
var checkWrap = createCheckBox(this.parent.createElement, false, { checked: value, label: ' ' });
checkWrap.classList.add('e-hierarchycheckbox');
checkWrap.insertBefore(rowChkBox.cloneNode(), checkWrap.firstChild);
if (!isNullOrUndefined(headerElement)) {
headerElement.insertBefore(checkWrap, headerElement.firstChild);
}
this.headerCheckboxFrameEl = checkWrap.querySelector('.e-frame'); // Assign the frame element
if (this.parent.autoCheckHierarchy) {
this.headerSelection();
} // Update header state based on data
}
else if (headerElement && headerElement.querySelectorAll('.e-treeselectall').length > 0) {
this.headerCheckboxFrameEl = headerElement.querySelector('.e-frame');
if (this.parent.autoCheckHierarchy) {
this.headerSelection();
} // Update status based on current selections
}
}
};
/**
* Renders a checkbox element for a column cell.
*
* @param {QueryCellInfoEventArgs} args - The QueryCellInfoEventArgs for the cell.
* @returns {Element} The rendered checkbox wrapper element.
*/
Selection.prototype.renderColumnCheckbox = function (args) {
var rowChkBox = this.parent.createElement('input', { className: 'e-treecheckselect', attrs: { 'type': 'checkbox', 'aria-label': 'checkbox' } });
var data = args.data;
args.cell.classList.add('e-treegridcheckbox');
args.cell.setAttribute('aria-label', 'checkbox');
var value = (data.checkboxState === 'check');
var checkWrap = createCheckBox(this.parent.createElement, false, { checked: value, label: ' ' });
checkWrap.classList.add('e-hierarchycheckbox');
if (this.parent.allowTextWrap) {
checkWrap.querySelector('.e-frame').style.width = '18px';
}
if (data.checkboxState === 'indeterminate') {
var checkbox = checkWrap.querySelectorAll('.e-frame')[0];
removeClass([checkbox], ['e-check', 'e-stop', 'e-uncheck']);
checkWrap.querySelector('.e-frame').classList.add('e-stop');
}
else if (data.checkboxState === 'uncheck') {
var checkbox = checkWrap.querySelectorAll('.e-frame')[0];
removeClass([checkbox], ['e-check', 'e-stop', 'e-uncheck']);
checkWrap.querySelector('.e-frame').classList.add('e-uncheck');
}
else if (data.checkboxState === 'check') {
var checkbox = checkWrap.querySelectorAll('.e-frame')[0];
removeClass([checkbox], ['e-check', 'e-stop', 'e-uncheck']);
checkWrap.querySelector('.e-frame').classList.add('e-check');
}
checkWrap.insertBefore(rowChkBox.cloneNode(), checkWrap.firstChild);
return checkWrap;
};
/**
* Injects the checkbox into a column cell during QueryCellInfo.
*
* @param {QueryCellInfoEventArgs} container - The cell event args.
* @returns {void}
*/
Selection.prototype.columnCheckbox = function (container) {
var checkWrap = this.renderColumnCheckbox(container);
var containerELe = container.cell.querySelector('.e-treecolumn-container');
if (!isNullOrUndefined(containerELe)) {
if (!container.cell.querySelector('.e-hierarchycheckbox')) {
containerELe.insertBefore(checkWrap, containerELe.querySelectorAll('.e-treecell')[0]);
}
}
else {
var spanEle = this.parent.createElement('span', { className: 'e-treecheckbox' });
var data = container.cell.innerHTML;
container.cell.innerHTML = '';
spanEle.innerHTML = data;
var divEle = this.parent.createElement('div', { className: 'e-treecheckbox-container' });
divEle.appendChild(checkWrap);
divEle.appendChild(spanEle);
container.cell.appendChild(divEle);
}
};
/**
* Selects or toggles checkboxes for the provided row indexes.
*
* @param {number[]} rowIndexes - Array of row indexes to toggle selection for.
* @returns {void}
*/
Selection.prototype.selectCheckboxes = function (rowIndexes) {
for (var i = 0; i < rowIndexes.length; i++) {
var viewRec = this.parent.getCurrentViewRecords()[rowIndexes[parseInt(i.toString(), 10)]];
var flatRec = getParentData(this.parent, viewRec.uniqueID);
var nextState = (flatRec.checkboxState === 'check') ? 'uncheck' : 'check';
flatRec.checkboxState = nextState;
this.traverSelection(flatRec, nextState, false);
}
};
/**
* Traverses selection for a record and cascades selections to children/parents as necessary.
*
* @param {ITreeData} record - The record to process.
* @param {string} checkboxState - The desired checkbox state ('check'|'uncheck'|'indeterminate').
* @param {boolean} isChildItem - True if this invocation is for a child during recursion.
* @returns {void}
*/
Selection.prototype.traverSelection = function (record, checkboxState, isChildItem) {
var previousState = record.checkboxState;
if (!isChildItem) {
this.buildVisibleUidMap();
}
var effectiveChildren = Array.isArray(record.childRecords) ? record.childRecords : [];
if ((!effectiveChildren || effectiveChildren.length === 0) && this.parent.autoCheckHierarchy) {
effectiveChildren = this.getChildrenFromFlat(record);
}
if (this.parent.filterModule && this.parent.filterModule.filteredResult.length > 0
&& effectiveChildren && effectiveChildren.length) {
effectiveChildren = this.getFilteredChildRecords(effectiveChildren);
}
if (!this.parent.autoCheckHierarchy || !effectiveChildren || effectiveChildren.length === 0) {
this.updateSelectedItems(record, checkboxState);
if (!isChildItem) {
if (record.parentItem && this.parent.autoCheckHierarchy) {
this.updateParentSelection(record.parentItem);
}
this.updateSelectedCollectionsAfterBulk(this.resolveHeaderSelectionList(), '');
this.refreshVisibleCheckboxes();
if (this.parent.autoCheckHierarchy) {
this.updateHeaderCheckboxState();
}
}
return;
}
var childCount = 0;
var checkedCount = 0;
var indeterminateCount = 0;
for (var i = 0; i < effectiveChildren.length; i++) {
var child = effectiveChildren[parseInt(i.toString(), 10)];
if (!child || child.isSummaryRow) {
continue;
}
childCount++;
this.updateSelectedItems(child, checkboxState, true);
if (child.hasChildRecords) {
this.traverSelection(child, checkboxState, true);
}
if (child.checkboxState === 'check') {
checkedCount++;
}
else if (child.checkboxState === 'indeterminate') {
indeterminateCount++;
}
}
if (record.uniqueID) {
this.parentSelectionCounters[record.uniqueID] = {
total: childCount,
checked: checkedCount,
indeterminate: indeterminateCount
};
}
var summary = this.parentSelectionCounters[record.uniqueID];
var finalState = this.deriveParentState(record, summary);
if (checkboxState === 'check' && summary.total > 0 && summary.checked === summary.total && summary.indeterminate === 0) {
finalState = 'check';
}
this.updateSelectedItems(record, finalState);
if (!isChildItem && record.parentItem && this.parent.autoCheckHierarchy) {
this.updateParentSelection(record.parentItem, previousState, finalState);
}
if (!isChildItem) {
var bulkList = this.resolveHeaderSelectionList();
this.updateSelectedCollectionsAfterBulk(bulkList, ''); // This will rebuild selectedItems & selectedIndexes based on total state
this.refreshVisibleCheckboxes();
this.updateHeaderCheckboxState();
}
};
/**
* Filters provided child records against the current filter result.
*
* @param {ITreeData[]} childRecords - The array of child records to filter.
* @returns {ITreeData[]} The filtered child records array.
*/
Selection.prototype.getFilteredChildRecords = function (childRecords) {
var _this = this;
var filteredChildRecords = childRecords.filter(function (e) {
return _this.parent.filterModule.filteredResult.indexOf(e) > -1;
});
return filteredChildRecords;
};
/**
* Derives children for a record from flatData using the parentItem link.
* Used when childRecords is missing or empty.
*
* @param {ITreeData} record - The record for which to find child elements.
* @returns {ITreeData[]} An array of child records derived from flatData.
*/
Selection.prototype.getChildrenFromFlat = function (record) {
var all = (this.parent.flatData);
if (!all || !record) {
return [];
}
var pid = record.uniqueID;
var out = [];
for (var i = 0; i < all.length; i++) {
var r = all[parseInt(i.toString(), 10)];
if (!r || r.isSummaryRow) {
continue;
}
var p = r.parentItem;
if (p && p.uniqueID === pid) {
out.push(r);
}
}
return out;
};
/**
* Updates parent selection by rebuilding summary and applying deltas, then bubbling up if required.
*
* @param {ITreeData} parentRecord - The parent record reference.
* @param {string} [previousChildState] - Previous state of the child that changed.
* @param {string} [nextChildState] - Next state of the child that changed.
* @returns {void}
*/
Selection.prototype.updateParentSelection = function (parentRecord, previousChildState, nextChildState) {
var parent = getParentData(this.parent, parentRecord.uniqueID);
if (!parent) {
return;
}
var summary = this.buildSelectionSummary(parent);
if (previousChildState) {
this.applySummaryDelta(summary, previousChildState, -1);
}
if (nextChildState) {
this.applySummaryDelta(summary, nextChildState, 1);
}
if (parent.uniqueID) {
this.parentSelectionCounters[parent.uniqueID] = summary;
}
var desiredState = this.deriveParentState(parent, summary);
if (parent.checkboxState === desiredState) {
return;
}
var parentPrev = parent.checkboxState;
parent.checkboxState = desiredState;
this.updateSelectedItems(parent, desiredState);
if (parent.parentItem) {
this.updateParentSelection(parent.parentItem, parentPrev, desiredState);
}
};
/**
* Builds a selection summary for a record's children.
*
* @param {Object} record - The record whose children should be summarized.
* @param {boolean} [ignoreFilter] - If true, ignore current filter when computing summary.
* @returns {{ total: number, checked: number, indeterminate: number }} The computed summary.
*/
Selection.prototype.buildSelectionSummary = function (record, ignoreFilter) {
var summary = { total: 0, checked: 0, indeterminate: 0 };
var children = [];
if (record && Array.isArray(record.childRecords) && record.childRecords.length) {
children = record.childRecords;
}
else {
children = this.getChildrenFromFlat(record);
}
if (!ignoreFilter && this.parent.filterModule && this.parent.filterModule.filteredResult.length > 0) {
children = this.getFilteredChildRecords(children);
}
for (var i = 0; i < children.length; i++) {
var child = children[parseInt(i.toString(), 10)];
if (!child || child.isSummaryRow) {
continue;
}
summary.total++;
if (child.checkboxState === 'check') {
summary.checked++;
}
else if (child.checkboxState === 'indeterminate') {
summary.indeterminate++;
}
}
return summary;
};
/**
* Applies a delta to a selection summary based on a state change.
*
* @param {Object} summary - The summary to modify. Object with numeric properties: total, checked, indeterminate.
* @param {string} state - The state that changed ('check' | 'indeterminate').
* @param {number} delta - The delta to apply (e.g. +1 or -1).
* @returns {void}
*/
Selection.prototype.applySummaryDelta = function (summary, state, delta) {
if (state === 'check') {
summary.checked = Math.max(0, summary.checked + delta);
}
else if (state === 'indeterminate') {
summary.indeterminate = Math.max(0, summary.indeterminate + delta);
}
};
/**
* Derives the parent's checkbox state based on children summary counts.
*
* @param {ITreeData} record The parent record.
* @param {{ total: number, checked: number, indeterminate: number }} summary The children summary.
* @returns {'check'|'indeterminate'|'uncheck'} The derived checkbox state.
*/
Selection.prototype.deriveParentState = function (record, summary) {
var total = summary.total;
var checked = summary.checked;
var indeterminate = summary.indeterminate;
if (indeterminate > 0 || (checked > 0 && checked !== total)) {
return 'indeterminate';
}
if (checked === total && total > 0) {
return 'check';
}
return 'uncheck';
};
/**
* Handles header checkbox (select all / clear all) behavior.
*
* @param {boolean} [checkAll] - Optional explicit flag to check or uncheck all.
* @returns {void}
*/
Selection.prototype.headerSelection = function (checkAll) {
if (!isNullOrUndefined(this.parent.filterModule) && this.parent.filterModule.filteredResult.length > 0) {
var filterResult = this.parent.filterModule.filteredResult;
if (this.filteredList.length === 0) {
this.filteredList = filterResult;
}
if (this.parent.grid.searchSettings.key.length) {
this.searchingRecords = filterResult;
}
else {
if (this.filteredList !== filterResult && !this.parent.grid.searchSettings.key.length) {
this.filteredList = filterResult;
this.searchingRecords = [];
}
}
}
if (this.searchingRecords.length > 0 && !isNullOrUndefined(checkAll)) {
this.filteredList = this.searchingRecords;
}
else if (this.filteredList.length > 0 && !this.parent.filterSettings.columns.length
&& !this.parent.grid.searchSettings.key.length) {
this.filteredList = [];
}
var records = this.resolveHeaderSelectionList(true);
if (!isNullOrUndefined(checkAll)) {
this.resetSelectionCaches();
var targetState = checkAll ? 'check' : 'uncheck';
this.headerSelectionState = targetState;
this.processHeaderSelection(records, targetState);
this.finalizeParentsAfterBulk(records);
this.updateSelectedCollectionsAfterBulk(records, '');
this.refreshVisibleCheckboxes();
this.updateHeaderCheckboxState();
return;
}
this.totalSelectableCount = this.countSelectableRecords(records);
this.updateHeaderCheckboxState();
};
/**
* Finalizes parent states after a bulk header operation (e.g., Select All).
* This ensures parent states (checked/indeterminate) are correct after cascades.
*
* @param {ITreeData[]} records - The records that were processed in the bulk operation.
* @returns {void}
*/
Selection.prototype.finalizeParentsAfterBulk = function (records) {
var all = records;
for (var i = 0; i < all.length; i++) {
var rec = all[parseInt(i.toString(), 10)];
if (!rec || !rec.hasChildRecords) {
continue;
}
var summary = this.buildSelectionSummary(rec, true);
this.parentSelectionCounters[rec.uniqueID] = summary;
var finalState = this.deriveParentState(rec, summary);
if (this.headerSelectionState === 'check' &&
summary.total > 0 && summary.checked === summary.total && summary.indeterminate === 0) {
finalState = 'check';
}
else if (this.headerSelectionState === 'uncheck') {
finalState = 'uncheck';
}
if (rec.checkboxState !== finalState) {
this.updateSelectedItems(rec, finalState);
}
}
};
/**
* Processes header selection for each record, setting their state silently in the data model.
* Called during bulk operations like "select all".
*
* @param {ITreeData[]} records - The records to process.
* @param {string} targetState - The target state to set on each record.
* @returns {void}
*/
Selection.prototype.processHeaderSelection = function (records, targetState) {
for (var i = 0; i < records.length; i++) {
var record = records[parseInt(i.toString(), 10)];
if (!record) {
continue;
}
var previousState = record.checkboxState;
if (previousState === targetState) {
continue;
}
record.checkboxState = targetState;
this.updateSelectedItems(record, targetState, true);
}
};
/**
* Rebuilds `selectedItems`, `selectedUidMap`, and `selectedIndexes` based on the current data states in the model.
* This method is called after bulk operations (like headerSelection, grid actions, etc.) to synchronize internal collections.
* It ensures `selectedItems` retains original selection order *as much as possible* for currently checked items
* and `selectedIndexes` reflects their *current visible order*.
*
* @param {ITreeData[]} records - The records that were processed (or the full data set if re-evaluating everything).
* @param {string} requestType - The data action type such as filtering, searching, refresh,etc.
* @returns {void}
*/
Selection.prototype.updateSelectedCollectionsAfterBulk = function (records, requestType) {
var hasFilter = !!(this.parent.filterModule && this.parent.filterModule.filteredResult &&
this.parent.filterModule.filteredResult.length);
var hasSearch = !!(this.parent.grid && this.parent.grid.searchSettings &&
this.parent.grid.searchSettings.key && this.parent.grid.searchSettings.key.length);
var isFilterOrSearch = hasFilter || hasSearch || requestType === 'refresh' || requestType === 'searching';
var currentlySelectedItemsInOrder = isFilterOrSearch ? records : this.selectedItems.slice();
var newSelectedItems = [];
var newSelectedUidMap = new Map();
var newSelectedIndexes = [];
for (var _i = 0, currentlySelectedItemsInOrder_1 = currentlySelectedItemsInOrder; _i < currentlySelectedItemsInOrder_1.length; _i++) {
var item = currentlySelectedItemsInOrder_1[_i];
if (item.hasChildRecords && isFilterOrSearch && item.level === 0) {
this.updateParentSelection(item);
}
if (item.uniqueID && item.checkboxState === 'check') {
newSelectedItems.push(item);
newSelectedUidMap.set(item.uniqueID, true);
}
}
if (!isFilterOrSearch) {
var allFlatData = this.parent.flatData;
if (allFlatData) {
for (var _a = 0, allFlatData_1 = allFlatData; _a < allFlatData_1.length; _a++) {
var record = allFlatData_1[_a];
if (!record || record.isSummaryRow) {
continue;
}
if (record.uniqueID && record.checkboxState === 'check' && !newSelectedUidMap.has(record.uniqueID)) {
newSelectedItems.push(record);
newSelectedUidMap.set(record.uniqueID, true);
}
}
}
}
this.selectedItems = newSelectedItems;
this.selectedUidMap = newSelectedUidMap;
this.buildVisibleUidMap();
for (var _b = 0, _c = this.selectedItems; _b < _c.length; _b++) {
var item = _c[_b];
var visibleIdx = this.visibleUidIndex[item.uniqueID];
if (visibleIdx !== undefined) {
newSelectedIndexes.push(visibleIdx);
}
}
this.selectedIndexes = newSelectedIndexes;
this.checkedItemCount = this.selectedItems.length;
this.totalSelectableCount =
this.countSelectableRecords(records);
};
/**
* Refreshes visible checkbox DOM elements to reflect the current data state.
* This method exclusively updates the UI representation of checkboxes.
*
* @returns {void}
*/
Selection.prototype.refreshVisibleCheckboxes = function () {
this.buildVisibleUidMap();
var data = this.parent.getCurrentViewRecords();
var uidMap = this.parent.uniqueIDCollection;
for (var i = 0; data && i < data.length; i++) {
var viewRec = data[parseInt(i.toString(), 10)];
if (!viewRec) {
continue;
}
var uid = viewRec.uniqueID;
var srcRec = (uidMap && uid != null) ? uidMap[String(uid)] : viewRec;
var state = (srcRec && srcRec.checkboxState) ? srcRec.checkboxState : 'uncheck';
var rowEl = null;
var rowUid = viewRec.uid;
if (rowUid) {
rowEl = this.parent.grid.getRowElementByUID(rowUid);
}
if (!rowEl) {
var rows = this.parent.getRows();
rowEl = rows && rows[parseInt(i.toString(), 10)];
if ((this.parent.frozenRows || this.parent.getFrozenColumns()) && !rowEl) {
var movableRows = this.parent.getDataRows();
rowEl = movableRows && movableRows[parseInt(i.toString(), 10)];
}
}
if (rowEl) {
var frame = rowEl.querySelector('.e-hierarchycheckbox .e-frame');
if (frame) {
removeClass([frame], ['e-check', 'e-stop', 'e-uncheck']);
frame.classList.add(state === 'indeterminate' ? 'e-stop' : ('e-' + state));
var input = rowEl.querySelector('.e-treecheckselect');
if (input) {
input.setAttribute('aria-checked', state === 'check' ? 'true' :
(state === 'uncheck' ? 'false' : 'mixed'));
}
}
}
}
};
/**
* Resets internal selection caches to their initial state.
* This is usually called before a bulk selection operation (like "select all").
*
* @returns {void}
*/
Selection.prototype.resetSelectionCaches = function () {
this.parentSelectionCounters = {};
this.selectedUidMap = new Map();
this.selectedItems = [];
this.selectedIndexes = [];
this.totalSelectableCount = 0;
this.headerSelectionState = 'uncheck';
this.checkedItemCount = 0;
};
/**
* Counts selectable (non-summary) records in the provided array.
*
* @param {ITreeData[]} records - The records to count.
* @returns {number} The number of selectable records.
*/
Selection.prototype.countSelectableRecords = function (records) {
var count = 0;
if (!records) {
return count;
}
for (var i = 0; i < records.length; i++) {
var rec = records[parseInt(i.toString(), 10)];
if (rec && !rec.isSummaryRow) {
count++;
}
}
return count;
};
/**
* Resolves the list of records used for header selection operations (e.g., for `select all`).
*
* @param {boolean} [includeAll] - If true and data is local, returns flatData (all records for full dataset actions).
* @returns {ITreeData[]} The array of records to consider for header operations.
*/
Selection.prototype.resolveHeaderSelectionList = function (includeAll) {
var dataToProcess = [];
if (!isRemoteData(this.parent)) {
var hasFilter = !!(this.parent.filterModule &&
this.parent.filterModule.filteredResult &&
this.parent.filterModule.filteredResult.length);
var hasSearch = !!(this.parent.grid &&
this.parent.grid.searchSettings &&
this.parent.grid.searchSettings.key &&
this.parent.grid.searchSettings.key.length);
if (includeAll) {
if (hasFilter) {
dataToProcess = this.filteredList && this.filteredList.length
? this.filteredList
: this.parent.filterModule.filteredResult;
}
else if (hasSearch && this.searchingRecords && this.searchingRecords.length) {
dataToProcess = this.searchingRecords;
}
else {
dataToProcess = this.parent.flatData;
}
}
else {
if (hasFilter) {
dataToProcess = this.filteredList && this.filteredList.length
? this.filteredList
: this.parent.filterModule.filteredResult;
}
else if (hasSearch && this.searchingRecords && this.searchingRecords.length) {
dataToProcess = this.searchingRecords;
}
else {
dataToProcess = this.parent.flatData;
}
}
}
else {
dataToProcess = this.parent.getCurrentViewRecords();
}
return dataToProcess;
};
/**
* Updates the header checkbox state (checked/indeterminate/unchecked) based on current selections.
*
* @returns {void}
*/
Selection.prototype.updateHeaderCheckboxState = function () {
var frame = this.headerCheckboxFrameEl;
if (!frame) {
return;
}
var recordsForHeaderLogic = this.resolveHeaderSelectionList(true);
this.totalSelectableCount = this.countSelectableRecords(recordsForHeaderLogic);
var checkedCountForHeaderLogic = 0;
for (var _i = 0, recordsForHeaderLogic_1 = recordsForHeaderLogic; _i < recordsForHeaderLogic_1.length; _i++) {
var record = recordsForHeaderLogic_1[_i];
if (record && !record.isSummaryRow && record.checkboxState === 'check') {
checkedCountForHeaderLogic++;
}
}
removeClass([frame], ['e-check', 'e-stop', 'e-uncheck']);
if (this.totalSelectableCount === 0) {
frame.classList.add('e-uncheck');
}
else if (checkedCountForHeaderLogic === 0) {
frame.classList.add('e-uncheck');
}
else if (checkedCountForHeaderLogic === this.totalSelectableCount) {
frame.classList.add('e-check');
}
else {
frame.classList.add('e-stop');
}
};
/**
* Updates selection arrays (selectedItems, selectedUidMap, selectedIndexes) and visible DOM for a single record.
* This is the core method for managing the state of a single checkbox.
*
* @param {ITreeData} currentRecord - The record to update.
* @param {string} checkState - The new checkbox state ('check' | 'uncheck' | 'indeterminate').
* @param {boolean} [silent] - If true, update is silent (only updates data model, no collection management or DOM update).
* @returns {void}
*/
Selection.prototype.updateSelectedItems = function (currentRecord, checkState, silent) {
this.buildVisibleUidMap();
var uid = currentRecord.uniqueID;
var uidMap = this.parent.uniqueIDCollection;
var checkboxRecord = (uidMap && uid != null) ? (uidMap[String(uid)] ?
uidMap[String(uid)] : currentRecord) : currentRecord;
var isSummary = currentRecord.isSummaryRow === true;
var previousState = checkboxRecord.checkboxState;
var currentVisibleIndex = this.visibleUidIndex[String(uid)];
checkboxRecord.checkboxState = checkState;
if (silent) {
return;
}
if (!isSummary && previousState !== checkState) {
if (checkState === 'check') {
this.checkedItemCount++;
if (!this.selectedUidMap.has(String(uid))) {
if (checkboxRecord.uniqueID) {
this.selectedUidMap.set(String(checkboxRecord.uniqueID), true);
}
this.selectedItems.push(checkboxRecord);
if (currentVisibleIndex !== undefined && this.selectedIndexes.indexOf(currentVisibleIndex) === -1) {
this.selectedIndexes.push(currentVisibleIndex);
}
}
}
else if (previousState === 'check' || previousState === 'indeterminate') {
if (this.checkedItemCount > 0) {
this.checkedItemCount--;
}
if (checkboxRecord && checkboxRecord.uniqueID && this.selectedUidMap.has(String(checkboxRecord.uniqueID))) {
this.selectedUidMap.delete(String(checkboxRecord.uniqueID));
var itemIdx = this.selectedItems.indexOf(checkboxRecord);
if (itemIdx !== -1) {
this.selectedItems.splice(itemIdx, 1);
}
if (currentVisibleIndex !== undefined) {
var indexInSelectedIndexes = this.selectedIndexes.indexOf(currentVisibleIndex);
if (indexInSelectedIndexes > -1) {
this.selectedIndexes.splice(indexInSelectedIndexes, 1);
}
}
}
}
}
var rowEl = null;
var rowUid = currentRecord.uid;
if (rowUid) {
rowEl = this.parent.grid.getRowElementByUID(rowUid);
}
if (!rowEl) {
var recordVisibleIndex = currentVisibleIndex !== undefined ? currentVisibleIndex : (typeof this.visibleUidIndex[String(uid)] === 'number' ? this.visibleUidIndex[String(uid)] : -1);
if (recordVisibleIndex > -1) {
rowEl = this.parent.getRows()[parseInt(recordVisibleIndex.toString(), 10)];
if (!rowEl && (this.parent.frozenRows || this.parent.getFrozenColumns())) {
rowEl = this.parent.getDataRows()[parseInt(recordVisibleIndex.toString(), 10)];
}
}
}
if (rowEl) {
var frame = rowEl.querySelector('.e-hierarchycheckbox .e-frame');
if (frame) {
removeClass([frame], ['e-check', 'e-stop', 'e-uncheck']);
frame.classList.add(checkState === 'indeterminate' ? 'e-stop' : ('e-' + checkState));
}
var input = rowEl.querySelector('.e-treecheckselect');
if (input) {
input.setAttribute('aria-checked', checkState === 'check' ? 'true' :
(checkState === 'uncheck' ? 'false' : 'mixed'));
}
}
};
/**
* Handles various grid actions and updates selection state accordingly.
* This method ensures that selection state is maintained and UI is refreshed after grid operations.
*
* @param {CellSaveEventArgs} args - Action arguments containing requestType and data.
* @returns {void}
*/
Selection.prototype.updateGridActions = function (args) {
var requestType = args.requestType;
if (isCheckboxcolumn(this.parent)) {
if (this.parent.autoCheckHierarchy) {
if ((requestType === 'sorting' || requestType === 'paging')) {
this.updateSelectedCollectionsAfterBulk(this.resolveHeaderSelectionList(), '');
this.refreshVisibleCheckboxes();
this.updateHeaderCheckboxState();
}
else if (requestType === 'delete' || args.action === 'add') {
var updatedData = [];
if (requestType === 'delete') {
updatedData = args.data;
}
else {
updatedData.push(args.data);
}
for (var i = 0; i < updatedData.length; i++) {
if (requestType === 'delete') {
this.updateSelectedItems(updatedData[parseInt(i.toString(), 10)], 'uncheck', false);
}
if (!isNullOrUndefined(updatedData[parseInt(i.toString(), 10)].parentItem)) {
this.updateParentSelection(updatedData[parseInt(i.toString(), 10)].parentItem);
}
}
this.updateSelectedCollectionsAfterBulk(this.resolveHeaderSelectionList(true), '');
this.refreshVisibleCheckboxes();
if (this.parent.autoCheckHierarchy) {
this.updateHeaderCheckboxState();
}
}
else if (args.requestType === 'add' && this.parent.autoCheckHierarchy) {
args.data.checkboxState = 'uncheck';
}
else if (requestType === 'filtering' || requestType === 'searching' || requestType === 'refresh') {
this.updateSelectedCollectionsAfterBulk(this.resolveHeaderSelectionList(), requestType);
this.refreshVisibleCheckboxes();
if (this.parent.autoCheckHierarchy) {
this.updateHeaderCheckboxState();
}
}
}
else {
if ((requestType === 'filtering' || requestType === 'searching' || requestType === 'refresh' ||
requestType === 'sorting' || requestType === 'paging' || requestType === 'expanding' ||
requestType === 'expand' || requestType === 'collapsing' || requestType === 'collapse') && !isRemoteData(this.parent)) {
this.selectedItems = [];
this.selectedUidMap = new Map();
this.selectedIndexes = [];
this.refreshVisibleCheckboxes();
if (this.parent.autoCheckHierarchy) {
this.updateHeaderCheckboxState();
}
}
}
}
};
/**
* Retrieves checked record objects.
* This array maintains the `ITreeData` objects in the order they were selected.
*
* @returns {ITreeData[]} Array of checked records.
*/
Selection.prototype.getCheckedrecords = function () { return this.selectedItems; };
/**
* Retrieves visible indexes of checked rows in the current view, in the order they were selected.
* This method dynamically generates the list of visible indexes by iterating through `selectedItems`
* (which preserves selection order) and finding their *current* visible index.
*
* @returns {number[]} Array of checked row indexes in selection order.
*/
Selection.prototype.getCheckedRowIndexes = function () {
this.buildVisibleUidMap();
var orderedVisibleIndexes = [];
for (var _i = 0, _a = this.selectedItems; _i < _a.length; _i++) {
var selectedItem = _a[_i];
var uid = selectedItem.uniqueID;
if (uid !== undefined && this.visibleUidIndex[uid] !== undefined) {
orderedVisibleIndexes.push(this.visibleUidIndex[uid]);
}
}
return orderedVisibleIndexes;
};
return Selection;
}());
export { Selection };