pragma-views2
Version:
519 lines (428 loc) • 14.7 kB
JavaScript
class GroupWorker {
/**
* constructor
*/
constructor() {
this.createCacheHandler = this.createCache.bind(this);
this.createGroupPerspectiveHandler = this.createGroupPerspective.bind(this);
this.disposeGroupPerspectiveHandler = this.disposeGroupPerspective.bind(this);
this.disposeCacheHandler = this.disposeCache.bind(this);
this.getGroupPerspectiveHandler = this.getGroupPerspective.bind(this);
this.getRecordsForHandler = this.getRecordsFor.bind(this);
this.appendCacheHandler = this.appendCache.bind(this);
this.functionMap = new Map();
this.functionMap.set("createCache", this.createCacheHandler);
this.functionMap.set("appendCache", this.appendCacheHandler);
this.functionMap.set("createGroupPerspective", this.createGroupPerspectiveHandler);
this.functionMap.set("disposeGroupPerspective", this.disposeGroupPerspectiveHandler);
this.functionMap.set("disposeCache", this.disposeCacheHandler);
this.functionMap.set("getGroupPerspective", this.getGroupPerspectiveHandler);
this.functionMap.set("getRecordsFor", this.getRecordsForHandler);
this.dataCache = new Map();
}
/**
* dispose
*/
dispose() {
this.createCacheHandler = null;
this.createGroupPerspectiveHandler = null;
this.disposeGroupPerspectiveHandler = null;
this.disposeCacheHandler = null;
this.getGroupPerspectiveHandler = null;
this.getRecordsForHandler = null;
this.functionMap.clear();
this.functionMap = null;
}
/**
* process all incomming messages mapping it to handlers
* @param args
*/
onMessage(args) {
if (this.functionMap.has(args.msg)) {
this.functionMap.get(args.msg)(args);
}
}
/**
* create cache from data sent to worker
* @param args
*/
createCache(args) {
if (this.dataCache.has(args.id)) {
const dataCache = this.dataCache.get(args.id);
dataCache.data = args.data;
dataCache.updateAllPerspectives();
}
else {
this.dataCache.set(args.id, new DataCache(args.id, args.data));
}
postMessage({
msg: "getRecordsForResponse",
id: args.id,
data: args.data
})
}
/**
* Append existing cache with new records
* @param args
*/
appendCache(args) {
if (this.dataCache.has(args.id)) {
this.dataCache.get(args.id).append(args.data);
postMessage({
msg: "appendRecordsForResponse",
id: args.id,
data: args.data
})
}
}
/**
* create a group perspective for data cached
* @param args
*/
createGroupPerspective(args) {
const id = args.id;
const perspectiveId = args.perspectiveId;
const fieldsToGroup = args.fieldsToGroup;
const aggegateOptions = args.aggegateOptions;
const sortOptions = args.sortOptions;
const data = args.data;
let dataCache = this.dataCache.get(id);
if (dataCache == undefined) {
dataCache = new DataCache(args.id, args.data);
this.dataCache.set(id, dataCache);
}
else {
if (data != undefined) {
dataCache.setData(data);
}
}
let perspective = dataCache.getPerspective(perspectiveId);
if (perspective == null) {
perspective = dataCache.createPerspective(perspectiveId, fieldsToGroup, aggegateOptions, sortOptions);
}
return perspective;
}
/**
* remove perspective
* @param args
*/
disposeGroupPerspective(args) {
const id = args.id;
const perspectiveId = args.perspectiveId;
if (this.dataCache.has(id)) {
const cache = this.dataCache.get(id);
cache.disposePerspective(perspectiveId);
}
}
/**
* remove perspectives and cache
* @param args
*/
disposeCache(args) {
const id = args.id;
if (this.dataCache.has(id)) {
const cache = this.dataCache.get(id);
cache.dispose();
this.dataCache.delete(id);
}
}
/**
* Return existing perspective
* @param args
*/
getGroupPerspective(args){
const id = args.id;
const perspectiveId = args.perspectiveId;
if (this.dataCache.has(id)) {
const cache = this.dataCache.get(id);
const perspective = cache.getPerspective(perspectiveId);
postMessage({
msg: "getGroupPerspectiveResponse",
id: id,
perspectiveId: perspectiveId,
data: perspective
})
}
}
/**
* Filter records based on args argument
* @params args
*/
getRecordsFor(args){
const id = args.id;
const filters = args.filters;
if (this.dataCache.has(id)) {
const cache = this.dataCache.get(id);
const items = cache.getRecordsFor(cache.data, filters);
postMessage({
msg: "getRecordsForResponse",
id: id,
data: items
})
}
}
}
class DataCache {
constructor(id, data) {
this.id = id;
this.data = data;
this.perspectiveGrouping = new Map();
this.sortDirectionMap = new Map([
["ascending", this.evaluateAscending],
["descending", this.evaluateDescending]
])
}
dispose() {
this.data = null;
this.perspectiveGrouping.clear();
this.perspectiveGrouping = null;
this.sortDirectionMap.clear();
this.sortDirectionMap = null;
}
setData(data) {
this.data = data;
this.updateAllPerspectives();
}
append(data) {
this.data = this.data.concat(data);
this.updateAllPerspectives();
}
// process all perspectives again
updateAllPerspectives() {
this.perspectiveGrouping.forEach((value, key) => {
const definition = value.__definition;
this.createPerspective(definition.perspectiveId, definition.fieldsToGroup, definition.aggegateOptions, definition.sortOptions, true);
})
}
/**
* Remove a perspective from the cache
* @param perspectiveId
*/
disposePerspective(perspectiveId) {
if (this.perspectiveGrouping.has(perspectiveId)) {
this.perspectiveGrouping.delete(perspectiveId);
}
}
/**
* Return an existing perspective
* @param perspectiveId
*/
getPerspective(perspectiveId){
if (this.perspectiveGrouping.has(perspectiveId)) {
return this.perspectiveGrouping.get(perspectiveId);
}
return null;
}
/**
* Filter records based on filter array argument
* @param items
* @param filters
*/
getRecordsFor(items, filters)
{
if (!filters) {
return items;
}
let result = items.slice(0);
result = result.filter(function(el) {
for(var j = 0; j < filters.length; j++){
var fieldName = filters[j].fieldName;
var value = filters[j].value;
if(el[fieldName] != value){
return false;
}
}
return true;
});
return result;
}
/**
* Create a perspective and group
* @param perspectiveId
* @param fieldsToGroup
* @param aggegateOptions
*/
createPerspective(perspectiveId, fieldsToGroup, aggegateOptions, sortOptions, override) {
let result = this.getPerspective(perspectiveId);
if(!result || override == true)
{
const newPerspective = this.createPerspectiveGroup(fieldsToGroup, aggegateOptions, sortOptions);
newPerspective.__definition = {
perspectiveId: perspectiveId,
fieldsToGroup: fieldsToGroup,
aggegateOptions: aggegateOptions,
sortOptions, sortOptions
};
this.perspectiveGrouping.set(perspectiveId, newPerspective);
result = newPerspective;
}
postMessage({
msg: "createGroupPerspectiveResponse",
id: this.id,
perspectiveId: perspectiveId,
data: result
});
return result;
}
/**
* Create a grouped and aggregated perspective from the data and store it with a key so that I can access it at any time from other views
* More than one view can use the same perspective, a example of tha is the grid on master detail and the group chart on the list
* @param fieldsToGroup: what fields are used in this grouping to define the perspective
* @param aggegateOptions: what are the calculations that need to be made on the group
*/
createPerspectiveGroup(fieldsToGroup, aggegateOptions, sortOptions) {
const dataCopy = this.data.slice(0);
const root = {
level: 0,
title: "None",
items: dataCopy,
isGroup: true
};
this.groupRecursive(root, fieldsToGroup, aggegateOptions, sortOptions);
return root;
}
/**
* Recursivly group items of a group oject grouping it according to level and the field defined for that level.
* @param group: the group to process
* @param fieldsToGroup: what are the fields to use while grouping
* @param aggegateOptions: what aggregate calculations should be used
*/
groupRecursive(group, fieldsToGroup, aggegateOptions, sortOptions) {
if (fieldsToGroup == undefined || fieldsToGroup == null) {
return;
}
if (group.level > fieldsToGroup.length -1) {
group.aggregate = {
aggregate: aggegateOptions.aggregate,
value: aggregator[aggegateOptions.aggregate](group.items, aggegateOptions.field)
};
group.lowestGroup = true;
group.items = this.sortItems(group.items, sortOptions);
return;
}
group.groups = this.group(group.items, fieldsToGroup[group.level], group.level + 1, sortOptions);
const keys = group.groups.keys();
for(let key of keys) {
const childGroup = group.groups.get(key);
this.groupRecursive(childGroup, fieldsToGroup, aggegateOptions, sortOptions);
}
group.aggregate = {
aggregate: aggegateOptions.aggregate,
value: aggregator[aggegateOptions.aggregate](group.items, aggegateOptions.field)
};
group.items = Array.from(group.groups, items => items[1]);
delete group.groups;
}
/**
* Create a
* @param array
* @param fieldName
* @param level
* @returns {any|*}
*/
group(array, fieldName, level, sortOptions) {
const result = array.reduce((groupMap, curr) => {
const key = curr[fieldName];
const id = curr[fieldName];
const groupId = groupMap.size;
if (groupMap.has(key)) {
groupMap.get(key).items.push(curr);
}
else {
groupMap.set(key, {
level: level,
field: fieldName,
title: key ? key.toString() : "none",
id: id,
items: [curr],
index: groupId,
isGroup: true
})
}
return groupMap;
}, new Map());
if (sortOptions == undefined) {
return result;
}
return this.sortGroup(result, sortOptions);
}
sortGroup(map, sortOptions) {
const array = Array.from(map);
const direction = sortOptions[array[0][1].field];
if (direction == undefined) {
return map;
}
const evaluation = this.sortDirectionMap.get(direction);
array.sort((a, b) => {
return evaluation(a[1].title, b[1].title);
});
return new Map(array);
}
sortItems(array, sortOptions) {
if (sortOptions == undefined || array.length == 1) {
return array;
}
const fields = Object.keys(sortOptions);
return array.sort((a, b) => {
for (let field of fields) {
const direction = sortOptions[field];
const result = this.sortDirectionMap.get(direction)(a[field], b[field]);
if (result == 1) {
return 1;
}
}
return 0;
})
}
evaluateAscending(aValue, bValue) {
if (aValue == bValue) {
return 0;
}
return aValue < bValue ? -1 : 1;
}
evaluateDescending(aValue, bValue) {
if (aValue == bValue) {
return 0;
}
return aValue > bValue ? -1 : 1;
}
}
const aggregator = {
count(items) {
return items.length;
},
sum(items, field) {
let result = 0;
for(let item of items) {
result += item[field];
}
return result;
},
min(items, field) {
let result = items[0][field];
for(let item of items) {
if (item[field] < result) {
result = item[field];
}
}
return result;
},
max(items, field) {
let result = items[0][field];
for(let item of items) {
if (item[field] > result) {
result = item[field];
}
}
return result;
},
ave(items, field) {
let result = this.sum(items, field);
result = result / items.length;
return result;
}
};
const groupWorker = new GroupWorker();
onmessage = function(event) {
groupWorker.onMessage(event.data);
};