devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
1,031 lines (858 loc) • 26.9 kB
JavaScript
"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"),
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 deferred.state() === "pending";
}
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 load() {
return ajax.sendRequest({
url: url,
dataType: "json"
});
},
loadMode: normalizationOptions && normalizationOptions.fromUrlLoadMode
});
}
if (typeof options === "string") {
options = {
paginate: false,
store: createCustomStoreFromUrl(options)
};
}
if (options === undefined) {
options = [];
}
if (Array.isArray(options) || options instanceof Store) {
options = { store: options };
} else {
options = extend({}, options);
}
if (options.store === undefined) {
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 undefined;
case 1:
return originalArguments[0];
}
return [].slice.call(originalArguments);
}
function generateStoreLoadOptionAccessor(optionName) {
return function () {
var args = normalizeStoreLoadOptionAccessorArguments(arguments);
if (args === undefined) {
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);
}
var DataSource = Class.inherit({
/**
* @name DataSourceMethods.ctor
* @publicName ctor(url)
* @param1 url:string
* @hidden
*/
/**
* @name DataSourceMethods.ctor
* @publicName ctor(data)
* @param1 data:Array<any>
* @hidden
*/
/**
* @name DataSourceMethods.ctor
* @publicName ctor(store)
* @param1 store:Store
* @hidden
*/
/**
* @name DataSourceMethods.ctor
* @publicName ctor(options)
* @param1 options:CustomStoreOptions|DataSourceOptions
* @hidden
*/
ctor: function ctor(options) {
var that = this;
options = normalizeDataSourceOptions(options);
/**
* @name DataSourceOptions.store.type
* @publicName type
* @type Enums.DataSourceStoreType
*/
/**
* @name DataSourceOptions.store
* @publicName store
* @type Store|StoreOptions|Array<any>|any
*/
this._store = options.store;
/**
* @name DataSourceOptions.sort
* @publicName sort
* @type Sort expression
*/
/**
* @name DataSourceOptions.filter
* @publicName filter
* @type Filter expression
*/
/**
* @name DataSourceOptions.group
* @publicName group
* @type Group expression
*/
/**
* @name DataSourceOptions.select
* @publicName select
* @type Select expression
*/
/**
* @name DataSourceOptions.expand
* @publicName expand
* @type Array<string>|string
*/
/**
* @name DataSourceOptions.customQueryParams
* @publicName customQueryParams
* @type Object
*/
/**
* @name DataSourceOptions.requireTotalCount
* @publicName requireTotalCount
* @type Boolean
*/
this._storeLoadOptions = this._extractLoadOptions(options);
/**
* @name DataSourceOptions.map
* @publicName map
* @type function
* @type_function_param1 dataItem:object
* @type_function_return object
*/
this._mapFunc = options.map;
/**
* @name DataSourceOptions.postProcess
* @publicName postProcess
* @type function
* @type_function_param1 data:Array<any>
* @type_function_return Array<any>
*/
this._postProcessFunc = options.postProcess;
this._pageIndex = options.pageIndex !== undefined ? options.pageIndex : 0;
/**
* @name DataSourceOptions.pageSize
* @publicName pageSize
* @type number
* @default 20
*/
this._pageSize = options.pageSize !== undefined ? options.pageSize : 20;
this._loadingCount = 0;
this._loadQueue = this._createLoadQueue();
/**
* @name DataSourceOptions.searchValue
* @publicName searchValue
* @type any
* @default null
*/
this._searchValue = "searchValue" in options ? options.searchValue : null;
/**
* @name DataSourceOptions.searchOperation
* @publicName searchOperation
* @type string
* @default "contains"
*/
this._searchOperation = options.searchOperation || "contains";
/**
* @name DataSourceOptions.searchExpr
* @publicName searchExpr
* @type getter|Array<getter>
*/
this._searchExpr = options.searchExpr;
/**
* @name DataSourceOptions.paginate
* @publicName paginate
* @type Boolean
* @default undefined
*/
this._paginate = options.paginate;
iteratorUtils.each([
/**
* @name DataSourceOptions.onChanged
* @publicName onChanged
* @type function
* @action
*/
"onChanged",
/**
* @name DataSourceOptions.onLoadError
* @publicName onLoadError
* @type function
* @type_function_param1 error:Object
* @type_function_param1_field1 message:string
* @action
*/
"onLoadError",
/**
* @name DataSourceOptions.onLoadingChanged
* @publicName onLoadingChanged
* @type function
* @type_function_param1 isLoading:boolean
* @action
*/
"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 _init() {
this._items = [];
this._userData = {};
this._totalCount = -1;
this._isLoaded = false;
if (!__isDefined(this._paginate)) {
this._paginate = !this.group();
}
this._isLastPage = !this._paginate;
},
/**
* @name DataSourceMethods.dispose
* @publicName dispose()
*/
dispose: function dispose() {
this._disposeEvents();
delete this._store;
if (this._delayedLoadTask) {
this._delayedLoadTask.abort();
}
this._operationManager.cancelAll();
this._disposed = true;
},
_extractLoadOptions: function _extractLoadOptions(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;
},
/**
* @name DataSourceMethods.loadOptions
* @publicName loadOptions()
* @return object
*/
loadOptions: function loadOptions() {
return this._storeLoadOptions;
},
/**
* @name DataSourceMethods.items
* @publicName items()
* @return Array<any>
*/
items: function items() {
return this._items;
},
/**
* @name DataSourceMethods.pageIndex
* @publicName pageIndex()
* @return numeric
*/
/**
* @name DataSourceMethods.pageIndex
* @publicName pageIndex(newIndex)
* @param1 newIndex:numeric
*/
pageIndex: function pageIndex(newIndex) {
if (!__isNumber(newIndex)) {
return this._pageIndex;
}
this._pageIndex = newIndex;
this._isLastPage = !this._paginate;
},
/**
* @name DataSourceMethods.paginate
* @publicName paginate()
* @return Boolean
*/
/**
* @name DataSourceMethods.paginate
* @publicName paginate(value)
* @param1 value:Boolean
*/
paginate: function paginate(value) {
if (!__isBoolean(value)) {
return this._paginate;
}
if (this._paginate !== value) {
this._paginate = value;
this.pageIndex(0);
}
},
/**
* @name DataSourceMethods.pageSize
* @publicName pageSize()
* @return numeric
*/
/**
* @name DataSourceMethods.pageSize
* @publicName pageSize(value)
* @param1 value:numeric
*/
pageSize: function pageSize(value) {
if (!__isNumber(value)) {
return this._pageSize;
}
this._pageSize = value;
},
/**
* @name DataSourceMethods.isLastPage
* @publicName isLastPage()
* @return boolean
*/
isLastPage: function isLastPage() {
return this._isLastPage;
},
/**
* @name DataSourceMethods.sort
* @publicName sort()
* @return any
*/
/**
* @name DataSourceMethods.sort
* @publicName sort(sortExpr)
* @param1 sortExpr:any
*/
sort: generateStoreLoadOptionAccessor("sort"),
/**
* @name DataSourceMethods.filter
* @publicName filter()
* @return object
*/
/**
* @name DataSourceMethods.filter
* @publicName filter(filterExpr)
* @param1 filterExpr:object
*/
filter: function filter() {
var newFilter = normalizeStoreLoadOptionAccessorArguments(arguments);
if (newFilter === undefined) {
return this._storeLoadOptions.filter;
}
this._storeLoadOptions.filter = newFilter;
this.pageIndex(0);
},
/**
* @name DataSourceMethods.group
* @publicName group()
* @return object
*/
/**
* @name DataSourceMethods.group
* @publicName group(groupExpr)
* @param1 groupExpr:object
*/
group: generateStoreLoadOptionAccessor("group"),
/**
* @name DataSourceMethods.select
* @publicName select()
* @return any
*/
/**
* @name DataSourceMethods.select
* @publicName select(expr)
* @param1 expr:any
*/
select: generateStoreLoadOptionAccessor("select"),
/**
* @name DataSourceMethods.requireTotalCount
* @publicName requireTotalCount()
* @return boolean
*/
/**
* @name DataSourceMethods.requireTotalCount
* @publicName requireTotalCount(value)
* @param1 value:boolean
*/
requireTotalCount: function requireTotalCount(value) {
if (!__isBoolean(value)) {
return this._storeLoadOptions.requireTotalCount;
}
this._storeLoadOptions.requireTotalCount = value;
},
/**
* @name DataSourceMethods.searchValue
* @publicName searchValue()
* @return any
*/
/**
* @name DataSourceMethods.searchValue
* @publicName searchValue(value)
* @param1 value:any
*/
searchValue: function searchValue(value) {
if (arguments.length < 1) {
return this._searchValue;
}
this._searchValue = value;
this.pageIndex(0);
},
/**
* @name DataSourceMethods.searchOperation
* @publicName searchOperation()
* @return string
*/
/**
* @name DataSourceMethods.searchOperation
* @publicName searchOperation(op)
* @param1 op:string
*/
searchOperation: function searchOperation(op) {
if (!__isString(op)) {
return this._searchOperation;
}
this._searchOperation = op;
this.pageIndex(0);
},
/**
* @name DataSourceMethods.searchExpr
* @publicName searchExpr()
* @return getter|Array<getter>
*/
/**
* @name DataSourceMethods.searchExpr
* @publicName searchExpr(expr)
* @param1 expr:getter|Array<getter>
*/
searchExpr: function searchExpr(expr) {
var argc = arguments.length;
if (argc === 0) {
return this._searchExpr;
}
if (argc > 1) {
expr = [].slice.call(arguments);
}
this._searchExpr = expr;
this.pageIndex(0);
},
/**
* @name DataSourceMethods.store
* @publicName store()
* @return object
*/
store: function store() {
return this._store;
},
/**
* @name DataSourceMethods.key
* @publicName key()
* @return object|string|number
*/
key: function key() {
return this._store && this._store.key();
},
/**
* @name DataSourceMethods.totalCount
* @publicName totalCount()
* @return numeric
*/
totalCount: function totalCount() {
return this._totalCount;
},
/**
* @name DataSourceMethods.isLoaded
* @publicName isLoaded()
* @return boolean
*/
isLoaded: function isLoaded() {
return this._isLoaded;
},
/**
* @name DataSourceMethods.isLoading
* @publicName isLoading()
* @return boolean
*/
isLoading: function isLoading() {
return this._loadingCount > 0;
},
beginLoading: function beginLoading() {
this._changeLoadingCount(1);
},
endLoading: function endLoading() {
this._changeLoadingCount(-1);
},
_createLoadQueue: function _createLoadQueue() {
return queue.create();
},
_changeLoadingCount: function _changeLoadingCount(increment) {
var oldLoading = this.isLoading(),
newLoading;
this._loadingCount += increment;
newLoading = this.isLoading();
if (oldLoading ^ newLoading) {
this.fireEvent("loadingChanged", [newLoading]);
}
},
_scheduleLoadCallbacks: function _scheduleLoadCallbacks(deferred) {
var that = this;
that.beginLoading();
deferred.always(function () {
that.endLoading();
});
},
_scheduleFailCallbacks: function _scheduleFailCallbacks(deferred) {
var that = this;
deferred.fail(function () {
if (arguments[0] === CANCELED_TOKEN) {
return;
}
that.fireEvent("loadError", arguments);
});
},
_scheduleChangedCallbacks: function _scheduleChangedCallbacks(deferred) {
var that = this;
deferred.done(function () {
that.fireEvent("changed");
});
},
loadSingle: function loadSingle(propName, propValue) {
var that = this;
var d = new Deferred(),
key = this.key(),
store = this._store,
options = this._createStoreLoadOptions(),
handleDone = function handleDone(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 () {
// NOTE for CustomStore always using byKey for backward compatibility with "old user datasource"
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();
},
/**
* @name DataSourceMethods.load
* @publicName load()
* @return Promise<any>
*/
load: function load() {
var that = this,
d = new Deferred(),
loadOperation;
function loadTask() {
if (that._disposed) {
return undefined;
}
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 (typeof loadOperation.delay === "number") {
that._delayedLoadTask = commonUtils.executeAsync(loadTask, loadOperation.delay);
} else {
loadTask();
}
return d.promise();
});
return d.promise({
operationId: loadOperation.operationId
});
},
_createLoadOperation: function _createLoadOperation(deferred) {
var id = this._operationManager.add(deferred),
options = this._createStoreLoadOptions();
deferred.always(function () {
this._operationManager.remove(id);
}.bind(this));
return {
operationId: id,
storeLoadOptions: options
};
},
/**
* @name DataSourceMethods.reload
* @publicName reload()
* @return Promise<any>
*/
reload: function reload() {
var store = this.store();
if (store instanceof CustomStore) {
store.clearRawDataCache();
}
this._init();
return this.load();
},
/**
* @name DataSourceMethods.cancel
* @publicName cancel(operationId)
* @return boolean
*/
cancel: function cancel(operationId) {
return this._operationManager.cancel(operationId);
},
_addSearchOptions: function _addSearchOptions(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 _createStoreLoadOptions() {
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 _addSearchFilter(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];
}
// TODO optimize for byKey case
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 _loadFromStore(loadOptions, pendingDeferred) {
var that = this;
function handleSuccess(data, extra) {
function processResult() {
var loadResult;
if (data && !Array.isArray(data) && data.data) {
extra = data;
data = data.data;
}
if (!Array.isArray(data)) {
data = [data];
}
loadResult = extend({
data: data,
extra: 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 _processStoreLoadResult(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;
}
// todo: if operation is canceled there is no need to do data transformation
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 _applyMapFunction(data) {
if (this._mapFunc) {
return mapDataRespectingGrouping(data, this._mapFunc, this.group());
}
return data;
},
_applyPostProcessFunction: function _applyPostProcessFunction(data) {
if (this._postProcessFunc) {
return this._postProcessFunc(data);
}
return data;
}
}).include(EventsMixin);
exports.DataSource = DataSource;
exports.normalizeDataSourceOptions = normalizeDataSourceOptions;