Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

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