devextreme
Version:
JavaScript/TypeScript Component Suite for Responsive Web Development
266 lines (265 loc) • 9.76 kB
JavaScript
/**
* DevExtreme (esm/__internal/ui/collection/collection_widget.live_update.js)
* Version: 25.2.7
* Build date: Tue May 05 2026
*
* Copyright (c) 2012 - 2026 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
indexByKey,
insert,
update
} from "../../../common/data/array_utils";
import {
keysEqual
} from "../../../common/data/utils";
import domAdapter from "../../../core/dom_adapter";
import $ from "../../../core/renderer";
import {
findChanges
} from "../../../core/utils/array_compare";
import {
when
} from "../../../core/utils/deferred";
import {
extend
} from "../../../core/utils/extend";
import {
each
} from "../../../core/utils/iterator";
import CollectionWidgetAsync from "../../ui/collection/collection_widget.async";
export const PRIVATE_KEY_FIELD = "__dx_key__";
class CollectionWidgetLiveUpdate extends CollectionWidgetAsync {
_getDefaultOptions() {
return Object.assign({}, super._getDefaultOptions(), {
repaintChangesOnly: false
})
}
reload() {}
_init() {
super._init();
this._refreshItemsCache()
}
_findItemElementByKey(key) {
let result = $();
const keyExpr = this.key();
this.itemElements().each((_, item) => {
const $item = $(item);
const itemData = this._getItemData($item);
if (keyExpr ? keysEqual(keyExpr, this.keyOf(itemData), key) : this._isItemEquals(itemData, key)) {
result = $item;
return false
}
return true
});
return result
}
_dataSourceChangedHandler(newItems, e) {
if (null !== e && void 0 !== e && e.changes) {
this._modifyByChanges(e.changes)
} else {
super._dataSourceChangedHandler(newItems, e);
this._refreshItemsCache()
}
}
_isItemEquals(item1, item2) {
let itemToCompare = item1;
if (item1 && "object" === typeof item1 && item1.__dx_key__) {
itemToCompare = item1.data
}
try {
return JSON.stringify(itemToCompare) === JSON.stringify(item2)
} catch (e) {
return itemToCompare === item2
}
}
_isItemStrictEquals(item1, item2) {
return this._isItemEquals(item1, item2)
}
_shouldAddNewGroup(changes, items) {
let result = false;
const {
grouped: grouped
} = this.option();
if (grouped) {
if (!changes.length) {
result = true
}
each(changes, (i, change) => {
if ("insert" === change.type) {
result = true;
each(items, (_, item) => {
if (void 0 !== change.data.key && change.data.key === item.key) {
result = false;
return false
}
return true
})
}
})
}
return result
}
_partialRefresh() {
const {
repaintChangesOnly: repaintChangesOnly
} = this.option();
if (repaintChangesOnly) {
const keyOf = data => {
if (data && void 0 !== data.__dx_key__) {
return data.__dx_key__
}
return this.keyOf(data)
};
const result = findChanges({
oldItems: this._itemsCache,
newItems: this._editStrategy.itemsGetter(),
getKey: keyOf,
isItemEquals: this._isItemStrictEquals.bind(this),
detectReorders: true
});
if (result && this._itemsCache.length && !this._shouldAddNewGroup(result, this._itemsCache)) {
this._modifyByChanges(result, true);
this._renderEmptyMessage();
return true
}
this._refreshItemsCache()
}
return false
}
_refreshItemsCache() {
const {
repaintChangesOnly: repaintChangesOnly
} = this.option();
if (repaintChangesOnly) {
const items = this._editStrategy.itemsGetter();
try {
this._itemsCache = extend(true, [], items);
if (!this.key()) {
this._itemsCache = this._itemsCache.map((itemCache, index) => ({
[PRIVATE_KEY_FIELD]: items[index],
data: itemCache
}))
}
} catch (e) {
this._itemsCache = extend([], items)
}
}
}
_updateByChange(keyInfo, items, change, isPartialRefresh) {
if (isPartialRefresh) {
this._renderItem(change.index, change.data, null, this._findItemElementByKey(change.key))
} else {
const changedItem = items[indexByKey(keyInfo, items, change.key)];
if (changedItem) {
update(keyInfo, items, change.key, change.data).done(() => {
this._renderItem(items.indexOf(changedItem), changedItem, null, this._findItemElementByKey(change.key))
})
}
}
}
_insertByChange(keyInfo, items, change, isPartialRefresh) {
when(isPartialRefresh ?? insert(keyInfo, items, change.data, change.index)).done(() => {
this._beforeItemElementInserted(change);
this._renderItem(change.index ?? items.length, change.data);
this._afterItemElementInserted()
})
}
_updateSelectionAfterRemoveByChange(removeIndex) {
const {
selectedIndex: selectedIndex,
selectedItems: selectedItems = []
} = this.option();
const index = selectedIndex;
if (index > removeIndex) {
this.option("selectedIndex", index - 1)
} else if (index === removeIndex && 1 === selectedItems.length) {
this.option("selectedItems", [])
} else {
this._normalizeSelectedItems()
}
}
_beforeItemElementInserted(change) {
const {
selectedIndex: selectedIndex
} = this.option();
const index = selectedIndex;
if (change.index <= index) {
this.option("selectedIndex", index + 1)
}
}
_afterItemElementInserted() {
this._renderEmptyMessage()
}
_removeByChange(keyInfo, items, change, isPartialRefresh) {
const index = isPartialRefresh ? change.index : indexByKey(keyInfo, items, change.key);
const removedItem = isPartialRefresh ? change.oldItem : items[index];
if (removedItem) {
const $removedItemElement = this._findItemElementByKey(change.key);
const deletedActionArgs = this._extendActionArgs($removedItemElement);
this._waitDeletingPrepare($removedItemElement).done(() => {
if (isPartialRefresh) {
this._updateIndicesAfterIndex(index - 1);
this._afterItemElementDeleted($removedItemElement, deletedActionArgs);
this._updateSelectionAfterRemoveByChange(index)
} else {
this._deleteItemElementByIndex(index);
this._afterItemElementDeleted($removedItemElement, deletedActionArgs)
}
})
}
}
_modifyByChanges(changes, isPartialRefresh) {
const items = this._editStrategy.itemsGetter();
const keyInfo = {
key: this.key.bind(this),
keyOf: this.keyOf.bind(this)
};
const dataController = this._dataController;
const paginate = dataController.paginate();
const group = dataController.group();
let filteredChanges = changes;
if (paginate || group) {
filteredChanges = changes.filter(item => "insert" !== item.type || void 0 !== item.index)
}
filteredChanges.forEach(change => this[`_${change.type}ByChange`](keyInfo, items, change, isPartialRefresh));
this._renderedItemsCount = items.length;
this._refreshItemsCache();
this._fireContentReadyAction()
}
_appendItemToContainer($container, $itemFrame, index) {
const nextSiblingElement = $container.children(this._itemSelector())[index];
domAdapter.insertElement($container[0], $itemFrame[0], nextSiblingElement)
}
_optionChanged(args) {
const {
name: name,
value: value
} = args;
switch (name) {
case "items": {
const isItemsUpdated = this._partialRefresh();
if (!isItemsUpdated) {
super._optionChanged(args)
}
break
}
case "dataSource": {
const {
repaintChangesOnly: repaintChangesOnly
} = this.option();
if (!repaintChangesOnly || !value) {
this.option("items", [])
}
super._optionChanged(args);
break
}
case "repaintChangesOnly":
break;
default:
super._optionChanged(args)
}
}
}
export default CollectionWidgetLiveUpdate;