UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

629 lines (612 loc) • 21.4 kB
/** * DevExtreme (data/data_source/data_source.js) * Version: 18.2.18 * Build date: Tue Oct 18 2022 * * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; var Class = require("../../core/class"), extend = require("../../core/utils/extend").extend, commonUtils = require("../../core/utils/common"), iteratorUtils = require("../../core/utils/iterator"), ajax = require("../../core/utils/ajax"), typeUtils = require("../../core/utils/type"), dataUtils = require("../utils"), arrayUtils = require("../array_utils"), Store = require("../abstract_store"), ArrayStore = require("../array_store"), CustomStore = require("../custom_store"), EventsMixin = require("../../core/events_mixin"), errors = require("../errors").errors, array = require("../../core/utils/array"), queue = require("../../core/utils/queue"), deferredUtils = require("../../core/utils/deferred"), when = deferredUtils.when, Deferred = deferredUtils.Deferred, __isString = typeUtils.isString, __isNumber = typeUtils.isNumeric, __isBoolean = typeUtils.isBoolean, __isDefined = typeUtils.isDefined; var CANCELED_TOKEN = "canceled"; function OperationManager() { this._counter = -1; this._deferreds = {} } OperationManager.prototype.constructor = OperationManager; OperationManager.prototype.add = function(deferred) { this._counter += 1; this._deferreds[this._counter] = deferred; return this._counter }; OperationManager.prototype.remove = function(operationId) { return delete this._deferreds[operationId] }; OperationManager.prototype.cancel = function(operationId) { if (operationId in this._deferreds) { this._deferreds[operationId].reject(CANCELED_TOKEN); return true } return false }; OperationManager.prototype.cancelAll = function() { while (this._counter > -1) { this.cancel(this._counter); this._counter-- } }; function isPending(deferred) { return "pending" === deferred.state() } function normalizeDataSourceOptions(options, normalizationOptions) { var store; function createCustomStoreFromLoadFunc() { var storeConfig = {}; iteratorUtils.each(["useDefaultSearch", "key", "load", "loadMode", "cacheRawData", "byKey", "lookup", "totalCount", "insert", "update", "remove"], function() { storeConfig[this] = options[this]; delete options[this] }); return new CustomStore(storeConfig) } function createStoreFromConfig(storeConfig) { var alias = storeConfig.type; delete storeConfig.type; return Store.create(alias, storeConfig) } function createCustomStoreFromUrl(url) { return new CustomStore({ load: function() { return ajax.sendRequest({ url: url, dataType: "json" }) }, loadMode: normalizationOptions && normalizationOptions.fromUrlLoadMode }) } if ("string" === typeof options) { options = { paginate: false, store: createCustomStoreFromUrl(options) } } if (void 0 === options) { options = [] } if (Array.isArray(options) || options instanceof Store) { options = { store: options } } else { options = extend({}, options) } if (void 0 === options.store) { options.store = [] } store = options.store; if ("load" in options) { store = createCustomStoreFromLoadFunc() } else { if (Array.isArray(store)) { store = new ArrayStore(store) } else { if (typeUtils.isPlainObject(store)) { store = createStoreFromConfig(extend({}, store)) } } } options.store = store; return options } function normalizeStoreLoadOptionAccessorArguments(originalArguments) { switch (originalArguments.length) { case 0: return; case 1: return originalArguments[0] } return [].slice.call(originalArguments) } function generateStoreLoadOptionAccessor(optionName) { return function() { var args = normalizeStoreLoadOptionAccessorArguments(arguments); if (void 0 === args) { return this._storeLoadOptions[optionName] } this._storeLoadOptions[optionName] = args } } function mapDataRespectingGrouping(items, mapper, groupInfo) { function mapRecursive(items, level) { if (!Array.isArray(items)) { return items } return level ? mapGroup(items, level) : iteratorUtils.map(items, mapper) } function mapGroup(group, level) { return iteratorUtils.map(group, function(item) { var result = { key: item.key, items: mapRecursive(item.items, level - 1) }; if ("aggregates" in item) { result.aggregates = item.aggregates } return result }) } return mapRecursive(items, groupInfo ? dataUtils.normalizeSortingInfo(groupInfo).length : 0) } function normalizeLoadResult(data, extra) { if (data && !Array.isArray(data) && data.data) { extra = data; data = data.data } if (!Array.isArray(data)) { data = [data] } return { data: data, extra: extra } } var DataSource = Class.inherit({ ctor: function(options) { var _this = this; var that = this; options = normalizeDataSourceOptions(options); var onPushHandler = 0 !== options.pushAggregationTimeout ? dataUtils.throttleChanges(this._onPush, function() { if (void 0 === options.pushAggregationTimeout) { return 5 * that._changedTime } return options.pushAggregationTimeout }) : this._onPush; this._changedTime = 0; this._onPushHandler = function(changes) { _this._aggregationTimeoutId = onPushHandler.call(_this, changes) }; this._store = options.store; this._store.on("push", this._onPushHandler); this._storeLoadOptions = this._extractLoadOptions(options); this._mapFunc = options.map; this._postProcessFunc = options.postProcess; this._pageIndex = void 0 !== options.pageIndex ? options.pageIndex : 0; this._pageSize = void 0 !== options.pageSize ? options.pageSize : 20; this._loadingCount = 0; this._loadQueue = this._createLoadQueue(); this._searchValue = "searchValue" in options ? options.searchValue : null; this._searchOperation = options.searchOperation || "contains"; this._searchExpr = options.searchExpr; this._paginate = options.paginate; this._reshapeOnPush = __isDefined(options.reshapeOnPush) ? options.reshapeOnPush : false; iteratorUtils.each(["onChanged", "onLoadError", "onLoadingChanged", "onCustomizeLoadResult", "onCustomizeStoreLoadOptions"], function(_, optionName) { if (optionName in options) { that.on(optionName.substr(2, 1).toLowerCase() + optionName.substr(3), options[optionName]) } }); this._operationManager = new OperationManager; this._init() }, _init: function() { this._items = []; this._userData = {}; this._totalCount = -1; this._isLoaded = false; if (!__isDefined(this._paginate)) { this._paginate = !this.group() } this._isLastPage = !this._paginate }, dispose: function() { this._store.off("push", this._onPushHandler); this._disposeEvents(); clearTimeout(this._aggregationTimeoutId); delete this._store; if (this._delayedLoadTask) { this._delayedLoadTask.abort() } this._operationManager.cancelAll(); this._disposed = true }, _extractLoadOptions: function(options) { var result = {}, names = ["sort", "filter", "select", "group", "requireTotalCount"], customNames = this._store._customLoadOptions(); if (customNames) { names = names.concat(customNames) } iteratorUtils.each(names, function() { result[this] = options[this] }); return result }, loadOptions: function() { return this._storeLoadOptions }, items: function() { return this._items }, pageIndex: function(newIndex) { if (!__isNumber(newIndex)) { return this._pageIndex } this._pageIndex = newIndex; this._isLastPage = !this._paginate }, paginate: function(value) { if (!__isBoolean(value)) { return this._paginate } if (this._paginate !== value) { this._paginate = value; this.pageIndex(0) } }, pageSize: function(value) { if (!__isNumber(value)) { return this._pageSize } this._pageSize = value }, isLastPage: function() { return this._isLastPage }, sort: generateStoreLoadOptionAccessor("sort"), filter: function() { var newFilter = normalizeStoreLoadOptionAccessorArguments(arguments); if (void 0 === newFilter) { return this._storeLoadOptions.filter } this._storeLoadOptions.filter = newFilter; this.pageIndex(0) }, group: generateStoreLoadOptionAccessor("group"), select: generateStoreLoadOptionAccessor("select"), requireTotalCount: function(value) { if (!__isBoolean(value)) { return this._storeLoadOptions.requireTotalCount } this._storeLoadOptions.requireTotalCount = value }, searchValue: function(value) { if (arguments.length < 1) { return this._searchValue } this._searchValue = value; this.pageIndex(0) }, searchOperation: function(op) { if (!__isString(op)) { return this._searchOperation } this._searchOperation = op; this.pageIndex(0) }, searchExpr: function(expr) { var argc = arguments.length; if (0 === argc) { return this._searchExpr } if (argc > 1) { expr = [].slice.call(arguments) } this._searchExpr = expr; this.pageIndex(0) }, store: function() { return this._store }, key: function() { return this._store && this._store.key() }, totalCount: function() { return this._totalCount }, isLoaded: function() { return this._isLoaded }, isLoading: function() { return this._loadingCount > 0 }, beginLoading: function() { this._changeLoadingCount(1) }, endLoading: function() { this._changeLoadingCount(-1) }, _createLoadQueue: function() { return queue.create() }, _changeLoadingCount: function(increment) { var newLoading, oldLoading = this.isLoading(); this._loadingCount += increment; newLoading = this.isLoading(); if (oldLoading ^ newLoading) { this.fireEvent("loadingChanged", [newLoading]) } }, _scheduleLoadCallbacks: function(deferred) { var that = this; that.beginLoading(); deferred.always(function() { that.endLoading() }) }, _scheduleFailCallbacks: function(deferred) { var that = this; deferred.fail(function() { if (arguments[0] === CANCELED_TOKEN) { return } that.fireEvent("loadError", arguments) }) }, _fireChanged: function(args) { var date = new Date; this.fireEvent("changed", args); this._changedTime = new Date - date }, _scheduleChangedCallbacks: function(deferred) { var _this2 = this; deferred.done(function() { _this2._fireChanged() }) }, loadSingle: function(propName, propValue) { var that = this; var d = new Deferred, key = this.key(), store = this._store, options = this._createStoreLoadOptions(), handleDone = function(data) { if (!__isDefined(data) || array.isEmpty(data)) { d.reject(new errors.Error("E4009")) } else { if (!Array.isArray(data)) { data = [data] } d.resolve(that._applyMapFunction(data)[0]) } }; this._scheduleFailCallbacks(d); if (arguments.length < 2) { propValue = propName; propName = key } delete options.skip; delete options.group; delete options.refresh; delete options.pageIndex; delete options.searchString; function shouldForceByKey() { return store instanceof CustomStore && !store._byKeyViaLoad() }(function() { if (propName === key || shouldForceByKey()) { return store.byKey(propValue, options) } options.take = 1; options.filter = options.filter ? [options.filter, [propName, propValue]] : [propName, propValue]; return store.load(options) })().fail(d.reject).done(handleDone); return d.promise() }, load: function() { var loadOperation, that = this, d = new Deferred; function loadTask() { if (that._disposed) { return } if (!isPending(d)) { return } return that._loadFromStore(loadOperation, d) } this._scheduleLoadCallbacks(d); this._scheduleFailCallbacks(d); this._scheduleChangedCallbacks(d); loadOperation = this._createLoadOperation(d); this.fireEvent("customizeStoreLoadOptions", [loadOperation]); this._loadQueue.add(function() { if ("number" === typeof loadOperation.delay) { that._delayedLoadTask = commonUtils.executeAsync(loadTask, loadOperation.delay) } else { loadTask() } return d.promise() }); return d.promise({ operationId: loadOperation.operationId }) }, _onPush: function(changes) { if (this._reshapeOnPush) { this.load() } else { this.fireEvent("changing", [{ changes: changes }]); var group = this.group(), items = this.items(), groupLevel = 0, dataSourceChanges = this.paginate() || group ? changes.filter(function(item) { return "update" === item.type }) : changes; if (group) { groupLevel = Array.isArray(group) ? group.length : 1 } arrayUtils.applyBatch(this.store(), items, dataSourceChanges, groupLevel, true); this._fireChanged([{ changes: changes }]) } }, _createLoadOperation: function(deferred) { var id = this._operationManager.add(deferred), options = this._createStoreLoadOptions(); deferred.always(function() { this._operationManager.remove(id) }.bind(this)); return { operationId: id, storeLoadOptions: options } }, reload: function() { var store = this.store(); if (store instanceof CustomStore) { store.clearRawDataCache() } this._init(); return this.load() }, cancel: function(operationId) { return this._operationManager.cancel(operationId) }, cancelAll: function() { return this._operationManager.cancelAll() }, _addSearchOptions: function(storeLoadOptions) { if (this._disposed) { return } if (this.store()._useDefaultSearch) { this._addSearchFilter(storeLoadOptions) } else { storeLoadOptions.searchOperation = this._searchOperation; storeLoadOptions.searchValue = this._searchValue; storeLoadOptions.searchExpr = this._searchExpr } }, _createStoreLoadOptions: function() { var result = extend({}, this._storeLoadOptions); this._addSearchOptions(result); if (this._paginate) { if (this._pageSize) { result.skip = this._pageIndex * this._pageSize; result.take = this._pageSize } } result.userData = this._userData; return result }, _addSearchFilter: function(storeLoadOptions) { var value = this._searchValue, op = this._searchOperation, selector = this._searchExpr, searchFilter = []; if (!value) { return } if (!selector) { selector = "this" } if (!Array.isArray(selector)) { selector = [selector] } iteratorUtils.each(selector, function(i, item) { if (searchFilter.length) { searchFilter.push("or") } searchFilter.push([item, op, value]) }); if (storeLoadOptions.filter) { storeLoadOptions.filter = [searchFilter, storeLoadOptions.filter] } else { storeLoadOptions.filter = searchFilter } }, _loadFromStore: function(loadOptions, pendingDeferred) { var that = this; function handleSuccess(data, extra) { function processResult() { var loadResult = extend(normalizeLoadResult(data, extra), loadOptions); that.fireEvent("customizeLoadResult", [loadResult]); when(loadResult.data).done(function(data) { loadResult.data = data; that._processStoreLoadResult(loadResult, pendingDeferred) }).fail(pendingDeferred.reject) } if (that._disposed) { return } if (!isPending(pendingDeferred)) { return } processResult() } if (loadOptions.data) { return (new Deferred).resolve(loadOptions.data).done(handleSuccess) } return this.store().load(loadOptions.storeLoadOptions).done(handleSuccess).fail(pendingDeferred.reject) }, _processStoreLoadResult: function(loadResult, pendingDeferred) { var that = this, data = loadResult.data, extra = loadResult.extra, storeLoadOptions = loadResult.storeLoadOptions; function resolvePendingDeferred() { that._isLoaded = true; that._totalCount = isFinite(extra.totalCount) ? extra.totalCount : -1; return pendingDeferred.resolve(data, extra) } function proceedLoadingTotalCount() { that.store().totalCount(storeLoadOptions).done(function(count) { extra.totalCount = count; resolvePendingDeferred() }).fail(pendingDeferred.reject) } if (that._disposed) { return } data = that._applyPostProcessFunction(that._applyMapFunction(data)); if (!typeUtils.isPlainObject(extra)) { extra = {} } that._items = data; if (!data.length || !that._paginate || that._pageSize && data.length < that._pageSize) { that._isLastPage = true } if (storeLoadOptions.requireTotalCount && !isFinite(extra.totalCount)) { proceedLoadingTotalCount() } else { resolvePendingDeferred() } }, _applyMapFunction: function(data) { if (this._mapFunc) { return mapDataRespectingGrouping(data, this._mapFunc, this.group()) } return data }, _applyPostProcessFunction: function(data) { if (this._postProcessFunc) { return this._postProcessFunc(data) } return data } }).include(EventsMixin); exports.DataSource = DataSource; exports.normalizeDataSourceOptions = normalizeDataSourceOptions; exports.normalizeLoadResult = normalizeLoadResult;