slickgrid 2.3.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +20 -0
  4. data/README.md +30 -0
  5. data/lib/slickgrid.rb +6 -0
  6. data/lib/slickgrid/version.rb +3 -0
  7. data/slickgrid.gemspec +20 -0
  8. data/vendor/assets/images/slickgrid/CheckboxN.png +0 -0
  9. data/vendor/assets/images/slickgrid/CheckboxY.png +0 -0
  10. data/vendor/assets/images/slickgrid/GrpCheckboxN.png +0 -0
  11. data/vendor/assets/images/slickgrid/GrpCheckboxY.png +0 -0
  12. data/vendor/assets/images/slickgrid/actions.gif +0 -0
  13. data/vendor/assets/images/slickgrid/ajax-loader-small.gif +0 -0
  14. data/vendor/assets/images/slickgrid/arrow-right.gif +0 -0
  15. data/vendor/assets/images/slickgrid/arrow_redo.png +0 -0
  16. data/vendor/assets/images/slickgrid/arrow_right_peppermint.png +0 -0
  17. data/vendor/assets/images/slickgrid/arrow_right_spearmint.png +0 -0
  18. data/vendor/assets/images/slickgrid/arrow_undo.png +0 -0
  19. data/vendor/assets/images/slickgrid/bullet_blue.png +0 -0
  20. data/vendor/assets/images/slickgrid/bullet_star.png +0 -0
  21. data/vendor/assets/images/slickgrid/bullet_toggle_minus.png +0 -0
  22. data/vendor/assets/images/slickgrid/bullet_toggle_plus.png +0 -0
  23. data/vendor/assets/images/slickgrid/calendar.gif +0 -0
  24. data/vendor/assets/images/slickgrid/collapse.gif +0 -0
  25. data/vendor/assets/images/slickgrid/comment_yellow.gif +0 -0
  26. data/vendor/assets/images/slickgrid/delete.png +0 -0
  27. data/vendor/assets/images/slickgrid/down.gif +0 -0
  28. data/vendor/assets/images/slickgrid/drag-handle.png +0 -0
  29. data/vendor/assets/images/slickgrid/editor-helper-bg.gif +0 -0
  30. data/vendor/assets/images/slickgrid/expand.gif +0 -0
  31. data/vendor/assets/images/slickgrid/header-bg.gif +0 -0
  32. data/vendor/assets/images/slickgrid/header-columns-bg.gif +0 -0
  33. data/vendor/assets/images/slickgrid/header-columns-over-bg.gif +0 -0
  34. data/vendor/assets/images/slickgrid/help.png +0 -0
  35. data/vendor/assets/images/slickgrid/info.gif +0 -0
  36. data/vendor/assets/images/slickgrid/listview.gif +0 -0
  37. data/vendor/assets/images/slickgrid/pencil.gif +0 -0
  38. data/vendor/assets/images/slickgrid/row-over-bg.gif +0 -0
  39. data/vendor/assets/images/slickgrid/sort-asc.gif +0 -0
  40. data/vendor/assets/images/slickgrid/sort-asc.png +0 -0
  41. data/vendor/assets/images/slickgrid/sort-desc.gif +0 -0
  42. data/vendor/assets/images/slickgrid/sort-desc.png +0 -0
  43. data/vendor/assets/images/slickgrid/stripes.png +0 -0
  44. data/vendor/assets/images/slickgrid/tag_red.png +0 -0
  45. data/vendor/assets/images/slickgrid/tick.png +0 -0
  46. data/vendor/assets/images/slickgrid/user_identity.gif +0 -0
  47. data/vendor/assets/images/slickgrid/user_identity_plus.gif +0 -0
  48. data/vendor/assets/javascripts/slickgrid.js +5 -0
  49. data/vendor/assets/javascripts/slickgrid/controls/columnpicker.js +221 -0
  50. data/vendor/assets/javascripts/slickgrid/controls/gridmenu.js +429 -0
  51. data/vendor/assets/javascripts/slickgrid/controls/pager.js +154 -0
  52. data/vendor/assets/javascripts/slickgrid/core.js +493 -0
  53. data/vendor/assets/javascripts/slickgrid/dataview.js +1220 -0
  54. data/vendor/assets/javascripts/slickgrid/editors.js +640 -0
  55. data/vendor/assets/javascripts/slickgrid/formatters.js +65 -0
  56. data/vendor/assets/javascripts/slickgrid/grid.js +3990 -0
  57. data/vendor/assets/javascripts/slickgrid/groupitemmetadataprovider.js +172 -0
  58. data/vendor/assets/javascripts/slickgrid/plugins/autotooltips.js +83 -0
  59. data/vendor/assets/javascripts/slickgrid/plugins/cellcopymanager.js +88 -0
  60. data/vendor/assets/javascripts/slickgrid/plugins/cellexternalcopymanager.js +452 -0
  61. data/vendor/assets/javascripts/slickgrid/plugins/cellrangedecorator.js +72 -0
  62. data/vendor/assets/javascripts/slickgrid/plugins/cellrangeselector.js +123 -0
  63. data/vendor/assets/javascripts/slickgrid/plugins/cellselectionmodel.js +168 -0
  64. data/vendor/assets/javascripts/slickgrid/plugins/checkboxselectcolumn.js +202 -0
  65. data/vendor/assets/javascripts/slickgrid/plugins/draggablegrouping.js +207 -0
  66. data/vendor/assets/javascripts/slickgrid/plugins/headerbuttons.js +177 -0
  67. data/vendor/assets/javascripts/slickgrid/plugins/headermenu.js +296 -0
  68. data/vendor/assets/javascripts/slickgrid/plugins/rowdetailview.js +455 -0
  69. data/vendor/assets/javascripts/slickgrid/plugins/rowmovemanager.js +138 -0
  70. data/vendor/assets/javascripts/slickgrid/plugins/rowselectionmodel.js +191 -0
  71. data/vendor/assets/javascripts/slickgrid/remotemodel.js +169 -0
  72. data/vendor/assets/stylesheets/slickgrid.scss +1 -0
  73. data/vendor/assets/stylesheets/slickgrid/controls/columnpicker.css +46 -0
  74. data/vendor/assets/stylesheets/slickgrid/controls/gridmenu.css +113 -0
  75. data/vendor/assets/stylesheets/slickgrid/controls/pager.css +41 -0
  76. data/vendor/assets/stylesheets/slickgrid/default-theme.css +132 -0
  77. data/vendor/assets/stylesheets/slickgrid/grid.css +189 -0
  78. data/vendor/assets/stylesheets/slickgrid/plugins/headerbuttons.css +39 -0
  79. data/vendor/assets/stylesheets/slickgrid/plugins/headermenu.css +59 -0
  80. data/vendor/assets/stylesheets/slickgrid/plugins/rowdetailview.css +39 -0
  81. metadata +165 -0
