devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
612 lines (601 loc) • 27.5 kB
JavaScript
/**
* DevExtreme (cjs/__internal/grids/data_grid/grouping/m_grouping_collapsed.js)
* Version: 24.2.6
* Build date: Mon Mar 17 2025
*
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.GroupingHelper = void 0;
var _errors = require("../../../../common/data/errors");
var _deferred = require("../../../../core/utils/deferred");
var _extend = require("../../../../core/utils/extend");
var _iterator = require("../../../../core/utils/iterator");
var _ui = _interopRequireDefault(require("../../../../ui/widget/ui.errors"));
var _m_core = _interopRequireDefault(require("../m_core"));
var _m_utils = require("../m_utils");
var _m_grouping_core = require("./m_grouping_core");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : {
default: e
}
}
function getContinuationGroupCount(groupOffset, pageSize, groupSize, groupIndex) {
groupIndex = groupIndex || 0;
if (pageSize > 1 && groupSize > 0) {
let pageOffset = groupOffset - Math.floor(groupOffset / pageSize) * pageSize || pageSize;
pageOffset += groupSize - groupIndex - 2;
if (pageOffset < 0) {
pageOffset += pageSize
}
return Math.floor(pageOffset / (pageSize - groupIndex - 1))
}
return 0
}
const foreachExpandedGroups = function(that, callback, updateGroups) {
return that.foreachGroups(((groupInfo, parents) => {
if (groupInfo.isExpanded) {
return callback(groupInfo, parents)
}
}), true, false, updateGroups, updateGroups)
};
const processGroupItems = function(that, items, groupsCount, expandedInfo, path, isCustomLoading, isLastGroupExpanded) {
let isExpanded;
expandedInfo.items = expandedInfo.items || [];
expandedInfo.paths = expandedInfo.paths || [];
expandedInfo.count = expandedInfo.count || 0;
expandedInfo.lastCount = expandedInfo.lastCount || 0;
if (!groupsCount) {
return
}
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (void 0 !== item.items) {
path.push(item.key);
if (isCustomLoading) {
isExpanded = true
} else {
const groupInfo = that.findGroupInfo(path);
isExpanded = groupInfo && groupInfo.isExpanded
}
if (!isExpanded) {
item.collapsedItems = item.items;
item.items = null
} else if (item.items) {
processGroupItems(that, item.items, groupsCount - 1, expandedInfo, path, isCustomLoading, isLastGroupExpanded)
} else if (1 === groupsCount && item.count && (!isCustomLoading || isLastGroupExpanded)) {
expandedInfo.items.push(item);
expandedInfo.paths.push(path.slice(0));
expandedInfo.count += expandedInfo.lastCount;
expandedInfo.lastCount = item.count
}
path.pop()
}
}
};
const updateGroupInfoItem = function(that, item, isLastGroupLevel, path, offset) {
const groupInfo = that.findGroupInfo(path);
let count;
if (!groupInfo) {
if (isLastGroupLevel) {
count = item.count > 0 ? item.count : item.items.length
}
that.addGroupInfo({
isExpanded: that._isGroupExpanded(path.length - 1),
path: path.slice(0),
offset: offset,
count: count || 0
})
} else {
if (isLastGroupLevel) {
groupInfo.count = item.count > 0 ? item.count : item.items && item.items.length || 0
} else {
item.count = groupInfo.count || item.count
}
groupInfo.offset = offset
}
};
const updateGroupInfos = function(that, options, items, loadedGroupCount, groupIndex, path, parentIndex) {
const groupCount = options.group ? options.group.length : 0;
const isLastGroupLevel = groupCount === loadedGroupCount;
const remotePaging = options.remoteOperations.paging;
let offset = 0;
let totalCount = 0;
let count;
groupIndex = groupIndex || 0;
path = path || [];
if (remotePaging && !parentIndex) {
offset = 0 === groupIndex ? options.skip || 0 : options.skips[groupIndex - 1] || 0
}
if (groupIndex >= loadedGroupCount) {
return items.length
}
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item) {
path.push(item.key);
if (!item.count && !item.items || void 0 === item.items) {
return -1
}
updateGroupInfoItem(that, item, isLastGroupLevel, path, offset + i);
count = item.items ? updateGroupInfos(that, options, item.items, loadedGroupCount, groupIndex + 1, path, i) : item.count || -1;
if (count < 0) {
return -1
}
totalCount += count;
path.pop()
}
}
return totalCount
};
const isGroupExpanded = function(groups, groupIndex) {
return groups && groups.length && groups[groupIndex] && !!groups[groupIndex].isExpanded
};
const getTotalOffset = function(groupInfos, pageSize, offset) {
let groupSize;
let totalOffset = offset;
for (let groupIndex = 0; groupIndex < groupInfos.length; groupIndex++) {
groupSize = groupInfos[groupIndex].offset + 1;
if (groupIndex > 0) {
groupSize += groupInfos[groupIndex - 1].childrenTotalCount;
if (pageSize) {
groupSize += getContinuationGroupCount(totalOffset, pageSize, groupSize, groupIndex - 1) * groupIndex
}
}
totalOffset += groupSize
}
return totalOffset
};
function applyContinuationToGroupItem(options, expandedInfo, groupLevel, expandedItemIndex) {
const item = expandedInfo.items[expandedItemIndex];
const skip = options.skips && options.skips[groupLevel];
const take = options.takes && options.takes[groupLevel];
const isLastExpandedItem = expandedItemIndex === expandedInfo.items.length - 1;
const isFirstExpandedItem = 0 === expandedItemIndex;
const lastExpandedItemSkip = isFirstExpandedItem && skip || 0;
const isItemsTruncatedByTake = item.count > take + lastExpandedItemSkip;
if (isFirstExpandedItem && void 0 !== skip) {
item.isContinuation = true
}
if (isLastExpandedItem && void 0 !== take && isItemsTruncatedByTake) {
item.isContinuationOnNextPage = true
}
}
function fillSkipTakeInExpandedInfo(options, expandedInfo, currentGroupCount) {
const currentGroupIndex = currentGroupCount - 1;
const groupCount = options.group ? options.group.length : 0;
expandedInfo.skip = options.skips && options.skips[currentGroupIndex];
if (options.takes && void 0 !== options.takes[currentGroupIndex]) {
if (groupCount === currentGroupCount) {
expandedInfo.take = expandedInfo.count ? expandedInfo.count - (expandedInfo.skip || 0) : 0
} else {
expandedInfo.take = 0
}
expandedInfo.take += options.takes[currentGroupIndex]
}
}
function isDataDeferred(data) {
return !Array.isArray(data)
}
function makeDataDeferred(options) {
if (!isDataDeferred(options.data)) {
options.data = new _deferred.Deferred
}
}
function loadGroupItems(that, options, loadedGroupCount, expandedInfo, groupLevel, data) {
if (!options.isCustomLoading) {
expandedInfo = {};
processGroupItems(that, data, loadedGroupCount, expandedInfo, []);
fillSkipTakeInExpandedInfo(options, expandedInfo, loadedGroupCount)
}
const groupCount = options.group ? options.group.length : 0;
if (expandedInfo.paths.length && groupCount - loadedGroupCount > 0) {
makeDataDeferred(options);
loadExpandedGroups(that, options, expandedInfo, loadedGroupCount, groupLevel, data)
} else if (expandedInfo.paths.length && options.storeLoadOptions.group) {
makeDataDeferred(options);
loadLastLevelGroupItems(that, options, expandedInfo, data)
} else if (isDataDeferred(options.data)) {
options.data.resolve(data)
}
}
function loadExpandedGroups(that, options, expandedInfo, loadedGroupCount, groupLevel, data) {
const groups = options.group || [];
const currentGroup = groups[groupLevel + 1];
const deferreds = [];
(0, _iterator.each)(expandedInfo.paths, (expandedItemIndex => {
var _options$storeLoadOpt;
const loadOptions = {
requireTotalCount: false,
requireGroupCount: true,
group: [currentGroup],
groupSummary: options.storeLoadOptions.groupSummary,
filter: (0, _m_utils.createGroupFilter)(expandedInfo.paths[expandedItemIndex], {
filter: options.storeLoadOptions.filter,
group: groups
}),
select: options.storeLoadOptions.select,
langParams: null === (_options$storeLoadOpt = options.storeLoadOptions) || void 0 === _options$storeLoadOpt ? void 0 : _options$storeLoadOpt.langParams
};
if (0 === expandedItemIndex) {
loadOptions.skip = expandedInfo.skip || 0
}
if (expandedItemIndex === expandedInfo.paths.length - 1) {
loadOptions.take = expandedInfo.take
}
const loadResult = 0 === loadOptions.take ? [] : that._dataSource.loadFromStore(loadOptions);
(0, _deferred.when)(loadResult).done((data => {
const item = expandedInfo.items[expandedItemIndex];
applyContinuationToGroupItem(options, expandedInfo, groupLevel, expandedItemIndex);
item.items = data
}));
deferreds.push(loadResult)
}));
_deferred.when.apply(null, deferreds).done((() => {
updateGroupInfos(that, options, data, loadedGroupCount + 1);
loadGroupItems(that, options, loadedGroupCount + 1, expandedInfo, groupLevel + 1, data)
}))
}
function loadLastLevelGroupItems(that, options, expandedInfo, data) {
const expandedFilters = [];
const groups = options.group || [];
(0, _iterator.each)(expandedInfo.paths, ((_, expandedPath) => {
expandedFilters.push((0, _m_utils.createGroupFilter)(expandedPath, {
group: options.isCustomLoading ? options.storeLoadOptions.group : groups
}))
}));
let {
filter: filter
} = options.storeLoadOptions;
if (!options.storeLoadOptions.isLoadingAll) {
filter = _m_core.default.combineFilters([filter, _m_core.default.combineFilters(expandedFilters, "or")])
}
const loadOptions = (0, _extend.extend)({}, options.storeLoadOptions, {
requireTotalCount: false,
requireGroupCount: false,
group: null,
sort: groups.concat(_m_core.default.normalizeSortingInfo(options.storeLoadOptions.sort || [])),
filter: filter
});
const isPagingLocal = that._dataSource.isLastLevelGroupItemsPagingLocal();
if (!isPagingLocal) {
loadOptions.skip = expandedInfo.skip;
loadOptions.take = expandedInfo.take
}(0, _deferred.when)(0 === expandedInfo.take ? [] : that._dataSource.loadFromStore(loadOptions)).done((items => {
if (isPagingLocal) {
items = that._dataSource.sortLastLevelGroupItems(items, groups, expandedInfo.paths);
items = expandedInfo.skip ? items.slice(expandedInfo.skip) : items;
items = expandedInfo.take ? items.slice(0, expandedInfo.take) : items
}(0, _iterator.each)(expandedInfo.items, ((index, item) => {
const itemCount = item.count - (0 === index && expandedInfo.skip || 0);
const expandedItems = items.splice(0, itemCount);
applyContinuationToGroupItem(options, expandedInfo, groups.length - 1, index);
item.items = expandedItems
}));
options.data.resolve(data)
})).fail(options.data.reject)
}
const loadGroupTotalCount = function(dataSource, options) {
const d = new _deferred.Deferred;
const isGrouping = !!(options.group && options.group.length);
const loadOptions = (0, _extend.extend)({
skip: 0,
take: 1,
requireGroupCount: isGrouping,
requireTotalCount: !isGrouping
}, options, {
group: isGrouping ? options.group : null
});
dataSource.load(loadOptions).done(((data, extra) => {
const count = extra && (isGrouping ? extra.groupCount : extra.totalCount);
if (!isFinite(count)) {
d.reject(_errors.errors.Error(isGrouping ? "E4022" : "E4021"));
return
}
d.resolve(count)
})).fail(d.reject.bind(d));
return d
};
class GroupingHelper extends _m_grouping_core.GroupingHelper {
updateTotalItemsCount(options) {
let totalItemsCount = 0;
const totalCount = options.extra && options.extra.totalCount || 0;
const groupCount = options.extra && options.extra.groupCount || 0;
const pageSize = this._dataSource.pageSize();
const isVirtualPaging = this._isVirtualPaging();
foreachExpandedGroups(this, (groupInfo => {
groupInfo.childrenTotalCount = 0
}));
foreachExpandedGroups(this, ((groupInfo, parents) => {
const totalOffset = getTotalOffset(parents, isVirtualPaging ? 0 : pageSize, totalItemsCount);
let count = groupInfo.count + groupInfo.childrenTotalCount;
if (!isVirtualPaging) {
count += getContinuationGroupCount(totalOffset, pageSize, count, parents.length - 1)
}
if (parents[parents.length - 2]) {
parents[parents.length - 2].childrenTotalCount += count
} else {
totalItemsCount += count
}
}));
super.updateTotalItemsCount(totalItemsCount - totalCount + groupCount)
}
_isGroupExpanded(groupIndex) {
const groups = this._dataSource.group();
return isGroupExpanded(groups, groupIndex)
}
_updatePagingOptions(options, callback) {
const that = this;
const isVirtualPaging = that._isVirtualPaging();
const pageSize = that._dataSource.pageSize();
const skips = [];
const takes = [];
let skipChildrenTotalCount = 0;
let childrenTotalCount = 0;
if (options.take) {
foreachExpandedGroups(this, (groupInfo => {
groupInfo.childrenTotalCount = 0;
groupInfo.skipChildrenTotalCount = 0
}));
foreachExpandedGroups(that, ((groupInfo, parents) => {
let take;
let takeCorrection = 0;
let parentTakeCorrection = 0;
const totalOffset = getTotalOffset(parents, isVirtualPaging ? 0 : pageSize, childrenTotalCount);
let continuationGroupCount = 0;
let skipContinuationGroupCount = 0;
let groupInfoCount = groupInfo.count + groupInfo.childrenTotalCount;
let childrenGroupInfoCount = groupInfoCount;
callback && callback(groupInfo, totalOffset);
const skip = options.skip - totalOffset;
if (totalOffset <= options.skip + options.take && groupInfoCount) {
take = options.take;
if (!isVirtualPaging) {
continuationGroupCount = getContinuationGroupCount(totalOffset, pageSize, groupInfoCount, parents.length - 1);
groupInfoCount += continuationGroupCount * parents.length;
childrenGroupInfoCount += continuationGroupCount;
if (pageSize && skip >= 0) {
takeCorrection = parents.length;
parentTakeCorrection = parents.length - 1;
skipContinuationGroupCount = Math.floor(skip / pageSize)
}
}
if (skip >= 0) {
if (totalOffset + groupInfoCount > options.skip) {
skips.unshift(skip - skipContinuationGroupCount * takeCorrection - groupInfo.skipChildrenTotalCount)
}
if (totalOffset + groupInfoCount >= options.skip + take) {
takes.unshift(take - takeCorrection - groupInfo.childrenTotalCount + groupInfo.skipChildrenTotalCount)
}
} else if (totalOffset + groupInfoCount >= options.skip + take) {
takes.unshift(take + skip - groupInfo.childrenTotalCount)
}
}
if (totalOffset <= options.skip) {
if (parents[parents.length - 2]) {
parents[parents.length - 2].skipChildrenTotalCount += Math.min(childrenGroupInfoCount, skip + 1 - skipContinuationGroupCount * parentTakeCorrection)
} else {
skipChildrenTotalCount += Math.min(childrenGroupInfoCount, skip + 1)
}
}
if (totalOffset <= options.skip + take) {
groupInfoCount = Math.min(childrenGroupInfoCount, skip + take - (skipContinuationGroupCount + 1) * parentTakeCorrection);
if (parents[parents.length - 2]) {
parents[parents.length - 2].childrenTotalCount += groupInfoCount
} else {
childrenTotalCount += groupInfoCount
}
}
}));
options.skip -= skipChildrenTotalCount;
options.take -= childrenTotalCount - skipChildrenTotalCount
}
options.skips = skips;
options.takes = takes
}
changeRowExpand(path) {
const groupInfo = this.findGroupInfo(path);
const dataSource = this._dataSource;
const remoteGroupPaging = dataSource.remoteOperations().groupPaging;
const groups = _m_core.default.normalizeSortingInfo(dataSource.group());
if (groupInfo) {
groupInfo.isExpanded = !groupInfo.isExpanded;
if (remoteGroupPaging && groupInfo.isExpanded && path.length < groups.length) {
return loadGroupTotalCount(dataSource, {
filter: (0, _m_utils.createGroupFilter)(path, {
filter: dataSource.lastLoadOptions().filter,
group: dataSource.group()
}),
group: [groups[path.length]],
select: dataSource.select()
}).done((groupCount => {
groupInfo.count = groupCount
}))
}
return (new _deferred.Deferred).resolve()
}
return (new _deferred.Deferred).reject()
}
handleDataLoading(options) {
const that = this;
const {
storeLoadOptions: storeLoadOptions
} = options;
const groups = _m_core.default.normalizeSortingInfo(storeLoadOptions.group || options.loadOptions.group);
if (options.isCustomLoading || !groups.length) {
return
}
if (options.remoteOperations.grouping) {
const remotePaging = that._dataSource.remoteOperations().paging;
storeLoadOptions.group = _m_core.default.normalizeSortingInfo(storeLoadOptions.group);
storeLoadOptions.group.forEach(((group, index) => {
const isLastGroup = index === storeLoadOptions.group.length - 1;
group.isExpanded = !remotePaging || !isLastGroup
}))
}
options.group = options.group || groups;
if (options.remoteOperations.paging) {
options.skip = storeLoadOptions.skip;
options.take = storeLoadOptions.take;
storeLoadOptions.requireGroupCount = true;
storeLoadOptions.group = groups.slice(0, 1);
that._updatePagingOptions(options);
storeLoadOptions.skip = options.skip;
storeLoadOptions.take = options.take
} else {
options.skip = options.loadOptions.skip;
options.take = options.loadOptions.take;
that._updatePagingOptions(options)
}
}
handleDataLoadedCore(options, callBase) {
const that = this;
const loadedGroupCount = _m_core.default.normalizeSortingInfo(options.storeLoadOptions.group || options.loadOptions.group).length;
const groupCount = options.group ? options.group.length : 0;
let totalCount;
const expandedInfo = {};
if (options.isCustomLoading) {
callBase(options);
processGroupItems(that, options.data, loadedGroupCount, expandedInfo, [], options.isCustomLoading, options.storeLoadOptions.isLoadingAll)
} else {
if (!options.remoteOperations.paging) {
that.foreachGroups((groupInfo => {
groupInfo.count = 0
}))
}
totalCount = updateGroupInfos(that, options, options.data, loadedGroupCount);
if (totalCount < 0) {
options.data = (new _deferred.Deferred).reject(_ui.default.Error("E1037"));
return
}
if (!options.remoteOperations.paging) {
if (loadedGroupCount && options.extra && options.loadOptions.requireTotalCount) {
options.extra.totalCount = totalCount;
options.extra.groupCount = options.data.length
}
}
if (groupCount && options.storeLoadOptions.requireGroupCount && !isFinite(options.extra.groupCount)) {
options.data = (new _deferred.Deferred).reject(_errors.errors.Error("E4022"));
return
}
that.updateTotalItemsCount(options);
if (!options.remoteOperations.paging) {
that._updatePagingOptions(options);
options.lastLoadOptions.skips = options.skips;
options.lastLoadOptions.takes = options.takes
}
callBase(options);
if (!options.remoteOperations.paging) {
that._processPaging(options, loadedGroupCount)
}
}
loadGroupItems(that, options, loadedGroupCount, expandedInfo, 0, options.data)
}
_processSkips(items, skips, groupCount) {
if (!groupCount) {
return
}
const firstItem = items[0];
const skip = skips[0];
const children = firstItem && firstItem.items;
if (void 0 !== skip) {
firstItem.isContinuation = true;
if (children) {
firstItem.items = children.slice(skip);
this._processSkips(firstItem.items, skips.slice(1), groupCount - 1)
}
}
}
_processTakes(items, skips, takes, groupCount, parents) {
if (!groupCount || !items) {
return
}
parents = parents || [];
const lastItem = items[items.length - 1];
let children = lastItem && lastItem.items;
const take = takes[0];
const skip = skips[0];
if (lastItem) {
const maxTakeCount = lastItem.count - (lastItem.isContinuation && skip || 0) || children.length;
if (void 0 !== take && maxTakeCount > take) {
lastItem.isContinuationOnNextPage = true;
parents.forEach((parent => {
parent.isContinuationOnNextPage = true
}));
if (children) {
children = children.slice(0, take);
lastItem.items = children
}
}
parents.push(lastItem);
this._processTakes(children, skips.slice(1), takes.slice(1), groupCount - 1, parents)
}
}
_processPaging(options, groupCount) {
this._processSkips(options.data, options.skips, groupCount);
this._processTakes(options.data, options.skips, options.takes, groupCount)
}
isLastLevelGroupItemsPagingLocal() {
return false
}
sortLastLevelGroupItems(items) {
return items
}
refresh(options, operationTypes) {
const that = this;
const dataSource = that._dataSource;
const {
storeLoadOptions: storeLoadOptions
} = options;
const group = options.group || options.storeLoadOptions.group;
const oldGroups = _m_core.default.normalizeSortingInfo(that._group);
let isExpanded;
let groupIndex;
function handleGroup(groupInfo, parents) {
if (parents.length === groupIndex + 1) {
groupInfo.isExpanded = isExpanded
}
}
for (groupIndex = 0; groupIndex < oldGroups.length; groupIndex++) {
isExpanded = isGroupExpanded(group, groupIndex);
if (isGroupExpanded(that._group, groupIndex) !== isExpanded) {
that.foreachGroups(handleGroup)
}
}
super.refresh.apply(this, arguments);
if (group && options.remoteOperations.paging && operationTypes.reload) {
return foreachExpandedGroups(that, (groupInfo => {
const groupCountQuery = loadGroupTotalCount(dataSource, {
filter: (0, _m_utils.createGroupFilter)(groupInfo.path, {
filter: storeLoadOptions.filter,
group: group
}),
group: group.slice(groupInfo.path.length),
select: storeLoadOptions.select
});
const groupOffsetQuery = loadGroupTotalCount(dataSource, {
filter: (0, _m_grouping_core.createOffsetFilter)(groupInfo.path, {
filter: storeLoadOptions.filter,
group: group
}, true),
group: group.slice(groupInfo.path.length - 1, groupInfo.path.length),
select: storeLoadOptions.select
});
return (0, _deferred.when)(groupOffsetQuery, groupCountQuery).done(((offset, count) => {
offset = parseInt(offset.length ? offset[0] : offset);
count = parseInt(count.length ? count[0] : count);
groupInfo.offset = offset;
if (groupInfo.count !== count) {
groupInfo.count = count;
that.updateTotalItemsCount(options)
}
}))
}), true)
}
}
}
exports.GroupingHelper = GroupingHelper;