devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
527 lines (508 loc) • 21 kB
JavaScript
/**
* DevExtreme (esm/ui/pivot_grid/local_store.js)
* Version: 21.1.4
* Build date: Mon Jun 21 2021
*
* Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
when,
Deferred
} from "../../core/utils/deferred";
import dataUtils from "../../data/utils";
import dataQuery from "../../data/query";
import dateSerialization from "../../core/utils/date_serialization";
import {
DataSource
} from "../../data/data_source/data_source";
import CustomStore from "../../data/custom_store";
import {
compileGetter,
toComparable
} from "../../core/utils/data";
import Class from "../../core/class";
import {
noop
} from "../../core/utils/common";
import {
isNumeric,
isDefined,
isString
} from "../../core/utils/type";
import {
each
} from "../../core/utils/iterator";
import {
getFiltersByPath,
setFieldProperty,
setDefaultFieldValueFormatting,
storeDrillDownMixin,
discoverObjectFields
} from "./ui.pivot_grid.utils";
import ArrayStore from "../../data/array_store";
var PATH_DELIMETER = "/./";
export var LocalStore = Class.inherit(function() {
var DATE_INTERVAL_SELECTORS = {
year: function(date) {
return date && date.getFullYear()
},
quarter: function(date) {
return date && Math.floor(date.getMonth() / 3) + 1
},
month: function(date) {
return date && date.getMonth() + 1
},
day: function(date) {
return date && date.getDate()
},
dayOfWeek: function(date) {
return date && date.getDay()
}
};
function getDataSelector(dataField) {
return -1 !== dataField.indexOf(".") ? compileGetter(dataField) : function(data) {
return data[dataField]
}
}
function getDateValue(dataSelector) {
return function(data) {
var value = dataSelector(data);
if (value && !(value instanceof Date)) {
value = dateSerialization.deserializeDate(value)
}
return value
}
}
function prepareFields(fields) {
each(fields || [], (function(_, field) {
var fieldSelector;
var intervalSelector;
var dataField = field.dataField;
var groupInterval;
var levels = field.levels;
var dataSelector;
if (!field.selector) {
if (!dataField) {
dataSelector = function(data) {
return data
}
} else {
dataSelector = getDataSelector(dataField)
}
if (levels) {
prepareFields(levels)
}
if ("date" === field.dataType) {
intervalSelector = DATE_INTERVAL_SELECTORS[field.groupInterval];
var valueSelector = getDateValue(dataSelector);
fieldSelector = function(data) {
var value = valueSelector(data);
return intervalSelector ? intervalSelector(value) : value
}
} else if ("number" === field.dataType) {
groupInterval = isNumeric(field.groupInterval) && field.groupInterval > 0 && field.groupInterval;
fieldSelector = function(data) {
var value = dataSelector(data);
if (isString(value)) {
value = Number(value)
}
return groupInterval ? Math.floor(value / groupInterval) * groupInterval : value
}
} else {
fieldSelector = dataSelector
}
setDefaultFieldValueFormatting(field);
setFieldProperty(field, "selector", fieldSelector)
}
}))
}
function generateHierarchyItems(data, loadOptions, headers, headerName) {
var result = [0];
var expandIndex = loadOptions.headerName === headerName ? loadOptions.path.length : 0;
var expandedPaths = "rows" === headerName ? loadOptions.rowExpandedPaths : loadOptions.columnExpandedPaths;
var options = {
data: data,
childrenHash: headers[headerName + "Hash"],
dimensions: loadOptions[headerName],
expandedPathsHash: loadOptions.headerName !== headerName && expandedPaths && expandedPaths.hash
};
! function fillHierarchyItemIndexesCore(indexes, options, children, expandIndex, pathHash) {
var dimension = options.dimensions[expandIndex];
var expandedPathsHash = options.expandedPathsHash;
var dimensionValue;
var hierarchyItem;
if (dimension) {
dimensionValue = dimension.selector(options.data);
pathHash = void 0 !== pathHash ? pathHash + PATH_DELIMETER + dimensionValue : dimensionValue + "";
hierarchyItem = function(value, hierarchyItems, pathHash, childrenHash) {
var hierarchyItem = childrenHash[pathHash];
if (!hierarchyItem) {
hierarchyItem = {
value: value,
index: childrenHash.length++
};
childrenHash[pathHash] = hierarchyItem;
hierarchyItems.push(hierarchyItem)
}
return hierarchyItem
}(dimensionValue, children, pathHash, options.childrenHash);
indexes.push(hierarchyItem.index);
if (expandedPathsHash && expandedPathsHash[pathHash] || dimension.expanded) {
if (!hierarchyItem.children) {
hierarchyItem.children = []
}
fillHierarchyItemIndexesCore(indexes, options, hierarchyItem.children, expandIndex + 1, pathHash)
}
}
}(result, options, headers[headerName], expandIndex);
return result
}
function generateAggregationCells(data, cells, headers, options) {
var cellSet = [];
var x;
var y;
var rowIndex;
var columnIndex;
var rowIndexes = generateHierarchyItems(data, options, headers, "rows");
var columnIndexes = generateHierarchyItems(data, options, headers, "columns");
for (y = 0; y < rowIndexes.length; y++) {
rowIndex = rowIndexes[y];
cells[rowIndex] = cells[rowIndex] || [];
for (x = 0; x < columnIndexes.length; x++) {
columnIndex = columnIndexes[x];
cellSet.push(cells[rowIndex][columnIndex] = cells[rowIndex][columnIndex] || [])
}
}
return cellSet
}
function fillHashExpandedPath(expandedPaths) {
if (expandedPaths) {
var hash = expandedPaths.hash = {};
expandedPaths.forEach((function(path) {
var pathValue = path.map((function(value) {
return value + ""
})).join(PATH_DELIMETER);
hash[pathValue] = true
}))
}
}
function prepareLoadOption(options) {
options.rows = options.rows || [];
options.columns = options.columns || [];
options.filters = options.filters || [];
fillHashExpandedPath(options.columnExpandedPaths);
fillHashExpandedPath(options.rowExpandedPaths);
prepareFields(options.columns);
prepareFields(options.rows);
prepareFields(options.values);
prepareFields(options.filters)
}
function getAggregator(field) {
if ("custom" === field.summaryType) {
field.calculateCustomSummary = field.calculateCustomSummary || noop;
return {
seed: function() {
var options = {
summaryProcess: "start",
totalValue: void 0
};
field.calculateCustomSummary(options);
return options
},
step: function(options, value) {
options.summaryProcess = "calculate";
options.value = value;
field.calculateCustomSummary(options);
return options
},
finalize: function(options) {
options.summaryProcess = "finalize";
delete options.value;
field.calculateCustomSummary(options);
return options.totalValue
}
}
}
return dataUtils.aggregators[field.summaryType] || dataUtils.aggregators.count
}
function aggregationStep(measures, aggregationCells, data) {
for (var aggregatorIndex = 0; aggregatorIndex < measures.length; aggregatorIndex++) {
var cellField = measures[aggregatorIndex];
var cellValue = cellField.selector(data);
var aggregator = getAggregator(cellField);
var isAggregatorSeedFunction = "function" === typeof aggregator.seed;
for (var cellSetIndex = 0; cellSetIndex < aggregationCells.length; cellSetIndex++) {
var cell = aggregationCells[cellSetIndex];
if (cell.length <= aggregatorIndex) {
cell[aggregatorIndex] = isAggregatorSeedFunction ? aggregator.seed() : aggregator.seed
}
if (void 0 === cell[aggregatorIndex]) {
cell[aggregatorIndex] = cellValue
} else if (isDefined(cellValue)) {
cell[aggregatorIndex] = aggregator.step(cell[aggregatorIndex], cellValue)
}
}
}
}
function areValuesEqual(filterValue, fieldValue) {
var valueOfFilter = filterValue && filterValue.valueOf();
var valueOfField = fieldValue && fieldValue.valueOf();
if (Array.isArray(filterValue)) {
fieldValue = fieldValue || [];
for (var i = 0; i < filterValue.length; i++) {
valueOfFilter = filterValue[i] && filterValue[i].valueOf();
valueOfField = fieldValue[i] && fieldValue[i].valueOf();
if (valueOfFilter !== valueOfField) {
return false
}
}
return true
} else {
return valueOfFilter === valueOfField
}
}
function createDimensionFilters(dimension) {
var filters = [];
each(dimension, (function(_, field) {
var filterValues = field.filterValues || [];
var groupName = field.groupName;
if (groupName && isNumeric(field.groupIndex)) {
return
}
filterValues.length && filters.push((function(dataItem) {
var value = field.levels ? function(levels, data) {
var value = [];
each(levels, (function(_, field) {
value.push(field.selector(data))
}));
return value
}(field.levels, dataItem) : field.selector(dataItem);
var result = false;
for (var i = 0; i < filterValues.length; i++) {
if (areValuesEqual(filterValues[i], value)) {
result = true;
break
}
}
return "exclude" === field.filterType ? !result : result
}))
}));
return filters
}
function createFilter(options) {
var filters = createDimensionFilters(options.rows).concat(createDimensionFilters(options.columns)).concat(createDimensionFilters(options.filters));
var expandedDimensions = options[options.headerName];
var path = options.path;
if (expandedDimensions) {
filters.push((function(dataItem) {
var expandValue;
for (var i = 0; i < path.length; i++) {
expandValue = expandedDimensions[i].selector(dataItem);
if (toComparable(expandValue, true) !== toComparable(path[i], true)) {
return false
}
}
return true
}))
}
return function(dataItem) {
for (var i = 0; i < filters.length; i++) {
if (!filters[i](dataItem)) {
return false
}
}
return true
}
}
function loadCore(items, options, notifyProgress) {
var headers = {
columns: [],
rows: [],
columnsHash: {
length: 1
},
rowsHash: {
length: 1
}
};
var values = [];
var aggregationCells;
var data;
var d = new Deferred;
var i = 0;
var filter = createFilter(options);
! function processData() {
var t = new Date;
var startIndex = i;
for (; i < items.length; i++) {
if (i > startIndex && i % 1e4 === 0) {
if (new Date - t >= 300) {
notifyProgress(i / items.length);
setTimeout(processData, 0);
return
}
}
data = items[i];
if (filter(data)) {
aggregationCells = generateAggregationCells(data, values, headers, options);
aggregationStep(options.values, aggregationCells, data)
}
}
measures = options.values, cells = values, void each(measures, (function(aggregatorIndex, cellField) {
var aggregator = getAggregator(cellField);
if (aggregator.finalize) {
each(cells, (function(_, row) {
each(row, (function(_, cell) {
if (cell && void 0 !== cell[aggregatorIndex]) {
cell[aggregatorIndex] = aggregator.finalize(cell[aggregatorIndex])
}
}))
}))
}
}));
var measures, cells;
notifyProgress(1);
d.resolve({
rows: headers.rows,
columns: headers.columns,
values: values,
grandTotalRowIndex: 0,
grandTotalColumnIndex: 0
})
}();
return d
}
function filterDataSource(dataSource, fieldSelectors) {
var filter = dataSource.filter();
if (dataSource.store() instanceof CustomStore && filter) {
filter = processFilter(filter, fieldSelectors);
return dataQuery(dataSource.items()).filter(filter).toArray()
}
return dataSource.items()
}
function loadDataSource(dataSource, fieldSelectors, reload) {
var d = new Deferred;
var customizeStoreLoadOptionsHandler = function(options) {
if (dataSource.store() instanceof ArrayStore) {
options.storeLoadOptions.filter = processFilter(options.storeLoadOptions.filter, fieldSelectors)
}
};
dataSource.on("customizeStoreLoadOptions", customizeStoreLoadOptionsHandler);
if (!dataSource.isLoaded() || reload) {
var loadDeferred = reload ? dataSource.load() : dataSource.reload();
when(loadDeferred).done((function() {
loadDataSource(dataSource, fieldSelectors).done((function() {
d.resolve(filterDataSource(dataSource, fieldSelectors))
})).fail(d.reject)
})).fail(d.reject)
} else {
d.resolve(filterDataSource(dataSource, fieldSelectors))
}
return d.always((function() {
dataSource.off("customizeStoreLoadOptions", customizeStoreLoadOptionsHandler)
}))
}
function fillSelectorsByFields(selectors, fields) {
fields.forEach((function(field) {
if (field.dataField && "date" === field.dataType) {
var valueSelector = getDateValue(getDataSelector(field.dataField));
selectors[field.dataField] = function(data) {
return valueSelector(data)
}
}
}))
}
function getFieldSelectors(options) {
var selectors = {};
if (Array.isArray(options)) {
fillSelectorsByFields(selectors, options)
} else if (options) {
["rows", "columns", "filters"].forEach((function(area) {
options[area] && fillSelectorsByFields(selectors, options[area])
}))
}
return selectors
}
function processFilter(filter, fieldSelectors) {
if (!Array.isArray(filter)) {
return filter
}
filter = filter.slice(0);
if (isString(filter[0]) && (filter[1] instanceof Date || filter[2] instanceof Date)) {
filter[0] = fieldSelectors[filter[0]]
}
for (var i = 0; i < filter.length; i++) {
filter[i] = processFilter(filter[i], fieldSelectors)
}
return filter
}
return {
ctor: function(options) {
this._progressChanged = options.onProgressChanged || noop;
this._dataSource = new DataSource(options);
this._dataSource.paginate(false)
},
getFields: function(fields) {
var dataSource = this._dataSource;
var d = new Deferred;
loadDataSource(dataSource, getFieldSelectors(fields)).done((function(data) {
d.resolve(discoverObjectFields(data, fields))
})).fail(d.reject);
return d
},
key: function() {
return this._dataSource.key()
},
load: function(options) {
var that = this;
var dataSource = that._dataSource;
var d = new Deferred;
prepareLoadOption(options);
loadDataSource(dataSource, getFieldSelectors(options), options.reload).done((function(data) {
when(loadCore(data, options, that._progressChanged)).done(d.resolve)
})).fail(d.reject);
return d
},
filter: function() {
var dataSource = this._dataSource;
return dataSource.filter.apply(dataSource, arguments)
},
supportPaging: function() {
return false
},
getDrillDownItems: function(loadOptions, params) {
loadOptions = loadOptions || {};
params = params || {};
prepareLoadOption(loadOptions);
var drillDownItems = [];
var items = this._dataSource.items();
var item;
var maxRowCount = params.maxRowCount;
var customColumns = params.customColumns;
var filter = createFilter(loadOptions);
var pathFilter = createFilter({
rows: getFiltersByPath(loadOptions.rows, params.rowPath),
columns: getFiltersByPath(loadOptions.columns, params.columnPath),
filters: []
});
for (var i = 0; i < items.length; i++) {
if (pathFilter(items[i]) && filter(items[i])) {
if (customColumns) {
item = {};
for (var j = 0; j < customColumns.length; j++) {
item[customColumns[j]] = items[i][customColumns[j]]
}
} else {
item = items[i]
}
drillDownItems.push(item)
}
if (maxRowCount > 0 && drillDownItems.length === maxRowCount) {
break
}
}
return drillDownItems
}
}
}()).include(storeDrillDownMixin);