Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 
 

1064 rindas
30 KiB

  1. (function ($) {
  2. $.extend(true, window, {
  3. Slick: {
  4. Data: {
  5. DataView: DataView,
  6. Aggregators: {
  7. Avg: AvgAggregator,
  8. Min: MinAggregator,
  9. Max: MaxAggregator,
  10. Sum: SumAggregator
  11. }
  12. }
  13. }
  14. });
  15. /***
  16. * A sample Model implementation.
  17. * Provides a filtered view of the underlying data.
  18. *
  19. * Relies on the data item having an "id" property uniquely identifying it.
  20. */
  21. function DataView(options) {
  22. var self = this;
  23. var defaults = {
  24. groupItemMetadataProvider: null,
  25. inlineFilters: false
  26. };
  27. // private
  28. var idProperty = "id"; // property holding a unique row id
  29. var items = []; // data by index
  30. var rows = []; // data by row
  31. var idxById = {}; // indexes by id
  32. var rowsById = null; // rows by id; lazy-calculated
  33. var filter = null; // filter function
  34. var updated = null; // updated item ids
  35. var suspend = false; // suspends the recalculation
  36. var sortAsc = true;
  37. var fastSortField;
  38. var sortComparer;
  39. var refreshHints = {};
  40. var prevRefreshHints = {};
  41. var filterArgs;
  42. var filteredItems = [];
  43. var compiledFilter;
  44. var compiledFilterWithCaching;
  45. var filterCache = [];
  46. // grouping
  47. var groupingInfoDefaults = {
  48. getter: null,
  49. formatter: null,
  50. comparer: function(a, b) { return a.value - b.value; },
  51. predefinedValues: [],
  52. aggregators: [],
  53. aggregateEmpty: false,
  54. aggregateCollapsed: false,
  55. aggregateChildGroups: false,
  56. collapsed: false,
  57. displayTotalsRow: true
  58. };
  59. var groupingInfos = [];
  60. var groups = [];
  61. var toggledGroupsByLevel = [];
  62. var groupingDelimiter = ':|:';
  63. var pagesize = 0;
  64. var pagenum = 0;
  65. var totalRows = 0;
  66. // events
  67. var onRowCountChanged = new Slick.Event();
  68. var onRowsChanged = new Slick.Event();
  69. var onPagingInfoChanged = new Slick.Event();
  70. options = $.extend(true, {}, defaults, options);
  71. function beginUpdate() {
  72. suspend = true;
  73. }
  74. function endUpdate() {
  75. suspend = false;
  76. refresh();
  77. }
  78. function setRefreshHints(hints) {
  79. refreshHints = hints;
  80. }
  81. function setFilterArgs(args) {
  82. filterArgs = args;
  83. }
  84. function updateIdxById(startingIndex) {
  85. startingIndex = startingIndex || 0;
  86. var id;
  87. for (var i = startingIndex, l = items.length; i < l; i++) {
  88. id = items[i][idProperty];
  89. if (id === undefined) {
  90. throw "Each data element must implement a unique 'id' property";
  91. }
  92. idxById[id] = i;
  93. }
  94. }
  95. function ensureIdUniqueness() {
  96. var id;
  97. for (var i = 0, l = items.length; i < l; i++) {
  98. id = items[i][idProperty];
  99. if (id === undefined || idxById[id] !== i) {
  100. throw "Each data element must implement a unique 'id' property";
  101. }
  102. }
  103. }
  104. function getItems() {
  105. return items;
  106. }
  107. function setItems(data, objectIdProperty) {
  108. if (objectIdProperty !== undefined) {
  109. idProperty = objectIdProperty;
  110. }
  111. items = filteredItems = data;
  112. idxById = {};
  113. updateIdxById();
  114. ensureIdUniqueness();
  115. refresh();
  116. }
  117. function setPagingOptions(args) {
  118. if (args.pageSize != undefined) {
  119. pagesize = args.pageSize;
  120. pagenum = pagesize ? Math.min(pagenum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)) : 0;
  121. }
  122. if (args.pageNum != undefined) {
  123. pagenum = Math.min(args.pageNum, Math.max(0, Math.ceil(totalRows / pagesize) - 1));
  124. }
  125. onPagingInfoChanged.notify(getPagingInfo(), null, self);
  126. refresh();
  127. }
  128. function getPagingInfo() {
  129. var totalPages = pagesize ? Math.max(1, Math.ceil(totalRows / pagesize)) : 1;
  130. return {pageSize: pagesize, pageNum: pagenum, totalRows: totalRows, totalPages: totalPages};
  131. }
  132. function sort(comparer, ascending) {
  133. sortAsc = ascending;
  134. sortComparer = comparer;
  135. fastSortField = null;
  136. if (ascending === false) {
  137. items.reverse();
  138. }
  139. items.sort(comparer);
  140. if (ascending === false) {
  141. items.reverse();
  142. }
  143. idxById = {};
  144. updateIdxById();
  145. refresh();
  146. }
  147. /***
  148. * Provides a workaround for the extremely slow sorting in IE.
  149. * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString
  150. * to return the value of that field and then doing a native Array.sort().
  151. */
  152. function fastSort(field, ascending) {
  153. sortAsc = ascending;
  154. fastSortField = field;
  155. sortComparer = null;
  156. var oldToString = Object.prototype.toString;
  157. Object.prototype.toString = (typeof field == "function") ? field : function () {
  158. return this[field]
  159. };
  160. // an extra reversal for descending sort keeps the sort stable
  161. // (assuming a stable native sort implementation, which isn't true in some cases)
  162. if (ascending === false) {
  163. items.reverse();
  164. }
  165. items.sort();
  166. Object.prototype.toString = oldToString;
  167. if (ascending === false) {
  168. items.reverse();
  169. }
  170. idxById = {};
  171. updateIdxById();
  172. refresh();
  173. }
  174. function reSort() {
  175. if (sortComparer) {
  176. sort(sortComparer, sortAsc);
  177. } else if (fastSortField) {
  178. fastSort(fastSortField, sortAsc);
  179. }
  180. }
  181. function setFilter(filterFn) {
  182. filter = filterFn;
  183. if (options.inlineFilters) {
  184. compiledFilter = compileFilter();
  185. compiledFilterWithCaching = compileFilterWithCaching();
  186. }
  187. refresh();
  188. }
  189. function getGrouping() {
  190. return groupingInfos;
  191. }
  192. function setGrouping(groupingInfo) {
  193. if (!options.groupItemMetadataProvider) {
  194. options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider();
  195. }
  196. groups = [];
  197. toggledGroupsByLevel = [];
  198. groupingInfo = groupingInfo || [];
  199. groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo];
  200. for (var i = 0; i < groupingInfos.length; i++) {
  201. var gi = groupingInfos[i] = $.extend(true, {}, groupingInfoDefaults, groupingInfos[i]);
  202. gi.getterIsAFn = typeof gi.getter === "function";
  203. // pre-compile accumulator loops
  204. gi.compiledAccumulators = [];
  205. var idx = gi.aggregators.length;
  206. while (idx--) {
  207. gi.compiledAccumulators[idx] = compileAccumulatorLoop(gi.aggregators[idx]);
  208. }
  209. toggledGroupsByLevel[i] = {};
  210. }
  211. refresh();
  212. }
  213. /**
  214. * @deprecated Please use {@link setGrouping}.
  215. */
  216. function groupBy(valueGetter, valueFormatter, sortComparer) {
  217. if (valueGetter == null) {
  218. setGrouping([]);
  219. return;
  220. }
  221. setGrouping({
  222. getter: valueGetter,
  223. formatter: valueFormatter,
  224. comparer: sortComparer
  225. });
  226. }
  227. /**
  228. * @deprecated Please use {@link setGrouping}.
  229. */
  230. function setAggregators(groupAggregators, includeCollapsed) {
  231. if (!groupingInfos.length) {
  232. throw new Error("At least must setGrouping must be specified before calling setAggregators().");
  233. }
  234. groupingInfos[0].aggregators = groupAggregators;
  235. groupingInfos[0].aggregateCollapsed = includeCollapsed;
  236. setGrouping(groupingInfos);
  237. }
  238. function getItemByIdx(i) {
  239. return items[i];
  240. }
  241. function getIdxById(id) {
  242. return idxById[id];
  243. }
  244. function ensureRowsByIdCache() {
  245. if (!rowsById) {
  246. rowsById = {};
  247. for (var i = 0, l = rows.length; i < l; i++) {
  248. rowsById[rows[i][idProperty]] = i;
  249. }
  250. }
  251. }
  252. function getRowById(id) {
  253. ensureRowsByIdCache();
  254. return rowsById[id];
  255. }
  256. function getItemById(id) {
  257. return items[idxById[id]];
  258. }
  259. function mapIdsToRows(idArray) {
  260. var rows = [];
  261. ensureRowsByIdCache();
  262. for (var i = 0; i < idArray.length; i++) {
  263. var row = rowsById[idArray[i]];
  264. if (row != null) {
  265. rows[rows.length] = row;
  266. }
  267. }
  268. return rows;
  269. }
  270. function mapRowsToIds(rowArray) {
  271. var ids = [];
  272. for (var i = 0; i < rowArray.length; i++) {
  273. if (rowArray[i] < rows.length) {
  274. ids[ids.length] = rows[rowArray[i]][idProperty];
  275. }
  276. }
  277. return ids;
  278. }
  279. function updateItem(id, item) {
  280. if (idxById[id] === undefined || id !== item[idProperty]) {
  281. throw "Invalid or non-matching id";
  282. }
  283. items[idxById[id]] = item;
  284. if (!updated) {
  285. updated = {};
  286. }
  287. updated[id] = true;
  288. refresh();
  289. }
  290. function insertItem(insertBefore, item) {
  291. items.splice(insertBefore, 0, item);
  292. updateIdxById(insertBefore);
  293. refresh();
  294. }
  295. function addItem(item) {
  296. items.push(item);
  297. updateIdxById(items.length - 1);
  298. refresh();
  299. }
  300. function deleteItem(id) {
  301. var idx = idxById[id];
  302. if (idx === undefined) {
  303. throw "Invalid id";
  304. }
  305. delete idxById[id];
  306. items.splice(idx, 1);
  307. updateIdxById(idx);
  308. refresh();
  309. }
  310. function getLength() {
  311. return rows.length;
  312. }
  313. function getItem(i) {
  314. return rows[i];
  315. }
  316. function getItemMetadata(i) {
  317. var item = rows[i];
  318. if (item === undefined) {
  319. return null;
  320. }
  321. // overrides for setGrouping rows
  322. if (item.__group) {
  323. return options.groupItemMetadataProvider.getGroupRowMetadata(item);
  324. }
  325. // overrides for totals rows
  326. if (item.__groupTotals) {
  327. return options.groupItemMetadataProvider.getTotalsRowMetadata(item);
  328. }
  329. return null;
  330. }
  331. function expandCollapseAllGroups(level, collapse) {
  332. if (level == null) {
  333. for (var i = 0; i < groupingInfos.length; i++) {
  334. toggledGroupsByLevel[i] = {};
  335. groupingInfos[i].collapsed = collapse;
  336. }
  337. } else {
  338. toggledGroupsByLevel[level] = {};
  339. groupingInfos[level].collapsed = collapse;
  340. }
  341. refresh();
  342. }
  343. /**
  344. * @param level {Number} Optional level to collapse. If not specified, applies to all levels.
  345. */
  346. function collapseAllGroups(level) {
  347. expandCollapseAllGroups(level, true);
  348. }
  349. /**
  350. * @param level {Number} Optional level to expand. If not specified, applies to all levels.
  351. */
  352. function expandAllGroups(level) {
  353. expandCollapseAllGroups(level, false);
  354. }
  355. function expandCollapseGroup(level, groupingKey, collapse) {
  356. toggledGroupsByLevel[level][groupingKey] = groupingInfos[level].collapsed ^ collapse;
  357. refresh();
  358. }
  359. /**
  360. * @param varArgs Either a Slick.Group's "groupingKey" property, or a
  361. * variable argument list of grouping values denoting a unique path to the row. For
  362. * example, calling collapseGroup('high', '10%') will collapse the '10%' subgroup of
  363. * the 'high' setGrouping.
  364. */
  365. function collapseGroup(varArgs) {
  366. var args = Array.prototype.slice.call(arguments);
  367. var arg0 = args[0];
  368. if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) {
  369. expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, true);
  370. } else {
  371. expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), true);
  372. }
  373. }
  374. /**
  375. * @param varArgs Either a Slick.Group's "groupingKey" property, or a
  376. * variable argument list of grouping values denoting a unique path to the row. For
  377. * example, calling expandGroup('high', '10%') will expand the '10%' subgroup of
  378. * the 'high' setGrouping.
  379. */
  380. function expandGroup(varArgs) {
  381. var args = Array.prototype.slice.call(arguments);
  382. var arg0 = args[0];
  383. if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) {
  384. expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, false);
  385. } else {
  386. expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), false);
  387. }
  388. }
  389. function getGroups() {
  390. return groups;
  391. }
  392. function extractGroups(rows, parentGroup) {
  393. var group;
  394. var val;
  395. var groups = [];
  396. var groupsByVal = [];
  397. var r;
  398. var level = parentGroup ? parentGroup.level + 1 : 0;
  399. var gi = groupingInfos[level];
  400. for (var i = 0, l = gi.predefinedValues.length; i < l; i++) {
  401. val = gi.predefinedValues[i];
  402. group = groupsByVal[val];
  403. if (!group) {
  404. group = new Slick.Group();
  405. group.value = val;
  406. group.level = level;
  407. group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val;
  408. groups[groups.length] = group;
  409. groupsByVal[val] = group;
  410. }
  411. }
  412. for (var i = 0, l = rows.length; i < l; i++) {
  413. r = rows[i];
  414. val = gi.getterIsAFn ? gi.getter(r) : r[gi.getter];
  415. group = groupsByVal[val];
  416. if (!group) {
  417. group = new Slick.Group();
  418. group.value = val;
  419. group.level = level;
  420. group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val;
  421. groups[groups.length] = group;
  422. groupsByVal[val] = group;
  423. }
  424. group.rows[group.count++] = r;
  425. }
  426. if (level < groupingInfos.length - 1) {
  427. for (var i = 0; i < groups.length; i++) {
  428. group = groups[i];
  429. group.groups = extractGroups(group.rows, group);
  430. }
  431. }
  432. groups.sort(groupingInfos[level].comparer);
  433. return groups;
  434. }
  435. // TODO: lazy totals calculation
  436. function calculateGroupTotals(group) {
  437. // TODO: try moving iterating over groups into compiled accumulator
  438. var gi = groupingInfos[group.level];
  439. var isLeafLevel = (group.level == groupingInfos.length);
  440. var totals = new Slick.GroupTotals();
  441. var agg, idx = gi.aggregators.length;
  442. while (idx--) {
  443. agg = gi.aggregators[idx];
  444. agg.init();
  445. gi.compiledAccumulators[idx].call(agg,
  446. (!isLeafLevel && gi.aggregateChildGroups) ? group.groups : group.rows);
  447. agg.storeResult(totals);
  448. }
  449. totals.group = group;
  450. group.totals = totals;
  451. }
  452. function calculateTotals(groups, level) {
  453. level = level || 0;
  454. var gi = groupingInfos[level];
  455. var idx = groups.length, g;
  456. while (idx--) {
  457. g = groups[idx];
  458. if (g.collapsed && !gi.aggregateCollapsed) {
  459. continue;
  460. }
  461. // Do a depth-first aggregation so that parent setGrouping aggregators can access subgroup totals.
  462. if (g.groups) {
  463. calculateTotals(g.groups, level + 1);
  464. }
  465. if (gi.aggregators.length && (
  466. gi.aggregateEmpty || g.rows.length || (g.groups && g.groups.length))) {
  467. calculateGroupTotals(g);
  468. }
  469. }
  470. }
  471. function finalizeGroups(groups, level) {
  472. level = level || 0;
  473. var gi = groupingInfos[level];
  474. var groupCollapsed = gi.collapsed;
  475. var toggledGroups = toggledGroupsByLevel[level];
  476. var idx = groups.length, g;
  477. while (idx--) {
  478. g = groups[idx];
  479. g.collapsed = groupCollapsed ^ toggledGroups[g.groupingKey];
  480. g.title = gi.formatter ? gi.formatter(g) : g.value;
  481. if (g.groups) {
  482. finalizeGroups(g.groups, level + 1);
  483. // Let the non-leaf setGrouping rows get garbage-collected.
  484. // They may have been used by aggregates that go over all of the descendants,
  485. // but at this point they are no longer needed.
  486. g.rows = [];
  487. }
  488. }
  489. }
  490. function flattenGroupedRows(groups, level) {
  491. level = level || 0;
  492. var gi = groupingInfos[level];
  493. var groupedRows = [], rows, gl = 0, g;
  494. for (var i = 0, l = groups.length; i < l; i++) {
  495. g = groups[i];
  496. groupedRows[gl++] = g;
  497. if (!g.collapsed) {
  498. rows = g.groups ? flattenGroupedRows(g.groups, level + 1) : g.rows;
  499. for (var j = 0, jj = rows.length; j < jj; j++) {
  500. groupedRows[gl++] = rows[j];
  501. }
  502. }
  503. if (g.totals && gi.displayTotalsRow && (!g.collapsed || gi.aggregateCollapsed)) {
  504. groupedRows[gl++] = g.totals;
  505. }
  506. }
  507. return groupedRows;
  508. }
  509. function getFunctionInfo(fn) {
  510. var fnRegex = /^function[^(]*\(([^)]*)\)\s*{([\s\S]*)}$/;
  511. var matches = fn.toString().match(fnRegex);
  512. return {
  513. params: matches[1].split(","),
  514. body: matches[2]
  515. };
  516. }
  517. function compileAccumulatorLoop(aggregator) {
  518. var accumulatorInfo = getFunctionInfo(aggregator.accumulate);
  519. var fn = new Function(
  520. "_items",
  521. "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" +
  522. accumulatorInfo.params[0] + " = _items[_i]; " +
  523. accumulatorInfo.body +
  524. "}"
  525. );
  526. fn.displayName = fn.name = "compiledAccumulatorLoop";
  527. return fn;
  528. }
  529. function compileFilter() {
  530. var filterInfo = getFunctionInfo(filter);
  531. var filterBody = filterInfo.body
  532. .replace(/return false[;}]/gi, "{ continue _coreloop; }")
  533. .replace(/return true[;}]/gi, "{ _retval[_idx++] = $item$; continue _coreloop; }")
  534. .replace(/return ([^;}]+?);/gi,
  535. "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }");
  536. // This preserves the function template code after JS compression,
  537. // so that replace() commands still work as expected.
  538. var tpl = [
  539. //"function(_items, _args) { ",
  540. "var _retval = [], _idx = 0; ",
  541. "var $item$, $args$ = _args; ",
  542. "_coreloop: ",
  543. "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
  544. "$item$ = _items[_i]; ",
  545. "$filter$; ",
  546. "} ",
  547. "return _retval; "
  548. //"}"
  549. ].join("");
  550. tpl = tpl.replace(/\$filter\$/gi, filterBody);
  551. tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
  552. tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
  553. var fn = new Function("_items,_args", tpl);
  554. fn.displayName = fn.name = "compiledFilter";
  555. return fn;
  556. }
  557. function compileFilterWithCaching() {
  558. var filterInfo = getFunctionInfo(filter);
  559. var filterBody = filterInfo.body
  560. .replace(/return false[;}]/gi, "{ continue _coreloop; }")
  561. .replace(/return true[;}]/gi, "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }")
  562. .replace(/return ([^;}]+?);/gi,
  563. "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }");
  564. // This preserves the function template code after JS compression,
  565. // so that replace() commands still work as expected.
  566. var tpl = [
  567. //"function(_items, _args, _cache) { ",
  568. "var _retval = [], _idx = 0; ",
  569. "var $item$, $args$ = _args; ",
  570. "_coreloop: ",
  571. "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
  572. "$item$ = _items[_i]; ",
  573. "if (_cache[_i]) { ",
  574. "_retval[_idx++] = $item$; ",
  575. "continue _coreloop; ",
  576. "} ",
  577. "$filter$; ",
  578. "} ",
  579. "return _retval; "
  580. //"}"
  581. ].join("");
  582. tpl = tpl.replace(/\$filter\$/gi, filterBody);
  583. tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
  584. tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
  585. var fn = new Function("_items,_args,_cache", tpl);
  586. fn.displayName = fn.name = "compiledFilterWithCaching";
  587. return fn;
  588. }
  589. function uncompiledFilter(items, args) {
  590. var retval = [], idx = 0;
  591. for (var i = 0, ii = items.length; i < ii; i++) {
  592. if (filter(items[i], args)) {
  593. retval[idx++] = items[i];
  594. }
  595. }
  596. return retval;
  597. }
  598. function uncompiledFilterWithCaching(items, args, cache) {
  599. var retval = [], idx = 0, item;
  600. for (var i = 0, ii = items.length; i < ii; i++) {
  601. item = items[i];
  602. if (cache[i]) {
  603. retval[idx++] = item;
  604. } else if (filter(item, args)) {
  605. retval[idx++] = item;
  606. cache[i] = true;
  607. }
  608. }
  609. return retval;
  610. }
  611. function getFilteredAndPagedItems(items) {
  612. if (filter) {
  613. var batchFilter = options.inlineFilters ? compiledFilter : uncompiledFilter;
  614. var batchFilterWithCaching = options.inlineFilters ? compiledFilterWithCaching : uncompiledFilterWithCaching;
  615. if (refreshHints.isFilterNarrowing) {
  616. filteredItems = batchFilter(filteredItems, filterArgs);
  617. } else if (refreshHints.isFilterExpanding) {
  618. filteredItems = batchFilterWithCaching(items, filterArgs, filterCache);
  619. } else if (!refreshHints.isFilterUnchanged) {
  620. filteredItems = batchFilter(items, filterArgs);
  621. }
  622. } else {
  623. // special case: if not filtering and not paging, the resulting
  624. // rows collection needs to be a copy so that changes due to sort
  625. // can be caught
  626. filteredItems = pagesize ? items : items.concat();
  627. }
  628. // get the current page
  629. var paged;
  630. if (pagesize) {
  631. if (filteredItems.length < pagenum * pagesize) {
  632. pagenum = Math.floor(filteredItems.length / pagesize);
  633. }
  634. paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize);
  635. } else {
  636. paged = filteredItems;
  637. }
  638. return {totalRows: filteredItems.length, rows: paged};
  639. }
  640. function getRowDiffs(rows, newRows) {
  641. var item, r, eitherIsNonData, diff = [];
  642. var from = 0, to = newRows.length;
  643. if (refreshHints && refreshHints.ignoreDiffsBefore) {
  644. from = Math.max(0,
  645. Math.min(newRows.length, refreshHints.ignoreDiffsBefore));
  646. }
  647. if (refreshHints && refreshHints.ignoreDiffsAfter) {
  648. to = Math.min(newRows.length,
  649. Math.max(0, refreshHints.ignoreDiffsAfter));
  650. }
  651. for (var i = from, rl = rows.length; i < to; i++) {
  652. if (i >= rl) {
  653. diff[diff.length] = i;
  654. } else {
  655. item = newRows[i];
  656. r = rows[i];
  657. if ((groupingInfos.length && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) &&
  658. item.__group !== r.__group ||
  659. item.__group && !item.equals(r))
  660. || (eitherIsNonData &&
  661. // no good way to compare totals since they are arbitrary DTOs
  662. // deep object comparison is pretty expensive
  663. // always considering them 'dirty' seems easier for the time being
  664. (item.__groupTotals || r.__groupTotals))
  665. || item[idProperty] != r[idProperty]
  666. || (updated && updated[item[idProperty]])
  667. ) {
  668. diff[diff.length] = i;
  669. }
  670. }
  671. }
  672. return diff;
  673. }
  674. function recalc(_items) {
  675. rowsById = null;
  676. if (refreshHints.isFilterNarrowing != prevRefreshHints.isFilterNarrowing ||
  677. refreshHints.isFilterExpanding != prevRefreshHints.isFilterExpanding) {
  678. filterCache = [];
  679. }
  680. var filteredItems = getFilteredAndPagedItems(_items);
  681. totalRows = filteredItems.totalRows;
  682. var newRows = filteredItems.rows;
  683. groups = [];
  684. if (groupingInfos.length) {
  685. groups = extractGroups(newRows);
  686. if (groups.length) {
  687. calculateTotals(groups);
  688. finalizeGroups(groups);
  689. newRows = flattenGroupedRows(groups);
  690. }
  691. }
  692. var diff = getRowDiffs(rows, newRows);
  693. rows = newRows;
  694. return diff;
  695. }
  696. function refresh() {
  697. if (suspend) {
  698. return;
  699. }
  700. var countBefore = rows.length;
  701. var totalRowsBefore = totalRows;
  702. var diff = recalc(items, filter); // pass as direct refs to avoid closure perf hit
  703. // if the current page is no longer valid, go to last page and recalc
  704. // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
  705. if (pagesize && totalRows < pagenum * pagesize) {
  706. pagenum = Math.max(0, Math.ceil(totalRows / pagesize) - 1);
  707. diff = recalc(items, filter);
  708. }
  709. updated = null;
  710. prevRefreshHints = refreshHints;
  711. refreshHints = {};
  712. if (totalRowsBefore != totalRows) {
  713. onPagingInfoChanged.notify(getPagingInfo(), null, self);
  714. }
  715. if (countBefore != rows.length) {
  716. onRowCountChanged.notify({previous: countBefore, current: rows.length}, null, self);
  717. }
  718. if (diff.length > 0) {
  719. onRowsChanged.notify({rows: diff}, null, self);
  720. }
  721. }
  722. function syncGridSelection(grid, preserveHidden) {
  723. var self = this;
  724. var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());;
  725. var inHandler;
  726. function update() {
  727. if (selectedRowIds.length > 0) {
  728. inHandler = true;
  729. var selectedRows = self.mapIdsToRows(selectedRowIds);
  730. if (!preserveHidden) {
  731. selectedRowIds = self.mapRowsToIds(selectedRows);
  732. }
  733. grid.setSelectedRows(selectedRows);
  734. inHandler = false;
  735. }
  736. }
  737. grid.onSelectedRowsChanged.subscribe(function(e, args) {
  738. if (inHandler) { return; }
  739. selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());
  740. });
  741. this.onRowsChanged.subscribe(update);
  742. this.onRowCountChanged.subscribe(update);
  743. }
  744. function syncGridCellCssStyles(grid, key) {
  745. var hashById;
  746. var inHandler;
  747. // since this method can be called after the cell styles have been set,
  748. // get the existing ones right away
  749. storeCellCssStyles(grid.getCellCssStyles(key));
  750. function storeCellCssStyles(hash) {
  751. hashById = {};
  752. for (var row in hash) {
  753. var id = rows[row][idProperty];
  754. hashById[id] = hash[row];
  755. }
  756. }
  757. function update() {
  758. if (hashById) {
  759. inHandler = true;
  760. ensureRowsByIdCache();
  761. var newHash = {};
  762. for (var id in hashById) {
  763. var row = rowsById[id];
  764. if (row != undefined) {
  765. newHash[row] = hashById[id];
  766. }
  767. }
  768. grid.setCellCssStyles(key, newHash);
  769. inHandler = false;
  770. }
  771. }
  772. grid.onCellCssStylesChanged.subscribe(function(e, args) {
  773. if (inHandler) { return; }
  774. if (key != args.key) { return; }
  775. if (args.hash) {
  776. storeCellCssStyles(args.hash);
  777. }
  778. });
  779. this.onRowsChanged.subscribe(update);
  780. this.onRowCountChanged.subscribe(update);
  781. }
  782. return {
  783. // methods
  784. "beginUpdate": beginUpdate,
  785. "endUpdate": endUpdate,
  786. "setPagingOptions": setPagingOptions,
  787. "getPagingInfo": getPagingInfo,
  788. "getItems": getItems,
  789. "setItems": setItems,
  790. "setFilter": setFilter,
  791. "sort": sort,
  792. "fastSort": fastSort,
  793. "reSort": reSort,
  794. "setGrouping": setGrouping,
  795. "getGrouping": getGrouping,
  796. "groupBy": groupBy,
  797. "setAggregators": setAggregators,
  798. "collapseAllGroups": collapseAllGroups,
  799. "expandAllGroups": expandAllGroups,
  800. "collapseGroup": collapseGroup,
  801. "expandGroup": expandGroup,
  802. "getGroups": getGroups,
  803. "getIdxById": getIdxById,
  804. "getRowById": getRowById,
  805. "getItemById": getItemById,
  806. "getItemByIdx": getItemByIdx,
  807. "mapRowsToIds": mapRowsToIds,
  808. "mapIdsToRows": mapIdsToRows,
  809. "setRefreshHints": setRefreshHints,
  810. "setFilterArgs": setFilterArgs,
  811. "refresh": refresh,
  812. "updateItem": updateItem,
  813. "insertItem": insertItem,
  814. "addItem": addItem,
  815. "deleteItem": deleteItem,
  816. "syncGridSelection": syncGridSelection,
  817. "syncGridCellCssStyles": syncGridCellCssStyles,
  818. // data provider methods
  819. "getLength": getLength,
  820. "getItem": getItem,
  821. "getItemMetadata": getItemMetadata,
  822. // events
  823. "onRowCountChanged": onRowCountChanged,
  824. "onRowsChanged": onRowsChanged,
  825. "onPagingInfoChanged": onPagingInfoChanged
  826. };
  827. }
  828. function AvgAggregator(field) {
  829. this.field_ = field;
  830. this.init = function () {
  831. this.count_ = 0;
  832. this.nonNullCount_ = 0;
  833. this.sum_ = 0;
  834. };
  835. this.accumulate = function (item) {
  836. var val = item[this.field_];
  837. this.count_++;
  838. if (val != null && val !== "" && val !== NaN) {
  839. this.nonNullCount_++;
  840. this.sum_ += parseFloat(val);
  841. }
  842. };
  843. this.storeResult = function (groupTotals) {
  844. if (!groupTotals.avg) {
  845. groupTotals.avg = {};
  846. }
  847. if (this.nonNullCount_ != 0) {
  848. groupTotals.avg[this.field_] = this.sum_ / this.nonNullCount_;
  849. }
  850. };
  851. }
  852. function MinAggregator(field) {
  853. this.field_ = field;
  854. this.init = function () {
  855. this.min_ = null;
  856. };
  857. this.accumulate = function (item) {
  858. var val = item[this.field_];
  859. if (val != null && val !== "" && val !== NaN) {
  860. if (this.min_ == null || val < this.min_) {
  861. this.min_ = val;
  862. }
  863. }
  864. };
  865. this.storeResult = function (groupTotals) {
  866. if (!groupTotals.min) {
  867. groupTotals.min = {};
  868. }
  869. groupTotals.min[this.field_] = this.min_;
  870. }
  871. }
  872. function MaxAggregator(field) {
  873. this.field_ = field;
  874. this.init = function () {
  875. this.max_ = null;
  876. };
  877. this.accumulate = function (item) {
  878. var val = item[this.field_];
  879. if (val != null && val !== "" && val !== NaN) {
  880. if (this.max_ == null || val > this.max_) {
  881. this.max_ = val;
  882. }
  883. }
  884. };
  885. this.storeResult = function (groupTotals) {
  886. if (!groupTotals.max) {
  887. groupTotals.max = {};
  888. }
  889. groupTotals.max[this.field_] = this.max_;
  890. }
  891. }
  892. function SumAggregator(field) {
  893. this.field_ = field;
  894. this.init = function () {
  895. this.sum_ = null;
  896. };
  897. this.accumulate = function (item) {
  898. var val = item[this.field_];
  899. if (val != null && val !== "" && val !== NaN) {
  900. this.sum_ += parseFloat(val);
  901. }
  902. };
  903. this.storeResult = function (groupTotals) {
  904. if (!groupTotals.sum) {
  905. groupTotals.sum = {};
  906. }
  907. groupTotals.sum[this.field_] = this.sum_;
  908. }
  909. }
  910. // TODO: add more built-in aggregators
  911. // TODO: merge common aggregators in one to prevent needles iterating
  912. })(jQuery);