@@ -0,0 +1,1220 @@
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
+
17
+ /***
18
+ * A sample Model implementation.
19
+ * Provides a filtered view of the underlying data.
20
+ *
21
+ * Relies on the data item having an "id" property uniquely identifying it.
22
+ */
23
+ function DataView(options) {
24
+ var self = this;
25
+
26
+ var defaults = {
27
+ groupItemMetadataProvider: null,
28
+ inlineFilters: false
29
+ };
30
+
31
+
32
+ // private
33
+ var idProperty = "id"; // property holding a unique row id
34
+ var items = []; // data by index
35
+ var rows = []; // data by row
36
+ var idxById = {}; // indexes by id
37
+ var rowsById = null; // rows by id; lazy-calculated
38
+ var filter = null; // filter function
39
+ var updated = null; // updated item ids
40
+ var suspend = false; // suspends the recalculation
41
+ var sortAsc = true;
42
+ var fastSortField;
43
+ var sortComparer;
44
+ var refreshHints = {};
45
+ var prevRefreshHints = {};
46
+ var filterArgs;
47
+ var filteredItems = [];
48
+ var compiledFilter;
49
+ var compiledFilterWithCaching;
50
+ var filterCache = [];
51
+
52
+ // grouping
53
+ var groupingInfoDefaults = {
54
+ getter: null,
55
+ formatter: null,
56
+ comparer: function(a, b) {
57
+ return (a.value === b.value ? 0 :
58
+ (a.value > b.value ? 1 : -1)
59
+ );
60
+ },
61
+ predefinedValues: [],
62
+ aggregators: [],
63
+ aggregateEmpty: false,
64
+ aggregateCollapsed: false,
65
+ aggregateChildGroups: false,
66
+ collapsed: false,
67
+ displayTotalsRow: true,
68
+ lazyTotalsCalculation: false
69
+ };
70
+ var groupingInfos = [];
71
+ var groups = [];
72
+ var toggledGroupsByLevel = [];
73
+ var groupingDelimiter = ':|:';
74
+
75
+ var pagesize = 0;
76
+ var pagenum = 0;
77
+ var totalRows = 0;
78
+
79
+ // events
80
+ var onRowCountChanged = new Slick.Event();
81
+ var onRowsChanged = new Slick.Event();
82
+ var onPagingInfoChanged = new Slick.Event();
83
+
84
+ options = $.extend(true, {}, defaults, options);
85
+
86
+
87
+ function beginUpdate() {
88
+ suspend = true;
89
+ }
90
+
91
+ function endUpdate() {
92
+ suspend = false;
93
+ refresh();
94
+ }
95
+
96
+ function setRefreshHints(hints) {
97
+ refreshHints = hints;
98
+ }
99
+
100
+ function setFilterArgs(args) {
101
+ filterArgs = args;
102
+ }
103
+
104
+ function updateIdxById(startingIndex) {
105
+ startingIndex = startingIndex || 0;
106
+ var id;
107
+ for (var i = startingIndex, l = items.length; i < l; i++) {
108
+ id = items[i][idProperty];
109
+ if (id === undefined) {
110
+ throw new Error("Each data element must implement a unique 'id' property");
111
+ }
112
+ idxById[id] = i;
113
+ }
114
+ }
115
+
116
+ function ensureIdUniqueness() {
117
+ var id;
118
+ for (var i = 0, l = items.length; i < l; i++) {
119
+ id = items[i][idProperty];
120
+ if (id === undefined || idxById[id] !== i) {
121
+ throw new Error("Each data element must implement a unique 'id' property");
122
+ }
123
+ }
124
+ }
125
+
126
+ function getItems() {
127
+ return items;
128
+ }
129
+
130
+ function setItems(data, objectIdProperty) {
131
+ if (objectIdProperty !== undefined) {
132
+ idProperty = objectIdProperty;
133
+ }
134
+ items = filteredItems = data;
135
+ idxById = {};
136
+ updateIdxById();
137
+ ensureIdUniqueness();
138
+ refresh();
139
+ }
140
+
141
+ function setPagingOptions(args) {
142
+ if (args.pageSize != undefined) {
143
+ pagesize = args.pageSize;
144
+ pagenum = pagesize ? Math.min(pagenum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)) : 0;
145
+ }
146
+
147
+ if (args.pageNum != undefined) {
148
+ pagenum = Math.min(args.pageNum, Math.max(0, Math.ceil(totalRows / pagesize) - 1));
149
+ }
150
+
151
+ onPagingInfoChanged.notify(getPagingInfo(), null, self);
152
+
153
+ refresh();
154
+ }
155
+
156
+ function getPagingInfo() {
157
+ var totalPages = pagesize ? Math.max(1, Math.ceil(totalRows / pagesize)) : 1;
158
+ return {pageSize: pagesize, pageNum: pagenum, totalRows: totalRows, totalPages: totalPages, dataView: self};
159
+ }
160
+
161
+ function sort(comparer, ascending) {
162
+ sortAsc = ascending;
163
+ sortComparer = comparer;
164
+ fastSortField = null;
165
+ if (ascending === false) {
166
+ items.reverse();
167
+ }
168
+ items.sort(comparer);
169
+ if (ascending === false) {
170
+ items.reverse();
171
+ }
172
+ idxById = {};
173
+ updateIdxById();
174
+ refresh();
175
+ }
176
+
177
+ /***
178
+ * Provides a workaround for the extremely slow sorting in IE.
179
+ * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString
180
+ * to return the value of that field and then doing a native Array.sort().
181
+ */
182
+ function fastSort(field, ascending) {
183
+ sortAsc = ascending;
184
+ fastSortField = field;
185
+ sortComparer = null;
186
+ var oldToString = Object.prototype.toString;
187
+ Object.prototype.toString = (typeof field == "function") ? field : function () {
188
+ return this[field]
189
+ };
190
+ // an extra reversal for descending sort keeps the sort stable
191
+ // (assuming a stable native sort implementation, which isn't true in some cases)
192
+ if (ascending === false) {
193
+ items.reverse();
194
+ }
195
+ items.sort();
196
+ Object.prototype.toString = oldToString;
197
+ if (ascending === false) {
198
+ items.reverse();
199
+ }
200
+ idxById = {};
201
+ updateIdxById();
202
+ refresh();
203
+ }
204
+
205
+ function reSort() {
206
+ if (sortComparer) {
207
+ sort(sortComparer, sortAsc);
208
+ } else if (fastSortField) {
209
+ fastSort(fastSortField, sortAsc);
210
+ }
211
+ }
212
+
213
+ function getFilteredItems(){
214
+ return filteredItems;
215
+ }
216
+
217
+
218
+ function getFilter(){
219
+ return filter;
220
+ }
221
+
222
+ function setFilter(filterFn) {
223
+ filter = filterFn;
224
+ if (options.inlineFilters) {
225
+ compiledFilter = compileFilter();
226
+ compiledFilterWithCaching = compileFilterWithCaching();
227
+ }
228
+ refresh();
229
+ }
230
+
231
+ function getGrouping() {
232
+ return groupingInfos;
233
+ }
234
+
235
+ function setGrouping(groupingInfo) {
236
+ if (!options.groupItemMetadataProvider) {
237
+ options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider();
238
+ }
239
+
240
+ groups = [];
241
+ toggledGroupsByLevel = [];
242
+ groupingInfo = groupingInfo || [];
243
+ groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo];
244
+
245
+ for (var i = 0; i < groupingInfos.length; i++) {
246
+ var gi = groupingInfos[i] = $.extend(true, {}, groupingInfoDefaults, groupingInfos[i]);
247
+ gi.getterIsAFn = typeof gi.getter === "function";
248
+
249
+ // pre-compile accumulator loops
250
+ gi.compiledAccumulators = [];
251
+ var idx = gi.aggregators.length;
252
+ while (idx--) {
253
+ gi.compiledAccumulators[idx] = compileAccumulatorLoop(gi.aggregators[idx]);
254
+ }
255
+
256
+ toggledGroupsByLevel[i] = {};
257
+ }
258
+
259
+ refresh();
260
+ }
261
+
262
+ /**
263
+ * @deprecated Please use {@link setGrouping}.
264
+ */
265
+ function groupBy(valueGetter, valueFormatter, sortComparer) {
266
+ if (valueGetter == null) {
267
+ setGrouping([]);
268
+ return;
269
+ }
270
+
271
+ setGrouping({
272
+ getter: valueGetter,
273
+ formatter: valueFormatter,
274
+ comparer: sortComparer
275
+ });
276
+ }
277
+
278
+ /**
279
+ * @deprecated Please use {@link setGrouping}.
280
+ */
281
+ function setAggregators(groupAggregators, includeCollapsed) {
282
+ if (!groupingInfos.length) {
283
+ throw new Error("At least one grouping must be specified before calling setAggregators().");
284
+ }
285
+
286
+ groupingInfos[0].aggregators = groupAggregators;
287
+ groupingInfos[0].aggregateCollapsed = includeCollapsed;
288
+
289
+ setGrouping(groupingInfos);
290
+ }
291
+
292
+ function getItemByIdx(i) {
293
+ return items[i];
294
+ }
295
+
296
+ function getIdxById(id) {
297
+ return idxById[id];
298
+ }
299
+
300
+ function ensureRowsByIdCache() {
301
+ if (!rowsById) {
302
+ rowsById = {};
303
+ for (var i = 0, l = rows.length; i < l; i++) {
304
+ rowsById[rows[i][idProperty]] = i;
305
+ }
306
+ }
307
+ }
308
+
309
+ function getRowByItem(item) {
310
+ ensureRowsByIdCache();
311
+ return rowsById[item[idProperty]];
312
+ }
313
+
314
+ function getRowById(id) {
315
+ ensureRowsByIdCache();
316
+ return rowsById[id];
317
+ }
318
+
319
+ function getItemById(id) {
320
+ return items[idxById[id]];
321
+ }
322
+
323
+ function mapItemsToRows(itemArray) {
324
+ var rows = [];
325
+ ensureRowsByIdCache();
326
+ for (var i = 0, l = itemArray.length; i < l; i++) {
327
+ var row = rowsById[itemArray[i][idProperty]];
328
+ if (row != null) {
329
+ rows[rows.length] = row;
330
+ }
331
+ }
332
+ return rows;
333
+ }
334
+
335
+ function mapIdsToRows(idArray) {
336
+ var rows = [];
337
+ ensureRowsByIdCache();
338
+ for (var i = 0, l = idArray.length; i < l; i++) {
339
+ var row = rowsById[idArray[i]];
340
+ if (row != null) {
341
+ rows[rows.length] = row;
342
+ }
343
+ }
344
+ return rows;
345
+ }
346
+
347
+ function mapRowsToIds(rowArray) {
348
+ var ids = [];
349
+ for (var i = 0, l = rowArray.length; i < l; i++) {
350
+ if (rowArray[i] < rows.length) {
351
+ ids[ids.length] = rows[rowArray[i]][idProperty];
352
+ }
353
+ }
354
+ return ids;
355
+ }
356
+
357
+ function updateItem(id, item) {
358
+ if (idxById[id] === undefined || id !== item[idProperty]) {
359
+ throw new Error("Invalid or non-matching id");
360
+ }
361
+ items[idxById[id]] = item;
362
+ if (!updated) {
363
+ updated = {};
364
+ }
365
+ updated[id] = true;
366
+ refresh();
367
+ }
368
+
369
+ function insertItem(insertBefore, item) {
370
+ items.splice(insertBefore, 0, item);
371
+ updateIdxById(insertBefore);
372
+ refresh();
373
+ }
374
+
375
+ function addItem(item) {
376
+ items.push(item);
377
+ updateIdxById(items.length - 1);
378
+ refresh();
379
+ }
380
+
381
+ function deleteItem(id) {
382
+ var idx = idxById[id];
383
+ if (idx === undefined) {
384
+ throw new Error("Invalid id");
385
+ }
386
+ delete idxById[id];
387
+ items.splice(idx, 1);
388
+ updateIdxById(idx);
389
+ refresh();
390
+ }
391
+
392
+ function sortedAddItem(item) {
393
+ if(!sortComparer) {
394
+ throw new Error("sortedAddItem() requires a sort comparer, use sort()");
395
+ }
396
+ insertItem(sortedIndex(item), item);
397
+ }
398
+
399
+ function sortedUpdateItem(id, item) {
400
+ if (idxById[id] === undefined || id !== item[idProperty]) {
401
+ throw new Error("Invalid or non-matching id " + idxById[id]);
402
+ }
403
+ if(!sortComparer) {
404
+ throw new Error("sortedUpdateItem() requires a sort comparer, use sort()");
405
+ }
406
+ var oldItem = getItemById(id);
407
+ if(sortComparer(oldItem, item) !== 0) {
408
+ // item affects sorting -> must use sorted add
409
+ deleteItem(id);
410
+ sortedAddItem(item);
411
+ }
412
+ else { // update does not affect sorting -> regular update works fine
413
+ updateItem(id, item);
414
+ }
415
+ }
416
+
417
+ function sortedIndex(searchItem) {
418
+ var low = 0, high = items.length;
419
+
420
+ while (low < high) {
421
+ var mid = low + high >>> 1;
422
+ if (sortComparer(items[mid], searchItem) === -1) {
423
+ low = mid + 1;
424
+ }
425
+ else {
426
+ high = mid;
427
+ }
428
+ }
429
+ return low;
430
+ }
431
+
432
+ function getLength() {
433
+ return rows.length;
434
+ }
435
+
436
+ function getItem(i) {
437
+ var item = rows[i];
438
+
439
+ // if this is a group row, make sure totals are calculated and update the title
440
+ if (item && item.__group && item.totals && !item.totals.initialized) {
441
+ var gi = groupingInfos[item.level];
442
+ if (!gi.displayTotalsRow) {
443
+ calculateTotals(item.totals);
444
+ item.title = gi.formatter ? gi.formatter(item) : item.value;
445
+ }
446
+ }
447
+ // if this is a totals row, make sure it's calculated
448
+ else if (item && item.__groupTotals && !item.initialized) {
449
+ calculateTotals(item);
450
+ }
451
+
452
+ return item;
453
+ }
454
+
455
+ function getItemMetadata(i) {
456
+ var item = rows[i];
457
+ if (item === undefined) {
458
+ return null;
459
+ }
460
+
461
+ // overrides for grouping rows
462
+ if (item.__group) {
463
+ return options.groupItemMetadataProvider.getGroupRowMetadata(item);
464
+ }
465
+
466
+ // overrides for totals rows
467
+ if (item.__groupTotals) {
468
+ return options.groupItemMetadataProvider.getTotalsRowMetadata(item);
469
+ }
470
+
471
+ return null;
472
+ }
473
+
474
+ function expandCollapseAllGroups(level, collapse) {
475
+ if (level == null) {
476
+ for (var i = 0; i < groupingInfos.length; i++) {
477
+ toggledGroupsByLevel[i] = {};
478
+ groupingInfos[i].collapsed = collapse;
479
+ }
480
+ } else {
481
+ toggledGroupsByLevel[level] = {};
482
+ groupingInfos[level].collapsed = collapse;
483
+ }
484
+ refresh();
485
+ }
486
+
487
+ /**
488
+ * @param level {Number} Optional level to collapse. If not specified, applies to all levels.
489
+ */
490
+ function collapseAllGroups(level) {
491
+ expandCollapseAllGroups(level, true);
492
+ }
493
+
494
+ /**
495
+ * @param level {Number} Optional level to expand. If not specified, applies to all levels.
496
+ */
497
+ function expandAllGroups(level) {
498
+ expandCollapseAllGroups(level, false);
499
+ }
500
+
501
+ function expandCollapseGroup(level, groupingKey, collapse) {
502
+ toggledGroupsByLevel[level][groupingKey] = groupingInfos[level].collapsed ^ collapse;
503
+ refresh();
504
+ }
505
+
506
+ /**
507
+ * @param varArgs Either a Slick.Group's "groupingKey" property, or a
508
+ * variable argument list of grouping values denoting a unique path to the row. For
509
+ * example, calling collapseGroup('high', '10%') will collapse the '10%' subgroup of
510
+ * the 'high' group.
511
+ */
512
+ function collapseGroup(varArgs) {
513
+ var args = Array.prototype.slice.call(arguments);
514
+ var arg0 = args[0];
515
+ if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) {
516
+ expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, true);
517
+ } else {
518
+ expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), true);
519
+ }
520
+ }
521
+
522
+ /**
523
+ * @param varArgs Either a Slick.Group's "groupingKey" property, or a
524
+ * variable argument list of grouping values denoting a unique path to the row. For
525
+ * example, calling expandGroup('high', '10%') will expand the '10%' subgroup of
526
+ * the 'high' group.
527
+ */
528
+ function expandGroup(varArgs) {
529
+ var args = Array.prototype.slice.call(arguments);
530
+ var arg0 = args[0];
531
+ if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) {
532
+ expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, false);
533
+ } else {
534
+ expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), false);
535
+ }
536
+ }
537
+
538
+ function getGroups() {
539
+ return groups;
540
+ }
541
+
542
+ function extractGroups(rows, parentGroup) {
543
+ var group;
544
+ var val;
545
+ var groups = [];
546
+ var groupsByVal = {};
547
+ var r;
548
+ var level = parentGroup ? parentGroup.level + 1 : 0;
549
+ var gi = groupingInfos[level];
550
+
551
+ for (var i = 0, l = gi.predefinedValues.length; i < l; i++) {
552
+ val = gi.predefinedValues[i];
553
+ group = groupsByVal[val];
554
+ if (!group) {
555
+ group = new Slick.Group();
556
+ group.value = val;
557
+ group.level = level;
558
+ group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val;
559
+ groups[groups.length] = group;
560
+ groupsByVal[val] = group;
561
+ }
562
+ }
563
+
564
+ for (var i = 0, l = rows.length; i < l; i++) {
565
+ r = rows[i];
566
+ val = gi.getterIsAFn ? gi.getter(r) : r[gi.getter];
567
+ group = groupsByVal[val];
568
+ if (!group) {
569
+ group = new Slick.Group();
570
+ group.value = val;
571
+ group.level = level;
572
+ group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val;
573
+ groups[groups.length] = group;
574
+ groupsByVal[val] = group;
575
+ }
576
+
577
+ group.rows[group.count++] = r;
578
+ }
579
+
580
+ if (level < groupingInfos.length - 1) {
581
+ for (var i = 0; i < groups.length; i++) {
582
+ group = groups[i];
583
+ group.groups = extractGroups(group.rows, group);
584
+ }
585
+ }
586
+
587
+ groups.sort(groupingInfos[level].comparer);
588
+
589
+ return groups;
590
+ }
591
+
592
+ function calculateTotals(totals) {
593
+ var group = totals.group;
594
+ var gi = groupingInfos[group.level];
595
+ var isLeafLevel = (group.level == groupingInfos.length);
596
+ var agg, idx = gi.aggregators.length;
597
+
598
+ if (!isLeafLevel && gi.aggregateChildGroups) {
599
+ // make sure all the subgroups are calculated
600
+ var i = group.groups.length;
601
+ while (i--) {
602
+ if (!group.groups[i].totals.initialized) {
603
+ calculateTotals(group.groups[i].totals);
604
+ }
605
+ }
606
+ }
607
+
608
+ while (idx--) {
609
+ agg = gi.aggregators[idx];
610
+ agg.init();
611
+ if (!isLeafLevel && gi.aggregateChildGroups) {
612
+ gi.compiledAccumulators[idx].call(agg, group.groups);
613
+ } else {
614
+ gi.compiledAccumulators[idx].call(agg, group.rows);
615
+ }
616
+ agg.storeResult(totals);
617
+ }
618
+ totals.initialized = true;
619
+ }
620
+
621
+ function addGroupTotals(group) {
622
+ var gi = groupingInfos[group.level];
623
+ var totals = new Slick.GroupTotals();
624
+ totals.group = group;
625
+ group.totals = totals;
626
+ if (!gi.lazyTotalsCalculation) {
627
+ calculateTotals(totals);
628
+ }
629
+ }
630
+
631
+ function addTotals(groups, level) {
632
+ level = level || 0;
633
+ var gi = groupingInfos[level];
634
+ var groupCollapsed = gi.collapsed;
635
+ var toggledGroups = toggledGroupsByLevel[level];
636
+ var idx = groups.length, g;
637
+ while (idx--) {
638
+ g = groups[idx];
639
+
640
+ if (g.collapsed && !gi.aggregateCollapsed) {
641
+ continue;
642
+ }
643
+
644
+ // Do a depth-first aggregation so that parent group aggregators can access subgroup totals.
645
+ if (g.groups) {
646
+ addTotals(g.groups, level + 1);
647
+ }
648
+
649
+ if (gi.aggregators.length && (
650
+ gi.aggregateEmpty || g.rows.length || (g.groups && g.groups.length))) {
651
+ addGroupTotals(g);
652
+ }
653
+
654
+ g.collapsed = groupCollapsed ^ toggledGroups[g.groupingKey];
655
+ g.title = gi.formatter ? gi.formatter(g) : g.value;
656
+ }
657
+ }
658
+
659
+ function flattenGroupedRows(groups, level) {
660
+ level = level || 0;
661
+ var gi = groupingInfos[level];
662
+ var groupedRows = [], rows, gl = 0, g;
663
+ for (var i = 0, l = groups.length; i < l; i++) {
664
+ g = groups[i];
665
+ groupedRows[gl++] = g;
666
+
667
+ if (!g.collapsed) {
668
+ rows = g.groups ? flattenGroupedRows(g.groups, level + 1) : g.rows;
669
+ for (var j = 0, jj = rows.length; j < jj; j++) {
670
+ groupedRows[gl++] = rows[j];
671
+ }
672
+ }
673
+
674
+ if (g.totals && gi.displayTotalsRow && (!g.collapsed || gi.aggregateCollapsed)) {
675
+ groupedRows[gl++] = g.totals;
676
+ }
677
+ }
678
+ return groupedRows;
679
+ }
680
+
681
+ function getFunctionInfo(fn) {
682
+ var fnRegex = /^function[^(]*\(([^)]*)\)\s*{([\s\S]*)}$/;
683
+ var matches = fn.toString().match(fnRegex);
684
+ return {
685
+ params: matches[1].split(","),
686
+ body: matches[2]
687
+ };
688
+ }
689
+
690
+ function compileAccumulatorLoop(aggregator) {
691
+ var accumulatorInfo = getFunctionInfo(aggregator.accumulate);
692
+ var fn = new Function(
693
+ "_items",
694
+ "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" +
695
+ accumulatorInfo.params[0] + " = _items[_i]; " +
696
+ accumulatorInfo.body +
697
+ "}"
698
+ );
699
+ fn.displayName = fn.name = "compiledAccumulatorLoop";
700
+ return fn;
701
+ }
702
+
703
+ function compileFilter() {
704
+ var filterInfo = getFunctionInfo(filter);
705
+
706
+ var filterPath1 = "{ continue _coreloop; }$1";
707
+ var filterPath2 = "{ _retval[_idx++] = $item$; continue _coreloop; }$1";
708
+ // make some allowances for minification - there's only so far we can go with RegEx
709
+ var filterBody = filterInfo.body
710
+ .replace(/return false\s*([;}]|\}|$)/gi, filterPath1)
711
+ .replace(/return!1([;}]|\}|$)/gi, filterPath1)
712
+ .replace(/return true\s*([;}]|\}|$)/gi, filterPath2)
713
+ .replace(/return!0([;}]|\}|$)/gi, filterPath2)
714
+ .replace(/return ([^;}]+?)\s*([;}]|$)/gi,
715
+ "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }$2");
716
+
717
+ // This preserves the function template code after JS compression,
718
+ // so that replace() commands still work as expected.
719
+ var tpl = [
720
+ //"function(_items, _args) { ",
721
+ "var _retval = [], _idx = 0; ",
722
+ "var $item$, $args$ = _args; ",
723
+ "_coreloop: ",
724
+ "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
725
+ "$item$ = _items[_i]; ",
726
+ "$filter$; ",
727
+ "} ",
728
+ "return _retval; "
729
+ //"}"
730
+ ].join("");
731
+ tpl = tpl.replace(/\$filter\$/gi, filterBody);
732
+ tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
733
+ tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
734
+
735
+ var fn = new Function("_items,_args", tpl);
736
+ fn.displayName = fn.name = "compiledFilter";
737
+ return fn;
738
+ }
739
+
740
+ function compileFilterWithCaching() {
741
+ var filterInfo = getFunctionInfo(filter);
742
+
743
+ var filterPath1 = "{ continue _coreloop; }$1";
744
+ var filterPath2 = "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }$1";
745
+ // make some allowances for minification - there's only so far we can go with RegEx
746
+ var filterBody = filterInfo.body
747
+ .replace(/return false\s*([;}]|\}|$)/gi, filterPath1)
748
+ .replace(/return!1([;}]|\}|$)/gi, filterPath1)
749
+ .replace(/return true\s*([;}]|\}|$)/gi, filterPath2)
750
+ .replace(/return!0([;}]|\}|$)/gi, filterPath2)
751
+ .replace(/return ([^;}]+?)\s*([;}]|$)/gi,
752
+ "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }$2");
753
+
754
+ // This preserves the function template code after JS compression,
755
+ // so that replace() commands still work as expected.
756
+ var tpl = [
757
+ //"function(_items, _args, _cache) { ",
758
+ "var _retval = [], _idx = 0; ",
759
+ "var $item$, $args$ = _args; ",
760
+ "_coreloop: ",
761
+ "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
762
+ "$item$ = _items[_i]; ",
763
+ "if (_cache[_i]) { ",
764
+ "_retval[_idx++] = $item$; ",
765
+ "continue _coreloop; ",
766
+ "} ",
767
+ "$filter$; ",
768
+ "} ",
769
+ "return _retval; "
770
+ //"}"
771
+ ].join("");
772
+ tpl = tpl.replace(/\$filter\$/gi, filterBody);
773
+ tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
774
+ tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
775
+
776
+ var fn = new Function("_items,_args,_cache", tpl);
777
+ fn.displayName = fn.name = "compiledFilterWithCaching";
778
+ return fn;
779
+ }
780
+
781
+ function uncompiledFilter(items, args) {
782
+ var retval = [], idx = 0;
783
+
784
+ for (var i = 0, ii = items.length; i < ii; i++) {
785
+ if (filter(items[i], args)) {
786
+ retval[idx++] = items[i];
787
+ }
788
+ }
789
+
790
+ return retval;
791
+ }
792
+
793
+ function uncompiledFilterWithCaching(items, args, cache) {
794
+ var retval = [], idx = 0, item;
795
+
796
+ for (var i = 0, ii = items.length; i < ii; i++) {
797
+ item = items[i];
798
+ if (cache[i]) {
799
+ retval[idx++] = item;
800
+ } else if (filter(item, args)) {
801
+ retval[idx++] = item;
802
+ cache[i] = true;
803
+ }
804
+ }
805
+
806
+ return retval;
807
+ }
808
+
809
+ function getFilteredAndPagedItems(items) {
810
+ if (filter) {
811
+ var batchFilter = options.inlineFilters ? compiledFilter : uncompiledFilter;
812
+ var batchFilterWithCaching = options.inlineFilters ? compiledFilterWithCaching : uncompiledFilterWithCaching;
813
+
814
+ if (refreshHints.isFilterNarrowing) {
815
+ filteredItems = batchFilter(filteredItems, filterArgs);
816
+ } else if (refreshHints.isFilterExpanding) {
817
+ filteredItems = batchFilterWithCaching(items, filterArgs, filterCache);
818
+ } else if (!refreshHints.isFilterUnchanged) {
819
+ filteredItems = batchFilter(items, filterArgs);
820
+ }
821
+ } else {
822
+ // special case: if not filtering and not paging, the resulting
823
+ // rows collection needs to be a copy so that changes due to sort
824
+ // can be caught
825
+ filteredItems = pagesize ? items : items.concat();
826
+ }
827
+
828
+ // get the current page
829
+ var paged;
830
+ if (pagesize) {
831
+ if (filteredItems.length <= pagenum * pagesize) {
832
+ if (filteredItems.length === 0) {
833
+ pagenum = 0;
834
+ } else {
835
+ pagenum = Math.floor((filteredItems.length - 1) / pagesize);
836
+ }
837
+ }
838
+ paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize);
839
+ } else {
840
+ paged = filteredItems;
841
+ }
842
+ return {totalRows: filteredItems.length, rows: paged};
843
+ }
844
+
845
+ function getRowDiffs(rows, newRows) {
846
+ var item, r, eitherIsNonData, diff = [];
847
+ var from = 0, to = newRows.length;
848
+
849
+ if (refreshHints && refreshHints.ignoreDiffsBefore) {
850
+ from = Math.max(0,
851
+ Math.min(newRows.length, refreshHints.ignoreDiffsBefore));
852
+ }
853
+
854
+ if (refreshHints && refreshHints.ignoreDiffsAfter) {
855
+ to = Math.min(newRows.length,
856
+ Math.max(0, refreshHints.ignoreDiffsAfter));
857
+ }
858
+
859
+ for (var i = from, rl = rows.length; i < to; i++) {
860
+ if (i >= rl) {
861
+ diff[diff.length] = i;
862
+ } else {
863
+ item = newRows[i];
864
+ r = rows[i];
865
+
866
+ if ((groupingInfos.length && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) &&
867
+ item.__group !== r.__group ||
868
+ item.__group && !item.equals(r))
869
+ || (eitherIsNonData &&
870
+ // no good way to compare totals since they are arbitrary DTOs
871
+ // deep object comparison is pretty expensive
872
+ // always considering them 'dirty' seems easier for the time being
873
+ (item.__groupTotals || r.__groupTotals))
874
+ || item[idProperty] != r[idProperty]
875
+ || (updated && updated[item[idProperty]])
876
+ ) {
877
+ diff[diff.length] = i;
878
+ }
879
+ }
880
+ }
881
+ return diff;
882
+ }
883
+
884
+ function recalc(_items) {
885
+ rowsById = null;
886
+
887
+ if (refreshHints.isFilterNarrowing != prevRefreshHints.isFilterNarrowing ||
888
+ refreshHints.isFilterExpanding != prevRefreshHints.isFilterExpanding) {
889
+ filterCache = [];
890
+ }
891
+
892
+ var filteredItems = getFilteredAndPagedItems(_items);
893
+ totalRows = filteredItems.totalRows;
894
+ var newRows = filteredItems.rows;
895
+
896
+ groups = [];
897
+ if (groupingInfos.length) {
898
+ groups = extractGroups(newRows);
899
+ if (groups.length) {
900
+ addTotals(groups);
901
+ newRows = flattenGroupedRows(groups);
902
+ }
903
+ }
904
+
905
+ var diff = getRowDiffs(rows, newRows);
906
+
907
+ rows = newRows;
908
+
909
+ return diff;
910
+ }
911
+
912
+ function refresh() {
913
+ if (suspend) {
914
+ return;
915
+ }
916
+
917
+ var countBefore = rows.length;
918
+ var totalRowsBefore = totalRows;
919
+
920
+ var diff = recalc(items, filter); // pass as direct refs to avoid closure perf hit
921
+
922
+ // if the current page is no longer valid, go to last page and recalc
923
+ // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
924
+ if (pagesize && totalRows < pagenum * pagesize) {
925
+ pagenum = Math.max(0, Math.ceil(totalRows / pagesize) - 1);
926
+ diff = recalc(items, filter);
927
+ }
928
+
929
+ updated = null;
930
+ prevRefreshHints = refreshHints;
931
+ refreshHints = {};
932
+
933
+ if (totalRowsBefore !== totalRows) {
934
+ onPagingInfoChanged.notify(getPagingInfo(), null, self);
935
+ }
936
+ if (countBefore !== rows.length) {
937
+ onRowCountChanged.notify({previous: countBefore, current: rows.length, dataView: self}, null, self);
938
+ }
939
+ if (diff.length > 0) {
940
+ onRowsChanged.notify({rows: diff, dataView: self}, null, self);
941
+ }
942
+ }
943
+
944
+ /***
945
+ * Wires the grid and the DataView together to keep row selection tied to item ids.
946
+ * This is useful since, without it, the grid only knows about rows, so if the items
947
+ * move around, the same rows stay selected instead of the selection moving along
948
+ * with the items.
949
+ *
950
+ * NOTE: This doesn't work with cell selection model.
951
+ *
952
+ * @param grid {Slick.Grid} The grid to sync selection with.
953
+ * @param preserveHidden {Boolean} Whether to keep selected items that go out of the
954
+ * view due to them getting filtered out.
955
+ * @param preserveHiddenOnSelectionChange {Boolean} Whether to keep selected items
956
+ * that are currently out of the view (see preserveHidden) as selected when selection
957
+ * changes.
958
+ * @return {Slick.Event} An event that notifies when an internal list of selected row ids
959
+ * changes. This is useful since, in combination with the above two options, it allows
960
+ * access to the full list selected row ids, and not just the ones visible to the grid.
961
+ * @method syncGridSelection
962
+ */
963
+ function syncGridSelection(grid, preserveHidden, preserveHiddenOnSelectionChange) {
964
+ var self = this;
965
+ var inHandler;
966
+ var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());
967
+ var onSelectedRowIdsChanged = new Slick.Event();
968
+
969
+ function setSelectedRowIds(rowIds) {
970
+ if (selectedRowIds.join(",") == rowIds.join(",")) {
971
+ return;
972
+ }
973
+
974
+ selectedRowIds = rowIds;
975
+
976
+ onSelectedRowIdsChanged.notify({
977
+ "grid": grid,
978
+ "ids": selectedRowIds,
979
+ "dataView": self
980
+ }, new Slick.EventData(), self);
981
+ }
982
+
983
+ function update() {
984
+ if (selectedRowIds.length > 0) {
985
+ inHandler = true;
986
+ var selectedRows = self.mapIdsToRows(selectedRowIds);
987
+ if (!preserveHidden) {
988
+ setSelectedRowIds(self.mapRowsToIds(selectedRows));
989
+ }
990
+ grid.setSelectedRows(selectedRows);
991
+ inHandler = false;
992
+ }
993
+ }
994
+
995
+ grid.onSelectedRowsChanged.subscribe(function(e, args) {
996
+ if (inHandler) { return; }
997
+ var newSelectedRowIds = self.mapRowsToIds(grid.getSelectedRows());
998
+ if (!preserveHiddenOnSelectionChange || !grid.getOptions().multiSelect) {
999
+ setSelectedRowIds(newSelectedRowIds);
1000
+ } else {
1001
+ // keep the ones that are hidden
1002
+ var existing = $.grep(selectedRowIds, function(id) { return self.getRowById(id) === undefined; });
1003
+ // add the newly selected ones
1004
+ setSelectedRowIds(existing.concat(newSelectedRowIds));
1005
+ }
1006
+ });
1007
+
1008
+ this.onRowsChanged.subscribe(update);
1009
+
1010
+ this.onRowCountChanged.subscribe(update);
1011
+
1012
+ return onSelectedRowIdsChanged;
1013
+ }
1014
+
1015
+ function syncGridCellCssStyles(grid, key) {
1016
+ var hashById;
1017
+ var inHandler;
1018
+
1019
+ // since this method can be called after the cell styles have been set,
1020
+ // get the existing ones right away
1021
+ storeCellCssStyles(grid.getCellCssStyles(key));
1022
+
1023
+ function storeCellCssStyles(hash) {
1024
+ hashById = {};
1025
+ for (var row in hash) {
1026
+ var id = rows[row][idProperty];
1027
+ hashById[id] = hash[row];
1028
+ }
1029
+ }
1030
+
1031
+ function update() {
1032
+ if (hashById) {
1033
+ inHandler = true;
1034
+ ensureRowsByIdCache();
1035
+ var newHash = {};
1036
+ for (var id in hashById) {
1037
+ var row = rowsById[id];
1038
+ if (row != undefined) {
1039
+ newHash[row] = hashById[id];
1040
+ }
1041
+ }
1042
+ grid.setCellCssStyles(key, newHash);
1043
+ inHandler = false;
1044
+ }
1045
+ }
1046
+
1047
+ grid.onCellCssStylesChanged.subscribe(function(e, args) {
1048
+ if (inHandler) { return; }
1049
+ if (key != args.key) { return; }
1050
+ if (args.hash) {
1051
+ storeCellCssStyles(args.hash);
1052
+ } else {
1053
+ grid.onCellCssStylesChanged.unsubscribe(styleChanged);
1054
+ self.onRowsChanged.unsubscribe(update);
1055
+ self.onRowCountChanged.unsubscribe(update);
1056
+ }
1057
+ });
1058
+
1059
+ this.onRowsChanged.subscribe(update);
1060
+
1061
+ this.onRowCountChanged.subscribe(update);
1062
+ }
1063
+
1064
+ $.extend(this, {
1065
+ // methods
1066
+ "beginUpdate": beginUpdate,
1067
+ "endUpdate": endUpdate,
1068
+ "setPagingOptions": setPagingOptions,
1069
+ "getPagingInfo": getPagingInfo,
1070
+ "getItems": getItems,
1071
+ "setItems": setItems,
1072
+ "setFilter": setFilter,
1073
+ "getFilter": getFilter,
1074
+ "getFilteredItems": getFilteredItems,
1075
+ "sort": sort,
1076
+ "fastSort": fastSort,
1077
+ "reSort": reSort,
1078
+ "setGrouping": setGrouping,
1079
+ "getGrouping": getGrouping,
1080
+ "groupBy": groupBy,
1081
+ "setAggregators": setAggregators,
1082
+ "collapseAllGroups": collapseAllGroups,
1083
+ "expandAllGroups": expandAllGroups,
1084
+ "collapseGroup": collapseGroup,
1085
+ "expandGroup": expandGroup,
1086
+ "getGroups": getGroups,
1087
+ "getIdxById": getIdxById,
1088
+ "getRowByItem": getRowByItem,
1089
+ "getRowById": getRowById,
1090
+ "getItemById": getItemById,
1091
+ "getItemByIdx": getItemByIdx,
1092
+ "mapItemsToRows": mapItemsToRows,
1093
+ "mapRowsToIds": mapRowsToIds,
1094
+ "mapIdsToRows": mapIdsToRows,
1095
+ "setRefreshHints": setRefreshHints,
1096
+ "setFilterArgs": setFilterArgs,
1097
+ "refresh": refresh,
1098
+ "updateItem": updateItem,
1099
+ "insertItem": insertItem,
1100
+ "addItem": addItem,
1101
+ "deleteItem": deleteItem,
1102
+ "sortedAddItem": sortedAddItem,
1103
+ "sortedUpdateItem": sortedUpdateItem,
1104
+ "syncGridSelection": syncGridSelection,
1105
+ "syncGridCellCssStyles": syncGridCellCssStyles,
1106
+
1107
+ // data provider methods
1108
+ "getLength": getLength,
1109
+ "getItem": getItem,
1110
+ "getItemMetadata": getItemMetadata,
1111
+
1112
+ // events
1113
+ "onRowCountChanged": onRowCountChanged,
1114
+ "onRowsChanged": onRowsChanged,
1115
+ "onPagingInfoChanged": onPagingInfoChanged
1116
+ });
1117
+ }
1118
+
1119
+ function AvgAggregator(field) {
1120
+ this.field_ = field;
1121
+
1122
+ this.init = function () {
1123
+ this.count_ = 0;
1124
+ this.nonNullCount_ = 0;
1125
+ this.sum_ = 0;
1126
+ };
1127
+
1128
+ this.accumulate = function (item) {
1129
+ var val = item[this.field_];
1130
+ this.count_++;
1131
+ if (val != null && val !== "" && !isNaN(val)) {
1132
+ this.nonNullCount_++;
1133
+ this.sum_ += parseFloat(val);
1134
+ }
1135
+ };
1136
+
1137
+ this.storeResult = function (groupTotals) {
1138
+ if (!groupTotals.avg) {
1139
+ groupTotals.avg = {};
1140
+ }
1141
+ if (this.nonNullCount_ != 0) {
1142
+ groupTotals.avg[this.field_] = this.sum_ / this.nonNullCount_;
1143
+ }
1144
+ };
1145
+ }
1146
+
1147
+ function MinAggregator(field) {
1148
+ this.field_ = field;
1149
+
1150
+ this.init = function () {
1151
+ this.min_ = null;
1152
+ };
1153
+
1154
+ this.accumulate = function (item) {
1155
+ var val = item[this.field_];
1156
+ if (val != null && val !== "" && !isNaN(val)) {
1157
+ if (this.min_ == null || val < this.min_) {
1158
+ this.min_ = val;
1159
+ }
1160
+ }
1161
+ };
1162
+
1163
+ this.storeResult = function (groupTotals) {
1164
+ if (!groupTotals.min) {
1165
+ groupTotals.min = {};
1166
+ }
1167
+ groupTotals.min[this.field_] = this.min_;
1168
+ }
1169
+ }
1170
+
1171
+ function MaxAggregator(field) {
1172
+ this.field_ = field;
1173
+
1174
+ this.init = function () {
1175
+ this.max_ = null;
1176
+ };
1177
+
1178
+ this.accumulate = function (item) {
1179
+ var val = item[this.field_];
1180
+ if (val != null && val !== "" && !isNaN(val)) {
1181
+ if (this.max_ == null || val > this.max_) {
1182
+ this.max_ = val;
1183
+ }
1184
+ }
1185
+ };
1186
+
1187
+ this.storeResult = function (groupTotals) {
1188
+ if (!groupTotals.max) {
1189
+ groupTotals.max = {};
1190
+ }
1191
+ groupTotals.max[this.field_] = this.max_;
1192
+ }
1193
+ }
1194
+
1195
+ function SumAggregator(field) {
1196
+ this.field_ = field;
1197
+
1198
+ this.init = function () {
1199
+ this.sum_ = null;
1200
+ };
1201
+
1202
+ this.accumulate = function (item) {
1203
+ var val = item[this.field_];
1204
+ if (val != null && val !== "" && !isNaN(val)) {
1205
+ this.sum_ += parseFloat(val);
1206
+ }
1207
+ };
1208
+
1209
+ this.storeResult = function (groupTotals) {
1210
+ if (!groupTotals.sum) {
1211
+ groupTotals.sum = {};
1212
+ }
1213
+ groupTotals.sum[this.field_] = this.sum_;
1214
+ }
1215
+ }
1216
+
1217
+ // TODO: add more built-in aggregators
1218
+ // TODO: merge common aggregators in one to prevent needles iterating
1219
+
1220
+ })(jQuery);