summaryrefslogtreecommitdiffstats
path: root/slick.dataview.js
diff options
context:
space:
mode:
authorMichael Leibman <michael.leibman@gmail.com>2012-01-17 12:29:57 -0800
committerMichael Leibman <mleibman@google.com>2012-01-17 12:29:57 -0800
commit061ac59fb6a4c09a49d5abe1fa86c26f9d13bf66 (patch)
tree94f65296e682881a84214cd3ed608cc991ccf666 /slick.dataview.js
parent6634f168da6a03ce36a4a63c83f9acd1c33cbd5d (diff)
downloadSlickGrid-061ac59fb6a4c09a49d5abe1fa86c26f9d13bf66.zip
SlickGrid-061ac59fb6a4c09a49d5abe1fa86c26f9d13bf66.tar.gz
SlickGrid-061ac59fb6a4c09a49d5abe1fa86c26f9d13bf66.tar.bz2
Reformatted code.
Diffstat (limited to 'slick.dataview.js')
-rw-r--r--slick.dataview.js1349
1 files changed, 687 insertions, 662 deletions
diff --git a/slick.dataview.js b/slick.dataview.js
index 9a4605c..790ec94 100644
--- a/slick.dataview.js
+++ b/slick.dataview.js
@@ -1,738 +1,763 @@
-(function($) {
- $.extend(true, window, {
- Slick: {
- Data: {
- DataView: DataView,
- Aggregators: {
- Avg: AvgAggregator,
- Min: MinAggregator,
- Max: MaxAggregator
- }
- }
- }
- });
-
-
- /***
- * A sample Model implementation.
- * Provides a filtered view of the underlying data.
- *
- * Relies on the data item having an "id" property uniquely identifying it.
- */
- function DataView(options) {
- var self = this;
-
- var defaults = {
- groupItemMetadataProvider: null
- };
-
-
- // private
- var idProperty = "id"; // property holding a unique row id
- var items = []; // data by index
- var rows = []; // data by row
- var idxById = {}; // indexes by id
- var rowsById = null; // rows by id; lazy-calculated
- var filter = null; // filter function
- var updated = null; // updated item ids
- var suspend = false; // suspends the recalculation
- var sortAsc = true;
- var fastSortField;
- var sortComparer;
- var refreshHints = {};
- var prevRefreshHints = {};
- var filterArgs;
- var filteredItems = [];
- var compiledFilter;
- var compiledFilterWithCaching;
- var filterCache = [];
-
- // grouping
- var groupingGetter;
- var groupingGetterIsAFn;
- var groupingFormatter;
- var groupingComparer;
- var groups = [];
- var collapsedGroups = {};
- var aggregators;
- var aggregateCollapsed = false;
- var compiledAccumulators;
-
- var pagesize = 0;
- var pagenum = 0;
- var totalRows = 0;
-
- // events
- var onRowCountChanged = new Slick.Event();
- var onRowsChanged = new Slick.Event();
- var onPagingInfoChanged = new Slick.Event();
-
- options = $.extend(true, {}, defaults, options);
-
-
- function beginUpdate() {
- suspend = true;
- }
+(function ($) {
+ $.extend(true, window, {
+ Slick:{
+ Data:{
+ DataView:DataView,
+ Aggregators:{
+ Avg:AvgAggregator,
+ Min:MinAggregator,
+ Max:MaxAggregator
+ }
+ }
+ }
+ });
+
+
+ /***
+ * A sample Model implementation.
+ * Provides a filtered view of the underlying data.
+ *
+ * Relies on the data item having an "id" property uniquely identifying it.
+ */
+ function DataView(options) {
+ var self = this;
+
+ var defaults = {
+ groupItemMetadataProvider:null
+ };
+
+
+ // private
+ var idProperty = "id"; // property holding a unique row id
+ var items = []; // data by index
+ var rows = []; // data by row
+ var idxById = {}; // indexes by id
+ var rowsById = null; // rows by id; lazy-calculated
+ var filter = null; // filter function
+ var updated = null; // updated item ids
+ var suspend = false; // suspends the recalculation
+ var sortAsc = true;
+ var fastSortField;
+ var sortComparer;
+ var refreshHints = {};
+ var prevRefreshHints = {};
+ var filterArgs;
+ var filteredItems = [];
+ var compiledFilter;
+ var compiledFilterWithCaching;
+ var filterCache = [];
+
+ // grouping
+ var groupingGetter;
+ var groupingGetterIsAFn;
+ var groupingFormatter;
+ var groupingComparer;
+ var groups = [];
+ var collapsedGroups = {};
+ var aggregators;
+ var aggregateCollapsed = false;
+ var compiledAccumulators;
+
+ var pagesize = 0;
+ var pagenum = 0;
+ var totalRows = 0;
+
+ // events
+ var onRowCountChanged = new Slick.Event();
+ var onRowsChanged = new Slick.Event();
+ var onPagingInfoChanged = new Slick.Event();
+
+ options = $.extend(true, {}, defaults, options);
+
+
+ function beginUpdate() {
+ suspend = true;
+ }
- function endUpdate() {
- suspend = false;
- refresh();
- }
+ function endUpdate() {
+ suspend = false;
+ refresh();
+ }
- function setRefreshHints(hints){
- refreshHints = hints;
- }
+ function setRefreshHints(hints) {
+ refreshHints = hints;
+ }
- function setFilterArgs(args) {
- filterArgs = args;
- }
+ function setFilterArgs(args) {
+ filterArgs = args;
+ }
- function updateIdxById(startingIndex) {
- startingIndex = startingIndex || 0;
- var id;
- for (var i = startingIndex, l = items.length; i < l; i++) {
- id = items[i][idProperty];
- if (id === undefined) {
- throw "Each data element must implement a unique 'id' property";
- }
- idxById[id] = i;
- }
+ function updateIdxById(startingIndex) {
+ startingIndex = startingIndex || 0;
+ var id;
+ for (var i = startingIndex, l = items.length; i < l; i++) {
+ id = items[i][idProperty];
+ if (id === undefined) {
+ throw "Each data element must implement a unique 'id' property";
}
+ idxById[id] = i;
+ }
+ }
- function ensureIdUniqueness() {
- var id;
- for (var i = 0, l = items.length; i < l; i++) {
- id = items[i][idProperty];
- if (id === undefined || idxById[id] !== i) {
- throw "Each data element must implement a unique 'id' property";
- }
- }
+ function ensureIdUniqueness() {
+ var id;
+ for (var i = 0, l = items.length; i < l; i++) {
+ id = items[i][idProperty];
+ if (id === undefined || idxById[id] !== i) {
+ throw "Each data element must implement a unique 'id' property";
}
+ }
+ }
- function getItems() {
- return items;
- }
+ function getItems() {
+ return items;
+ }
- function setItems(data, objectIdProperty) {
- if (objectIdProperty !== undefined) idProperty = objectIdProperty;
- items = filteredItems = data;
- idxById = {};
- updateIdxById();
- ensureIdUniqueness();
- refresh();
- }
+ function setItems(data, objectIdProperty) {
+ if (objectIdProperty !== undefined) {
+ idProperty = objectIdProperty;
+ }
+ items = filteredItems = data;
+ idxById = {};
+ updateIdxById();
+ ensureIdUniqueness();
+ refresh();
+ }
- function setPagingOptions(args) {
- if (args.pageSize != undefined)
- pagesize = args.pageSize;
+ function setPagingOptions(args) {
+ if (args.pageSize != undefined) {
+ pagesize = args.pageSize;
+ }
- if (args.pageNum != undefined)
- pagenum = Math.min(args.pageNum, Math.ceil(totalRows / pagesize));
+ if (args.pageNum != undefined) {
+ pagenum = Math.min(args.pageNum, Math.ceil(totalRows / pagesize));
+ }
- onPagingInfoChanged.notify(getPagingInfo(), null, self);
+ onPagingInfoChanged.notify(getPagingInfo(), null, self);
- refresh();
- }
+ refresh();
+ }
- function getPagingInfo() {
- return {pageSize:pagesize, pageNum:pagenum, totalRows:totalRows};
- }
+ function getPagingInfo() {
+ return {pageSize:pagesize, pageNum:pagenum, totalRows:totalRows};
+ }
- function sort(comparer, ascending) {
- sortAsc = ascending;
- sortComparer = comparer;
- fastSortField = null;
- if (ascending === false) items.reverse();
- items.sort(comparer);
- if (ascending === false) items.reverse();
- idxById = {};
- updateIdxById();
- refresh();
- }
+ function sort(comparer, ascending) {
+ sortAsc = ascending;
+ sortComparer = comparer;
+ fastSortField = null;
+ if (ascending === false) {
+ items.reverse();
+ }
+ items.sort(comparer);
+ if (ascending === false) {
+ items.reverse();
+ }
+ idxById = {};
+ updateIdxById();
+ refresh();
+ }
- /***
- * Provides a workaround for the extremely slow sorting in IE.
- * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString
- * to return the value of that field and then doing a native Array.sort().
- */
- function fastSort(field, ascending) {
- sortAsc = ascending;
- fastSortField = field;
- sortComparer = null;
- var oldToString = Object.prototype.toString;
- Object.prototype.toString = (typeof field == "function")?field:function() { return this[field] };
- // an extra reversal for descending sort keeps the sort stable
- // (assuming a stable native sort implementation, which isn't true in some cases)
- if (ascending === false) items.reverse();
- items.sort();
- Object.prototype.toString = oldToString;
- if (ascending === false) items.reverse();
- idxById = {};
- updateIdxById();
- refresh();
- }
+ /***
+ * Provides a workaround for the extremely slow sorting in IE.
+ * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString
+ * to return the value of that field and then doing a native Array.sort().
+ */
+ function fastSort(field, ascending) {
+ sortAsc = ascending;
+ fastSortField = field;
+ sortComparer = null;
+ var oldToString = Object.prototype.toString;
+ Object.prototype.toString = (typeof field == "function") ? field : function () {
+ return this[field]
+ };
+ // an extra reversal for descending sort keeps the sort stable
+ // (assuming a stable native sort implementation, which isn't true in some cases)
+ if (ascending === false) {
+ items.reverse();
+ }
+ items.sort();
+ Object.prototype.toString = oldToString;
+ if (ascending === false) {
+ items.reverse();
+ }
+ idxById = {};
+ updateIdxById();
+ refresh();
+ }
- function reSort() {
- if (sortComparer) {
- sort(sortComparer, sortAsc);
- }
- else if (fastSortField) {
- fastSort(fastSortField, sortAsc);
- }
- }
+ function reSort() {
+ if (sortComparer) {
+ sort(sortComparer, sortAsc);
+ }
+ else if (fastSortField) {
+ fastSort(fastSortField, sortAsc);
+ }
+ }
- function setFilter(filterFn) {
- filter = filterFn;
- compiledFilter = compileFilter();
- compiledFilterWithCaching = compileFilterWithCaching();
- refresh();
- }
+ function setFilter(filterFn) {
+ filter = filterFn;
+ compiledFilter = compileFilter();
+ compiledFilterWithCaching = compileFilterWithCaching();
+ refresh();
+ }
- function groupBy(valueGetter, valueFormatter, sortComparer) {
- if (!options.groupItemMetadataProvider) {
- options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider();
- }
-
- groupingGetter = valueGetter;
- groupingGetterIsAFn = typeof groupingGetter === "function";
- groupingFormatter = valueFormatter;
- groupingComparer = sortComparer;
- collapsedGroups = {};
- groups = [];
- refresh();
- }
+ function groupBy(valueGetter, valueFormatter, sortComparer) {
+ if (!options.groupItemMetadataProvider) {
+ options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider();
+ }
+
+ groupingGetter = valueGetter;
+ groupingGetterIsAFn = typeof groupingGetter === "function";
+ groupingFormatter = valueFormatter;
+ groupingComparer = sortComparer;
+ collapsedGroups = {};
+ groups = [];
+ refresh();
+ }
- function setAggregators(groupAggregators, includeCollapsed) {
- aggregators = groupAggregators;
- aggregateCollapsed = (includeCollapsed !== undefined)
- ? includeCollapsed : aggregateCollapsed;
+ function setAggregators(groupAggregators, includeCollapsed) {
+ aggregators = groupAggregators;
+ aggregateCollapsed = (includeCollapsed !== undefined)
+ ? includeCollapsed : aggregateCollapsed;
- // pre-compile accumulator loops
- compiledAccumulators = [];
- var idx = aggregators.length;
- while (idx--) {
- compiledAccumulators[idx] = compileAccumulatorLoop(aggregators[idx]);
- }
+ // pre-compile accumulator loops
+ compiledAccumulators = [];
+ var idx = aggregators.length;
+ while (idx--) {
+ compiledAccumulators[idx] = compileAccumulatorLoop(aggregators[idx]);
+ }
- refresh();
- }
+ refresh();
+ }
- function getItemByIdx(i) {
- return items[i];
- }
+ function getItemByIdx(i) {
+ return items[i];
+ }
- function getIdxById(id) {
- return idxById[id];
- }
+ function getIdxById(id) {
+ return idxById[id];
+ }
- function ensureRowsByIdCache() {
- if (!rowsById) {
- rowsById = {};
- for (var i = 0, l = rows.length; i < l; i++) {
- rowsById[rows[i][idProperty]] = i;
- }
- }
+ function ensureRowsByIdCache() {
+ if (!rowsById) {
+ rowsById = {};
+ for (var i = 0, l = rows.length; i < l; i++) {
+ rowsById[rows[i][idProperty]] = i;
}
+ }
+ }
- function getRowById(id) {
- ensureRowsByIdCache();
- return rowsById[id];
- }
+ function getRowById(id) {
+ ensureRowsByIdCache();
+ return rowsById[id];
+ }
- function getItemById(id) {
- return items[idxById[id]];
- }
+ function getItemById(id) {
+ return items[idxById[id]];
+ }
- function updateItem(id, item) {
- if (idxById[id] === undefined || id !== item[idProperty])
- throw "Invalid or non-matching id";
- items[idxById[id]] = item;
- if (!updated) updated = {};
- updated[id] = true;
- refresh();
- }
+ function updateItem(id, item) {
+ if (idxById[id] === undefined || id !== item[idProperty]) {
+ throw "Invalid or non-matching id";
+ }
+ items[idxById[id]] = item;
+ if (!updated) {
+ updated = {};
+ }
+ updated[id] = true;
+ refresh();
+ }
- function insertItem(insertBefore, item) {
- items.splice(insertBefore, 0, item);
- updateIdxById(insertBefore);
- refresh();
- }
+ function insertItem(insertBefore, item) {
+ items.splice(insertBefore, 0, item);
+ updateIdxById(insertBefore);
+ refresh();
+ }
- function addItem(item) {
- items.push(item);
- updateIdxById(items.length - 1);
- refresh();
- }
+ function addItem(item) {
+ items.push(item);
+ updateIdxById(items.length - 1);
+ refresh();
+ }
- function deleteItem(id) {
- var idx = idxById[id];
- if (idx === undefined) {
- throw "Invalid id";
- }
- delete idxById[id];
- items.splice(idx, 1);
- updateIdxById(idx);
- refresh();
- }
+ function deleteItem(id) {
+ var idx = idxById[id];
+ if (idx === undefined) {
+ throw "Invalid id";
+ }
+ delete idxById[id];
+ items.splice(idx, 1);
+ updateIdxById(idx);
+ refresh();
+ }
- function getLength() {
- return rows.length;
- }
+ function getLength() {
+ return rows.length;
+ }
- function getItem(i) {
- return rows[i];
- }
+ function getItem(i) {
+ return rows[i];
+ }
- function getItemMetadata(i) {
- var item = rows[i];
- if (item === undefined) {
- return null;
- }
+ function getItemMetadata(i) {
+ var item = rows[i];
+ if (item === undefined) {
+ return null;
+ }
- // overrides for group rows
- if (item.__group) {
- return options.groupItemMetadataProvider.getGroupRowMetadata(item);
- }
+ // overrides for group rows
+ if (item.__group) {
+ return options.groupItemMetadataProvider.getGroupRowMetadata(item);
+ }
- // overrides for totals rows
- if (item.__groupTotals) {
- return options.groupItemMetadataProvider.getTotalsRowMetadata(item);
- }
+ // overrides for totals rows
+ if (item.__groupTotals) {
+ return options.groupItemMetadataProvider.getTotalsRowMetadata(item);
+ }
- return null;
- }
+ return null;
+ }
- function collapseGroup(groupingValue) {
- collapsedGroups[groupingValue] = true;
- refresh();
- }
+ function collapseGroup(groupingValue) {
+ collapsedGroups[groupingValue] = true;
+ refresh();
+ }
- function expandGroup(groupingValue) {
- delete collapsedGroups[groupingValue];
- refresh();
- }
+ function expandGroup(groupingValue) {
+ delete collapsedGroups[groupingValue];
+ refresh();
+ }
- function getGroups() {
- return groups;
- }
+ function getGroups() {
+ return groups;
+ }
- function extractGroups(rows) {
- var group;
- var val;
- var groups = [];
- var groupsByVal = [];
- var r;
-
- for (var i = 0, l = rows.length; i < l; i++) {
- r = rows[i];
- val = (groupingGetterIsAFn) ? groupingGetter(r) : r[groupingGetter];
- val = val || 0;
- group = groupsByVal[val];
- if (!group) {
- group = new Slick.Group();
- group.count = 0;
- group.value = val;
- group.rows = [];
- groups[groups.length] = group;
- groupsByVal[val] = group;
- }
-
- group.rows[group.count++] = r;
- }
-
- return groups;
- }
+ function extractGroups(rows) {
+ var group;
+ var val;
+ var groups = [];
+ var groupsByVal = [];
+ var r;
+
+ for (var i = 0, l = rows.length; i < l; i++) {
+ r = rows[i];
+ val = (groupingGetterIsAFn) ? groupingGetter(r) : r[groupingGetter];
+ val = val || 0;
+ group = groupsByVal[val];
+ if (!group) {
+ group = new Slick.Group();
+ group.count = 0;
+ group.value = val;
+ group.rows = [];
+ groups[groups.length] = group;
+ groupsByVal[val] = group;
+ }
+
+ group.rows[group.count++] = r;
+ }
+
+ return groups;
+ }
- // TODO: lazy totals calculation
- function calculateGroupTotals(group) {
- if (group.collapsed && !aggregateCollapsed) {
- return;
- }
-
- // TODO: try moving iterating over groups into compiled accumulator
- var totals = new Slick.GroupTotals();
- var agg, idx = aggregators.length;
- while (idx--) {
- agg = aggregators[idx];
- agg.init();
- compiledAccumulators[idx].call(agg, group.rows);
- agg.storeResult(totals);
- }
- totals.group = group;
- group.totals = totals;
- }
+ // TODO: lazy totals calculation
+ function calculateGroupTotals(group) {
+ if (group.collapsed && !aggregateCollapsed) {
+ return;
+ }
+
+ // TODO: try moving iterating over groups into compiled accumulator
+ var totals = new Slick.GroupTotals();
+ var agg, idx = aggregators.length;
+ while (idx--) {
+ agg = aggregators[idx];
+ agg.init();
+ compiledAccumulators[idx].call(agg, group.rows);
+ agg.storeResult(totals);
+ }
+ totals.group = group;
+ group.totals = totals;
+ }
- function calculateTotals(groups) {
- var idx = groups.length;
- while (idx--) {
- calculateGroupTotals(groups[idx]);
- }
- }
+ function calculateTotals(groups) {
+ var idx = groups.length;
+ while (idx--) {
+ calculateGroupTotals(groups[idx]);
+ }
+ }
- function finalizeGroups(groups) {
- var idx = groups.length, g;
- while (idx--) {
- g = groups[idx];
- g.collapsed = (g.value in collapsedGroups);
- g.title = groupingFormatter ? groupingFormatter(g) : g.value;
- }
- }
+ function finalizeGroups(groups) {
+ var idx = groups.length, g;
+ while (idx--) {
+ g = groups[idx];
+ g.collapsed = (g.value in collapsedGroups);
+ g.title = groupingFormatter ? groupingFormatter(g) : g.value;
+ }
+ }
- function flattenGroupedRows(groups) {
- var groupedRows = [], gl = 0, g;
- for (var i = 0, l = groups.length; i < l; i++) {
- g = groups[i];
- groupedRows[gl++] = g;
-
- if (!g.collapsed) {
- for (var j = 0, jj = g.rows.length; j < jj; j++) {
- groupedRows[gl++] = g.rows[j];
- }
- }
-
- if (g.totals && (!g.collapsed || aggregateCollapsed)) {
- groupedRows[gl++] = g.totals;
- }
- }
- return groupedRows;
- }
+ function flattenGroupedRows(groups) {
+ var groupedRows = [], gl = 0, g;
+ for (var i = 0, l = groups.length; i < l; i++) {
+ g = groups[i];
+ groupedRows[gl++] = g;
- function getFunctionInfo(fn) {
- var fnRegex = /^function[^(]*\(([^)]*)\)\s*{([\s\S]*)}$/;
- var matches = fn.toString().match(fnRegex);
- return {
- params: matches[1].split(","),
- body: matches[2]
- };
+ if (!g.collapsed) {
+ for (var j = 0, jj = g.rows.length; j < jj; j++) {
+ groupedRows[gl++] = g.rows[j];
+ }
}
- function compileAccumulatorLoop(aggregator) {
- var accumulatorInfo = getFunctionInfo(aggregator.accumulate);
- var fn = new Function(
- "_items",
- "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" +
- accumulatorInfo.params[0] + " = _items[_i]; " +
- accumulatorInfo.body +
- "}"
- );
- fn.displayName = fn.name = "compiledAccumulatorLoop";
- return fn;
- }
-
- function compileFilter() {
- var filterInfo = getFunctionInfo(filter);
-
- var filterBody = filterInfo.body
- .replace(/return false;/gi, "{ continue _coreloop; }")
- .replace(/return true;/gi, "{ _retval[_idx++] = $item$; continue _coreloop; }")
- .replace(/return ([^;]+?);/gi,
- "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }");
-
- // This preserves the function template code after JS compression,
- // so that replace() commands still work as expected.
- var tpl = [
- //"function(_items, _args) { ",
- "var _retval = [], _idx = 0; ",
- "var $item$, $args$ = _args; ",
- "_coreloop: ",
- "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
- "$item$ = _items[_i]; ",
- "$filter$; ",
- "} ",
- "return _retval; "
- //"}"
- ].join("");
- tpl = tpl.replace(/\$filter\$/gi, filterBody);
- tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
- tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
-
- var fn = new Function("_items,_args", tpl);
- fn.displayName = fn.name = "compiledFilter";
- return fn;
+ if (g.totals && (!g.collapsed || aggregateCollapsed)) {
+ groupedRows[gl++] = g.totals;
}
+ }
+ return groupedRows;
+ }
- function compileFilterWithCaching() {
- var filterInfo = getFunctionInfo(filter);
-
- var filterBody = filterInfo.body
- .replace(/return false;/gi, "{ continue _coreloop; }")
- .replace(/return true;/gi, "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }")
- .replace(/return ([^;]+?);/gi,
- "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }");
-
- // This preserves the function template code after JS compression,
- // so that replace() commands still work as expected.
- var tpl = [
- //"function(_items, _args, _cache) { ",
- "var _retval = [], _idx = 0; ",
- "var $item$, $args$ = _args; ",
- "_coreloop: ",
- "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
- "$item$ = _items[_i]; ",
- "if (_cache[_i]) { ",
- "_retval[_idx++] = $item$; ",
- "continue _coreloop; ",
- "} ",
- "$filter$; ",
- "} ",
- "return _retval; "
- //"}"
- ].join("");
- tpl = tpl.replace(/\$filter\$/gi, filterBody);
- tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
- tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
-
- var fn = new Function("_items,_args,_cache", tpl);
- fn.displayName = fn.name = "compiledFilterWithCaching";
- return fn;
- }
+ function getFunctionInfo(fn) {
+ var fnRegex = /^function[^(]*\(([^)]*)\)\s*{([\s\S]*)}$/;
+ var matches = fn.toString().match(fnRegex);
+ return {
+ params:matches[1].split(","),
+ body:matches[2]
+ };
+ }
- function getFilteredAndPagedItems(items) {
- if (filter && !refreshHints.isFilterUnchanged) {
- if (refreshHints.isFilterNarrowing) {
- filteredItems = compiledFilter(filteredItems, filterArgs);
- } else if (refreshHints.isFilterExpanding) {
- filteredItems = compiledFilterWithCaching(items, filterArgs, filterCache);
- } else {
- filteredItems = compiledFilter(items, filterArgs);
- }
- } else {
- // special case: if not filtering and not paging, the resulting
- // rows collection needs to be a copy so that changes due to sort
- // can be caught
- filteredItems = pagesize ? items : items.concat();
- }
-
- // get the current page
- var paged;
- if (pagesize) {
- if (filteredItems.length < pagenum * pagesize) {
- pagenum = Math.floor(filteredItems.length / pagesize);
- }
- paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize);
- } else {
- paged = filteredItems;
- }
-
- return {totalRows:filteredItems.length, rows:paged};
- }
+ function compileAccumulatorLoop(aggregator) {
+ var accumulatorInfo = getFunctionInfo(aggregator.accumulate);
+ var fn = new Function(
+ "_items",
+ "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" +
+ accumulatorInfo.params[0] + " = _items[_i]; " +
+ accumulatorInfo.body +
+ "}"
+ );
+ fn.displayName = fn.name = "compiledAccumulatorLoop";
+ return fn;
+ }
- function getRowDiffs(rows, newRows) {
- var item, r, eitherIsNonData, diff = [];
- var from = 0, to = newRows.length;
-
- if (refreshHints && refreshHints.ignoreDiffsBefore) {
- from = Math.max(0,
- Math.min(newRows.length, refreshHints.ignoreDiffsBefore));
- }
-
- if (refreshHints && refreshHints.ignoreDiffsAfter) {
- to = Math.min(newRows.length,
- Math.max(0, refreshHints.ignoreDiffsAfter));
- }
-
- for (var i = from, rl = rows.length; i < to; i++) {
- if (i >= rl) {
- diff[diff.length] = i;
- }
- else {
- item = newRows[i];
- r = rows[i];
-
- if ((groupingGetter && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) &&
- item.__group !== r.__group ||
- item.__updated ||
- item.__group && !item.equals(r))
- || (aggregators && eitherIsNonData &&
- // no good way to compare totals since they are arbitrary DTOs
- // deep object comparison is pretty expensive
- // always considering them 'dirty' seems easier for the time being
- (item.__groupTotals || r.__groupTotals))
- || item[idProperty] != r[idProperty]
- || (updated && updated[item[idProperty]])
- ) {
- diff[diff.length] = i;
- }
- }
- }
- return diff;
- }
+ function compileFilter() {
+ var filterInfo = getFunctionInfo(filter);
+
+ var filterBody = filterInfo.body
+ .replace(/return false;/gi, "{ continue _coreloop; }")
+ .replace(/return true;/gi, "{ _retval[_idx++] = $item$; continue _coreloop; }")
+ .replace(/return ([^;]+?);/gi,
+ "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }");
+
+ // This preserves the function template code after JS compression,
+ // so that replace() commands still work as expected.
+ var tpl = [
+ //"function(_items, _args) { ",
+ "var _retval = [], _idx = 0; ",
+ "var $item$, $args$ = _args; ",
+ "_coreloop: ",
+ "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
+ "$item$ = _items[_i]; ",
+ "$filter$; ",
+ "} ",
+ "return _retval; "
+ //"}"
+ ].join("");
+ tpl = tpl.replace(/\$filter\$/gi, filterBody);
+ tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
+ tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
+
+ var fn = new Function("_items,_args", tpl);
+ fn.displayName = fn.name = "compiledFilter";
+ return fn;
+ }
- function recalc(_items) {
- rowsById = null;
+ function compileFilterWithCaching() {
+ var filterInfo = getFunctionInfo(filter);
+
+ var filterBody = filterInfo.body
+ .replace(/return false;/gi, "{ continue _coreloop; }")
+ .replace(/return true;/gi, "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }")
+ .replace(/return ([^;]+?);/gi,
+ "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }");
+
+ // This preserves the function template code after JS compression,
+ // so that replace() commands still work as expected.
+ var tpl = [
+ //"function(_items, _args, _cache) { ",
+ "var _retval = [], _idx = 0; ",
+ "var $item$, $args$ = _args; ",
+ "_coreloop: ",
+ "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
+ "$item$ = _items[_i]; ",
+ "if (_cache[_i]) { ",
+ "_retval[_idx++] = $item$; ",
+ "continue _coreloop; ",
+ "} ",
+ "$filter$; ",
+ "} ",
+ "return _retval; "
+ //"}"
+ ].join("");
+ tpl = tpl.replace(/\$filter\$/gi, filterBody);
+ tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
+ tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
+
+ var fn = new Function("_items,_args,_cache", tpl);
+ fn.displayName = fn.name = "compiledFilterWithCaching";
+ return fn;
+ }
- if (refreshHints.isFilterNarrowing != prevRefreshHints.isFilterNarrowing ||
- refreshHints.isFilterExpanding != prevRefreshHints.isFilterExpanding) {
- filterCache = [];
- }
+ function getFilteredAndPagedItems(items) {
+ if (filter && !refreshHints.isFilterUnchanged) {
+ if (refreshHints.isFilterNarrowing) {
+ filteredItems = compiledFilter(filteredItems, filterArgs);
+ } else if (refreshHints.isFilterExpanding) {
+ filteredItems = compiledFilterWithCaching(items, filterArgs, filterCache);
+ } else {
+ filteredItems = compiledFilter(items, filterArgs);
+ }
+ } else {
+ // special case: if not filtering and not paging, the resulting
+ // rows collection needs to be a copy so that changes due to sort
+ // can be caught
+ filteredItems = pagesize ? items : items.concat();
+ }
+
+ // get the current page
+ var paged;
+ if (pagesize) {
+ if (filteredItems.length < pagenum * pagesize) {
+ pagenum = Math.floor(filteredItems.length / pagesize);
+ }
+ paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize);
+ } else {
+ paged = filteredItems;
+ }
+
+ return {totalRows:filteredItems.length, rows:paged};
+ }
- var filteredItems = getFilteredAndPagedItems(_items);
- totalRows = filteredItems.totalRows;
- var newRows = filteredItems.rows;
+ function getRowDiffs(rows, newRows) {
+ var item, r, eitherIsNonData, diff = [];
+ var from = 0, to = newRows.length;
+
+ if (refreshHints && refreshHints.ignoreDiffsBefore) {
+ from = Math.max(0,
+ Math.min(newRows.length, refreshHints.ignoreDiffsBefore));
+ }
+
+ if (refreshHints && refreshHints.ignoreDiffsAfter) {
+ to = Math.min(newRows.length,
+ Math.max(0, refreshHints.ignoreDiffsAfter));
+ }
+
+ for (var i = from, rl = rows.length; i < to; i++) {
+ if (i >= rl) {
+ diff[diff.length] = i;
+ }
+ else {
+ item = newRows[i];
+ r = rows[i];
+
+ if ((groupingGetter && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) &&
+ item.__group !== r.__group ||
+ item.__updated ||
+ item.__group && !item.equals(r))
+ || (aggregators && eitherIsNonData &&
+ // no good way to compare totals since they are arbitrary DTOs
+ // deep object comparison is pretty expensive
+ // always considering them 'dirty' seems easier for the time being
+ (item.__groupTotals || r.__groupTotals))
+ || item[idProperty] != r[idProperty]
+ || (updated && updated[item[idProperty]])
+ ) {
+ diff[diff.length] = i;
+ }
+ }
+ }
+ return diff;
+ }
- groups = [];
- if (groupingGetter != null) {
- groups = extractGroups(newRows);
- if (groups.length) {
- finalizeGroups(groups);
- if (aggregators) {
- calculateTotals(groups);
- }
- groups.sort(groupingComparer);
- newRows = flattenGroupedRows(groups);
- }
- }
+ function recalc(_items) {
+ rowsById = null;
- var diff = getRowDiffs(rows, newRows);
+ if (refreshHints.isFilterNarrowing != prevRefreshHints.isFilterNarrowing ||
+ refreshHints.isFilterExpanding != prevRefreshHints.isFilterExpanding) {
+ filterCache = [];
+ }
- rows = newRows;
+ var filteredItems = getFilteredAndPagedItems(_items);
+ totalRows = filteredItems.totalRows;
+ var newRows = filteredItems.rows;
- return diff;
+ groups = [];
+ if (groupingGetter != null) {
+ groups = extractGroups(newRows);
+ if (groups.length) {
+ finalizeGroups(groups);
+ if (aggregators) {
+ calculateTotals(groups);
+ }
+ groups.sort(groupingComparer);
+ newRows = flattenGroupedRows(groups);
}
+ }
- function refresh() {
- if (suspend) return;
+ var diff = getRowDiffs(rows, newRows);
- var countBefore = rows.length;
- var totalRowsBefore = totalRows;
+ rows = newRows;
- var diff = recalc(items, filter); // pass as direct refs to avoid closure perf hit
-
- // if the current page is no longer valid, go to last page and recalc
- // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
- if (pagesize && totalRows < pagenum * pagesize) {
- pagenum = Math.floor(totalRows / pagesize);
- diff = recalc(items, filter);
- }
-
- updated = null;
- prevRefreshHints = refreshHints;
- refreshHints = {};
+ return diff;
+ }
- if (totalRowsBefore != totalRows) onPagingInfoChanged.notify(getPagingInfo(), null, self);
- if (countBefore != rows.length) onRowCountChanged.notify({previous:countBefore, current:rows.length}, null, self);
- if (diff.length > 0) onRowsChanged.notify({rows:diff}, null, self);
- }
+ function refresh() {
+ if (suspend) {
+ return;
+ }
+
+ var countBefore = rows.length;
+ var totalRowsBefore = totalRows;
+
+ var diff = recalc(items, filter); // pass as direct refs to avoid closure perf hit
+
+ // if the current page is no longer valid, go to last page and recalc
+ // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
+ if (pagesize && totalRows < pagenum * pagesize) {
+ pagenum = Math.floor(totalRows / pagesize);
+ diff = recalc(items, filter);
+ }
+
+ updated = null;
+ prevRefreshHints = refreshHints;
+ refreshHints = {};
+
+ if (totalRowsBefore != totalRows) {
+ onPagingInfoChanged.notify(getPagingInfo(), null, self);
+ }
+ if (countBefore != rows.length) {
+ onRowCountChanged.notify({previous:countBefore, current:rows.length}, null, self);
+ }
+ if (diff.length > 0) {
+ onRowsChanged.notify({rows:diff}, null, self);
+ }
+ }
- return {
- // methods
- "beginUpdate": beginUpdate,
- "endUpdate": endUpdate,
- "setPagingOptions": setPagingOptions,
- "getPagingInfo": getPagingInfo,
- "getItems": getItems,
- "setItems": setItems,
- "setFilter": setFilter,
- "sort": sort,
- "fastSort": fastSort,
- "reSort": reSort,
- "groupBy": groupBy,
- "setAggregators": setAggregators,
- "collapseGroup": collapseGroup,
- "expandGroup": expandGroup,
- "getGroups": getGroups,
- "getIdxById": getIdxById,
- "getRowById": getRowById,
- "getItemById": getItemById,
- "getItemByIdx": getItemByIdx,
- "setRefreshHints": setRefreshHints,
- "setFilterArgs": setFilterArgs,
- "refresh": refresh,
- "updateItem": updateItem,
- "insertItem": insertItem,
- "addItem": addItem,
- "deleteItem": deleteItem,
-
- // data provider methods
- "getLength": getLength,
- "getItem": getItem,
- "getItemMetadata": getItemMetadata,
-
- // events
- "onRowCountChanged": onRowCountChanged,
- "onRowsChanged": onRowsChanged,
- "onPagingInfoChanged": onPagingInfoChanged
- };
- }
-
- function AvgAggregator(field) {
- this.field_ = field;
-
- this.init = function() {
- this.count_ = 0;
- this.nonNullCount_ = 0;
- this.sum_ = 0;
- };
-
- this.accumulate = function(item) {
- var val = item[this.field_];
- this.count_++;
- if (val != null && val != NaN) {
- this.nonNullCount_++;
- this.sum_ += 1 * val;
- }
- };
-
- this.storeResult = function(groupTotals) {
- if (!groupTotals.avg) {
- groupTotals.avg = {};
- }
- if (this.nonNullCount_ != 0) {
- groupTotals.avg[this.field_] = this.sum_ / this.nonNullCount_;
- }
- };
- }
-
- function MinAggregator(field) {
- this.field_ = field;
-
- this.init = function() {
- this.min_ = null;
- };
-
- this.accumulate = function(item) {
- var val = item[this.field_];
- if (val != null && val != NaN) {
- if (this.min_ == null ||val < this.min_) {
- this.min_ = val;
- }
- }
- };
-
- this.storeResult = function(groupTotals) {
- if (!groupTotals.min) {
- groupTotals.min = {};
- }
- groupTotals.min[this.field_] = this.min_;
- }
+ return {
+ // methods
+ "beginUpdate":beginUpdate,
+ "endUpdate":endUpdate,
+ "setPagingOptions":setPagingOptions,
+ "getPagingInfo":getPagingInfo,
+ "getItems":getItems,
+ "setItems":setItems,
+ "setFilter":setFilter,
+ "sort":sort,
+ "fastSort":fastSort,
+ "reSort":reSort,
+ "groupBy":groupBy,
+ "setAggregators":setAggregators,
+ "collapseGroup":collapseGroup,
+ "expandGroup":expandGroup,
+ "getGroups":getGroups,
+ "getIdxById":getIdxById,
+ "getRowById":getRowById,
+ "getItemById":getItemById,
+ "getItemByIdx":getItemByIdx,
+ "setRefreshHints":setRefreshHints,
+ "setFilterArgs":setFilterArgs,
+ "refresh":refresh,
+ "updateItem":updateItem,
+ "insertItem":insertItem,
+ "addItem":addItem,
+ "deleteItem":deleteItem,
+
+ // data provider methods
+ "getLength":getLength,
+ "getItem":getItem,
+ "getItemMetadata":getItemMetadata,
+
+ // events
+ "onRowCountChanged":onRowCountChanged,
+ "onRowsChanged":onRowsChanged,
+ "onPagingInfoChanged":onPagingInfoChanged
+ };
+ }
+
+ function AvgAggregator(field) {
+ this.field_ = field;
+
+ this.init = function () {
+ this.count_ = 0;
+ this.nonNullCount_ = 0;
+ this.sum_ = 0;
+ };
+
+ this.accumulate = function (item) {
+ var val = item[this.field_];
+ this.count_++;
+ if (val != null && val != NaN) {
+ this.nonNullCount_++;
+ this.sum_ += 1 * val;
+ }
+ };
+
+ this.storeResult = function (groupTotals) {
+ if (!groupTotals.avg) {
+ groupTotals.avg = {};
+ }
+ if (this.nonNullCount_ != 0) {
+ groupTotals.avg[this.field_] = this.sum_ / this.nonNullCount_;
+ }
+ };
+ }
+
+ function MinAggregator(field) {
+ this.field_ = field;
+
+ this.init = function () {
+ this.min_ = null;
+ };
+
+ this.accumulate = function (item) {
+ var val = item[this.field_];
+ if (val != null && val != NaN) {
+ if (this.min_ == null || val < this.min_) {
+ this.min_ = val;
+ }
+ }
+ };
+
+ this.storeResult = function (groupTotals) {
+ if (!groupTotals.min) {
+ groupTotals.min = {};
+ }
+ groupTotals.min[this.field_] = this.min_;
}
+ }
- function MaxAggregator(field) {
- this.field_ = field;
+ function MaxAggregator(field) {
+ this.field_ = field;
- this.init = function() {
- this.max_ = null;
- };
+ this.init = function () {
+ this.max_ = null;
+ };
- this.accumulate = function(item) {
- var val = item[this.field_];
- if (val != null && val != NaN) {
- if (this.max_ == null ||val > this.max_) {
- this.max_ = val;
- }
- }
- };
-
- this.storeResult = function(groupTotals) {
- if (!groupTotals.max) {
- groupTotals.max = {};
- }
- groupTotals.max[this.field_] = this.max_;
+ this.accumulate = function (item) {
+ var val = item[this.field_];
+ if (val != null && val != NaN) {
+ if (this.max_ == null || val > this.max_) {
+ this.max_ = val;
}
+ }
+ };
+
+ this.storeResult = function (groupTotals) {
+ if (!groupTotals.max) {
+ groupTotals.max = {};
+ }
+ groupTotals.max[this.field_] = this.max_;
}
+ }
- // TODO: add more built-in aggregators
- // TODO: merge common aggregators in one to prevent needles iterating
+ // TODO: add more built-in aggregators
+ // TODO: merge common aggregators in one to prevent needles iterating
})(jQuery); \ No newline at end of file