diff options
author | Michael Leibman <michael.leibman@gmail.com> | 2012-01-17 12:29:57 -0800 |
---|---|---|
committer | Michael Leibman <mleibman@google.com> | 2012-01-17 12:29:57 -0800 |
commit | 061ac59fb6a4c09a49d5abe1fa86c26f9d13bf66 (patch) | |
tree | 94f65296e682881a84214cd3ed608cc991ccf666 /slick.dataview.js | |
parent | 6634f168da6a03ce36a4a63c83f9acd1c33cbd5d (diff) | |
download | SlickGrid-061ac59fb6a4c09a49d5abe1fa86c26f9d13bf66.zip SlickGrid-061ac59fb6a4c09a49d5abe1fa86c26f9d13bf66.tar.gz SlickGrid-061ac59fb6a4c09a49d5abe1fa86c26f9d13bf66.tar.bz2 |
Reformatted code.
Diffstat (limited to 'slick.dataview.js')
-rw-r--r-- | slick.dataview.js | 1349 |
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 |