dataclips 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.eot +0 -0
  6. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.svg +288 -0
  7. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf +0 -0
  8. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.woff +0 -0
  9. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2 +0 -0
  10. data/app/assets/images/dataclips/slickgrid/sort-asc.png +0 -0
  11. data/app/assets/images/dataclips/slickgrid/sort-desc.png +0 -0
  12. data/app/assets/javascripts/dataclips/application.js +26 -0
  13. data/app/assets/javascripts/dataclips/backbone.js +1608 -0
  14. data/app/assets/javascripts/dataclips/dataclips.js.coffee +168 -0
  15. data/app/assets/javascripts/dataclips/formatters.js.coffee +14 -0
  16. data/app/assets/javascripts/dataclips/namespace.js.coffee +1 -0
  17. data/app/assets/javascripts/dataclips/record.js.coffee +39 -0
  18. data/app/assets/javascripts/dataclips/slickgrid.js +5 -0
  19. data/app/assets/javascripts/dataclips/slickgrid/jquery.event.drag-2.2.js +402 -0
  20. data/app/assets/javascripts/dataclips/slickgrid/plugins/slick.rowselectionmodel.js +187 -0
  21. data/app/assets/javascripts/dataclips/slickgrid/slick.core.js +458 -0
  22. data/app/assets/javascripts/dataclips/slickgrid/slick.dataview.js +1063 -0
  23. data/app/assets/javascripts/dataclips/slickgrid/slick.grid.js +3309 -0
  24. data/app/assets/javascripts/dataclips/underscore.js +1416 -0
  25. data/app/assets/stylesheets/bootstrap/_alerts.scss +68 -0
  26. data/app/assets/stylesheets/bootstrap/_badges.scss +63 -0
  27. data/app/assets/stylesheets/bootstrap/_breadcrumbs.scss +26 -0
  28. data/app/assets/stylesheets/bootstrap/_button-groups.scss +243 -0
  29. data/app/assets/stylesheets/bootstrap/_buttons.scss +160 -0
  30. data/app/assets/stylesheets/bootstrap/_carousel.scss +267 -0
  31. data/app/assets/stylesheets/bootstrap/_close.scss +35 -0
  32. data/app/assets/stylesheets/bootstrap/_code.scss +69 -0
  33. data/app/assets/stylesheets/bootstrap/_component-animations.scss +38 -0
  34. data/app/assets/stylesheets/bootstrap/_dropdowns.scss +213 -0
  35. data/app/assets/stylesheets/bootstrap/_forms.scss +548 -0
  36. data/app/assets/stylesheets/bootstrap/_glyphicons.scss +234 -0
  37. data/app/assets/stylesheets/bootstrap/_grid.scss +84 -0
  38. data/app/assets/stylesheets/bootstrap/_input-groups.scss +166 -0
  39. data/app/assets/stylesheets/bootstrap/_jumbotron.scss +49 -0
  40. data/app/assets/stylesheets/bootstrap/_labels.scss +66 -0
  41. data/app/assets/stylesheets/bootstrap/_list-group.scss +124 -0
  42. data/app/assets/stylesheets/bootstrap/_media.scss +47 -0
  43. data/app/assets/stylesheets/bootstrap/_mixins.scss +39 -0
  44. data/app/assets/stylesheets/bootstrap/_modals.scss +148 -0
  45. data/app/assets/stylesheets/bootstrap/_navbar.scss +662 -0
  46. data/app/assets/stylesheets/bootstrap/_navs.scss +244 -0
  47. data/app/assets/stylesheets/bootstrap/_normalize.scss +427 -0
  48. data/app/assets/stylesheets/bootstrap/_pager.scss +54 -0
  49. data/app/assets/stylesheets/bootstrap/_pagination.scss +88 -0
  50. data/app/assets/stylesheets/bootstrap/_panels.scss +261 -0
  51. data/app/assets/stylesheets/bootstrap/_popovers.scss +135 -0
  52. data/app/assets/stylesheets/bootstrap/_print.scss +107 -0
  53. data/app/assets/stylesheets/bootstrap/_progress-bars.scss +87 -0
  54. data/app/assets/stylesheets/bootstrap/_responsive-embed.scss +35 -0
  55. data/app/assets/stylesheets/bootstrap/_responsive-utilities.scss +174 -0
  56. data/app/assets/stylesheets/bootstrap/_scaffolding.scss +150 -0
  57. data/app/assets/stylesheets/bootstrap/_tables.scss +234 -0
  58. data/app/assets/stylesheets/bootstrap/_theme.scss +272 -0
  59. data/app/assets/stylesheets/bootstrap/_thumbnails.scss +38 -0
  60. data/app/assets/stylesheets/bootstrap/_tooltip.scss +103 -0
  61. data/app/assets/stylesheets/bootstrap/_type.scss +298 -0
  62. data/app/assets/stylesheets/bootstrap/_utilities.scss +56 -0
  63. data/app/assets/stylesheets/bootstrap/_variables.scss +864 -0
  64. data/app/assets/stylesheets/bootstrap/_wells.scss +29 -0
  65. data/app/assets/stylesheets/bootstrap/mixins/_alerts.scss +14 -0
  66. data/app/assets/stylesheets/bootstrap/mixins/_background-variant.scss +11 -0
  67. data/app/assets/stylesheets/bootstrap/mixins/_border-radius.scss +18 -0
  68. data/app/assets/stylesheets/bootstrap/mixins/_buttons.scss +52 -0
  69. data/app/assets/stylesheets/bootstrap/mixins/_center-block.scss +7 -0
  70. data/app/assets/stylesheets/bootstrap/mixins/_clearfix.scss +22 -0
  71. data/app/assets/stylesheets/bootstrap/mixins/_forms.scss +88 -0
  72. data/app/assets/stylesheets/bootstrap/mixins/_gradients.scss +58 -0
  73. data/app/assets/stylesheets/bootstrap/mixins/_grid-framework.scss +81 -0
  74. data/app/assets/stylesheets/bootstrap/mixins/_grid.scss +122 -0
  75. data/app/assets/stylesheets/bootstrap/mixins/_hide-text.scss +21 -0
  76. data/app/assets/stylesheets/bootstrap/mixins/_image.scss +33 -0
  77. data/app/assets/stylesheets/bootstrap/mixins/_labels.scss +12 -0
  78. data/app/assets/stylesheets/bootstrap/mixins/_list-group.scss +31 -0
  79. data/app/assets/stylesheets/bootstrap/mixins/_nav-divider.scss +10 -0
  80. data/app/assets/stylesheets/bootstrap/mixins/_nav-vertical-align.scss +9 -0
  81. data/app/assets/stylesheets/bootstrap/mixins/_opacity.scss +8 -0
  82. data/app/assets/stylesheets/bootstrap/mixins/_pagination.scss +23 -0
  83. data/app/assets/stylesheets/bootstrap/mixins/_panels.scss +24 -0
  84. data/app/assets/stylesheets/bootstrap/mixins/_progress-bar.scss +10 -0
  85. data/app/assets/stylesheets/bootstrap/mixins/_reset-filter.scss +8 -0
  86. data/app/assets/stylesheets/bootstrap/mixins/_resize.scss +6 -0
  87. data/app/assets/stylesheets/bootstrap/mixins/_responsive-visibility.scss +21 -0
  88. data/app/assets/stylesheets/bootstrap/mixins/_size.scss +10 -0
  89. data/app/assets/stylesheets/bootstrap/mixins/_tab-focus.scss +9 -0
  90. data/app/assets/stylesheets/bootstrap/mixins/_table-row.scss +28 -0
  91. data/app/assets/stylesheets/bootstrap/mixins/_text-emphasis.scss +11 -0
  92. data/app/assets/stylesheets/bootstrap/mixins/_text-overflow.scss +8 -0
  93. data/app/assets/stylesheets/bootstrap/mixins/_vendor-prefixes.scss +222 -0
  94. data/app/assets/stylesheets/dataclips/_bootstrap.scss +53 -0
  95. data/app/assets/stylesheets/dataclips/application.css +18 -0
  96. data/app/assets/stylesheets/dataclips/slickgrid/slickgrid.sass +341 -0
  97. data/app/controllers/dataclips/application_controller.rb +31 -0
  98. data/app/controllers/dataclips/clips_controller.rb +46 -0
  99. data/app/controllers/dataclips/insights_controller.rb +45 -0
  100. data/app/helpers/dataclips/application_helper.rb +4 -0
  101. data/app/models/dataclips/clip.rb +79 -0
  102. data/app/models/dataclips/insight.rb +18 -0
  103. data/app/models/dataclips/sql_query.rb +36 -0
  104. data/app/views/dataclips/application/_boolean.html.erb +1 -0
  105. data/app/views/dataclips/application/_date.html.erb +22 -0
  106. data/app/views/dataclips/application/_datetime.html.erb +25 -0
  107. data/app/views/dataclips/application/_float.html.erb +7 -0
  108. data/app/views/dataclips/application/_integer.html.erb +7 -0
  109. data/app/views/dataclips/application/_text.html.erb +10 -0
  110. data/app/views/dataclips/application/_time.html.erb +23 -0
  111. data/app/views/dataclips/clips/edit.html.erb +14 -0
  112. data/app/views/dataclips/clips/show.html.erb +109 -0
  113. data/app/views/dataclips/insights/show.html.erb +99 -0
  114. data/app/views/layouts/dataclips/application.html.erb +14 -0
  115. data/config/initializers/assets.rb +1 -0
  116. data/config/routes.rb +9 -0
  117. data/db/migrate/20150101143530_create_dataclips_insights.rb +12 -0
  118. data/lib/dataclips.rb +47 -0
  119. data/lib/dataclips/engine.rb +29 -0
  120. data/lib/dataclips/version.rb +3 -0
  121. data/lib/tasks/dataclips_tasks.rake +4 -0
  122. data/test/dataclips_test.rb +7 -0
  123. data/test/dummy/README.rdoc +28 -0
  124. data/test/dummy/Rakefile +6 -0
  125. data/test/dummy/app/assets/javascripts/application.js +13 -0
  126. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  127. data/test/dummy/app/controllers/application_controller.rb +5 -0
  128. data/test/dummy/app/helpers/application_helper.rb +2 -0
  129. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  130. data/test/dummy/bin/bundle +3 -0
  131. data/test/dummy/bin/rails +4 -0
  132. data/test/dummy/bin/rake +4 -0
  133. data/test/dummy/config.ru +4 -0
  134. data/test/dummy/config/application.rb +23 -0
  135. data/test/dummy/config/boot.rb +5 -0
  136. data/test/dummy/config/database.yml +25 -0
  137. data/test/dummy/config/environment.rb +5 -0
  138. data/test/dummy/config/environments/development.rb +37 -0
  139. data/test/dummy/config/environments/production.rb +78 -0
  140. data/test/dummy/config/environments/test.rb +39 -0
  141. data/test/dummy/config/initializers/assets.rb +8 -0
  142. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  143. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  144. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  145. data/test/dummy/config/initializers/inflections.rb +16 -0
  146. data/test/dummy/config/initializers/mime_types.rb +4 -0
  147. data/test/dummy/config/initializers/session_store.rb +3 -0
  148. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  149. data/test/dummy/config/locales/en.yml +23 -0
  150. data/test/dummy/config/routes.rb +4 -0
  151. data/test/dummy/config/secrets.yml +22 -0
  152. data/test/dummy/public/404.html +67 -0
  153. data/test/dummy/public/422.html +67 -0
  154. data/test/dummy/public/500.html +66 -0
  155. data/test/dummy/public/favicon.ico +0 -0
  156. data/test/integration/navigation_test.rb +10 -0
  157. data/test/test_helper.rb +17 -0
  158. metadata +348 -0
@@ -0,0 +1,1063 @@
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) { return a.value - b.value; },
57
+ predefinedValues: [],
58
+ aggregators: [],
59
+ aggregateEmpty: false,
60
+ aggregateCollapsed: false,
61
+ aggregateChildGroups: false,
62
+ collapsed: false,
63
+ displayTotalsRow: true
64
+ };
65
+ var groupingInfos = [];
66
+ var groups = [];
67
+ var toggledGroupsByLevel = [];
68
+ var groupingDelimiter = ':|:';
69
+
70
+ var pagesize = 0;
71
+ var pagenum = 0;
72
+ var totalRows = 0;
73
+
74
+ // events
75
+ var onRowCountChanged = new Slick.Event();
76
+ var onRowsChanged = new Slick.Event();
77
+ var onPagingInfoChanged = new Slick.Event();
78
+
79
+ options = $.extend(true, {}, defaults, options);
80
+
81
+
82
+ function beginUpdate() {
83
+ suspend = true;
84
+ }
85
+
86
+ function endUpdate() {
87
+ suspend = false;
88
+ refresh();
89
+ }
90
+
91
+ function setRefreshHints(hints) {
92
+ refreshHints = hints;
93
+ }
94
+
95
+ function setFilterArgs(args) {
96
+ filterArgs = args;
97
+ }
98
+
99
+ function updateIdxById(startingIndex) {
100
+ startingIndex = startingIndex || 0;
101
+ var id;
102
+ for (var i = startingIndex, l = items.length; i < l; i++) {
103
+ id = items[i][idProperty];
104
+ if (id === undefined) {
105
+ throw "Each data element must implement a unique 'id' property";
106
+ }
107
+ idxById[id] = i;
108
+ }
109
+ }
110
+
111
+ function ensureIdUniqueness() {
112
+ var id;
113
+ for (var i = 0, l = items.length; i < l; i++) {
114
+ id = items[i][idProperty];
115
+ if (id === undefined || idxById[id] !== i) {
116
+ throw "Each data element must implement a unique 'id' property";
117
+ }
118
+ }
119
+ }
120
+
121
+ function getItems() {
122
+ return items;
123
+ }
124
+
125
+ function setItems(data, objectIdProperty) {
126
+ if (objectIdProperty !== undefined) {
127
+ idProperty = objectIdProperty;
128
+ }
129
+ items = filteredItems = data;
130
+ idxById = {};
131
+ updateIdxById();
132
+ ensureIdUniqueness();
133
+ refresh();
134
+ }
135
+
136
+ function setPagingOptions(args) {
137
+ if (args.pageSize != undefined) {
138
+ pagesize = args.pageSize;
139
+ pagenum = pagesize ? Math.min(pagenum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)) : 0;
140
+ }
141
+
142
+ if (args.pageNum != undefined) {
143
+ pagenum = Math.min(args.pageNum, Math.max(0, Math.ceil(totalRows / pagesize) - 1));
144
+ }
145
+
146
+ onPagingInfoChanged.notify(getPagingInfo(), null, self);
147
+
148
+ refresh();
149
+ }
150
+
151
+ function getPagingInfo() {
152
+ var totalPages = pagesize ? Math.max(1, Math.ceil(totalRows / pagesize)) : 1;
153
+ return {pageSize: pagesize, pageNum: pagenum, totalRows: totalRows, totalPages: totalPages};
154
+ }
155
+
156
+ function sort(comparer, ascending) {
157
+ sortAsc = ascending;
158
+ sortComparer = comparer;
159
+ fastSortField = null;
160
+ if (ascending === false) {
161
+ items.reverse();
162
+ }
163
+ items.sort(comparer);
164
+ if (ascending === false) {
165
+ items.reverse();
166
+ }
167
+ idxById = {};
168
+ updateIdxById();
169
+ refresh();
170
+ }
171
+
172
+ /***
173
+ * Provides a workaround for the extremely slow sorting in IE.
174
+ * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString
175
+ * to return the value of that field and then doing a native Array.sort().
176
+ */
177
+ function fastSort(field, ascending) {
178
+ sortAsc = ascending;
179
+ fastSortField = field;
180
+ sortComparer = null;
181
+ var oldToString = Object.prototype.toString;
182
+ Object.prototype.toString = (typeof field == "function") ? field : function () {
183
+ return this[field]
184
+ };
185
+ // an extra reversal for descending sort keeps the sort stable
186
+ // (assuming a stable native sort implementation, which isn't true in some cases)
187
+ if (ascending === false) {
188
+ items.reverse();
189
+ }
190
+ items.sort();
191
+ Object.prototype.toString = oldToString;
192
+ if (ascending === false) {
193
+ items.reverse();
194
+ }
195
+ idxById = {};
196
+ updateIdxById();
197
+ refresh();
198
+ }
199
+
200
+ function reSort() {
201
+ if (sortComparer) {
202
+ sort(sortComparer, sortAsc);
203
+ } else if (fastSortField) {
204
+ fastSort(fastSortField, sortAsc);
205
+ }
206
+ }
207
+
208
+ function setFilter(filterFn) {
209
+ filter = filterFn;
210
+ if (options.inlineFilters) {
211
+ compiledFilter = compileFilter();
212
+ compiledFilterWithCaching = compileFilterWithCaching();
213
+ }
214
+ refresh();
215
+ }
216
+
217
+ function getGrouping() {
218
+ return groupingInfos;
219
+ }
220
+
221
+ function setGrouping(groupingInfo) {
222
+ if (!options.groupItemMetadataProvider) {
223
+ options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider();
224
+ }
225
+
226
+ groups = [];
227
+ toggledGroupsByLevel = [];
228
+ groupingInfo = groupingInfo || [];
229
+ groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo];
230
+
231
+ for (var i = 0; i < groupingInfos.length; i++) {
232
+ var gi = groupingInfos[i] = $.extend(true, {}, groupingInfoDefaults, groupingInfos[i]);
233
+ gi.getterIsAFn = typeof gi.getter === "function";
234
+
235
+ // pre-compile accumulator loops
236
+ gi.compiledAccumulators = [];
237
+ var idx = gi.aggregators.length;
238
+ while (idx--) {
239
+ gi.compiledAccumulators[idx] = compileAccumulatorLoop(gi.aggregators[idx]);
240
+ }
241
+
242
+ toggledGroupsByLevel[i] = {};
243
+ }
244
+
245
+ refresh();
246
+ }
247
+
248
+ /**
249
+ * @deprecated Please use {@link setGrouping}.
250
+ */
251
+ function groupBy(valueGetter, valueFormatter, sortComparer) {
252
+ if (valueGetter == null) {
253
+ setGrouping([]);
254
+ return;
255
+ }
256
+
257
+ setGrouping({
258
+ getter: valueGetter,
259
+ formatter: valueFormatter,
260
+ comparer: sortComparer
261
+ });
262
+ }
263
+
264
+ /**
265
+ * @deprecated Please use {@link setGrouping}.
266
+ */
267
+ function setAggregators(groupAggregators, includeCollapsed) {
268
+ if (!groupingInfos.length) {
269
+ throw new Error("At least one grouping must be specified before calling setAggregators().");
270
+ }
271
+
272
+ groupingInfos[0].aggregators = groupAggregators;
273
+ groupingInfos[0].aggregateCollapsed = includeCollapsed;
274
+
275
+ setGrouping(groupingInfos);
276
+ }
277
+
278
+ function getItemByIdx(i) {
279
+ return items[i];
280
+ }
281
+
282
+ function getIdxById(id) {
283
+ return idxById[id];
284
+ }
285
+
286
+ function ensureRowsByIdCache() {
287
+ if (!rowsById) {
288
+ rowsById = {};
289
+ for (var i = 0, l = rows.length; i < l; i++) {
290
+ rowsById[rows[i][idProperty]] = i;
291
+ }
292
+ }
293
+ }
294
+
295
+ function getRowById(id) {
296
+ ensureRowsByIdCache();
297
+ return rowsById[id];
298
+ }
299
+
300
+ function getItemById(id) {
301
+ return items[idxById[id]];
302
+ }
303
+
304
+ function mapIdsToRows(idArray) {
305
+ var rows = [];
306
+ ensureRowsByIdCache();
307
+ for (var i = 0; i < idArray.length; i++) {
308
+ var row = rowsById[idArray[i]];
309
+ if (row != null) {
310
+ rows[rows.length] = row;
311
+ }
312
+ }
313
+ return rows;
314
+ }
315
+
316
+ function mapRowsToIds(rowArray) {
317
+ var ids = [];
318
+ for (var i = 0; i < rowArray.length; i++) {
319
+ if (rowArray[i] < rows.length) {
320
+ ids[ids.length] = rows[rowArray[i]][idProperty];
321
+ }
322
+ }
323
+ return ids;
324
+ }
325
+
326
+ function updateItem(id, item) {
327
+ if (idxById[id] === undefined || id !== item[idProperty]) {
328
+ throw "Invalid or non-matching id";
329
+ }
330
+ items[idxById[id]] = item;
331
+ if (!updated) {
332
+ updated = {};
333
+ }
334
+ updated[id] = true;
335
+ refresh();
336
+ }
337
+
338
+ function insertItem(insertBefore, item) {
339
+ items.splice(insertBefore, 0, item);
340
+ updateIdxById(insertBefore);
341
+ refresh();
342
+ }
343
+
344
+ function addItem(item) {
345
+ items.push(item);
346
+ updateIdxById(items.length - 1);
347
+ refresh();
348
+ }
349
+
350
+ function deleteItem(id) {
351
+ var idx = idxById[id];
352
+ if (idx === undefined) {
353
+ throw "Invalid id";
354
+ }
355
+ delete idxById[id];
356
+ items.splice(idx, 1);
357
+ updateIdxById(idx);
358
+ refresh();
359
+ }
360
+
361
+ function getLength() {
362
+ return rows.length;
363
+ }
364
+
365
+ function getItem(i) {
366
+ return rows[i];
367
+ }
368
+
369
+ function getItemMetadata(i) {
370
+ var item = rows[i];
371
+ if (item === undefined) {
372
+ return null;
373
+ }
374
+
375
+ // overrides for grouping rows
376
+ if (item.__group) {
377
+ return options.groupItemMetadataProvider.getGroupRowMetadata(item);
378
+ }
379
+
380
+ // overrides for totals rows
381
+ if (item.__groupTotals) {
382
+ return options.groupItemMetadataProvider.getTotalsRowMetadata(item);
383
+ }
384
+
385
+ return null;
386
+ }
387
+
388
+ function expandCollapseAllGroups(level, collapse) {
389
+ if (level == null) {
390
+ for (var i = 0; i < groupingInfos.length; i++) {
391
+ toggledGroupsByLevel[i] = {};
392
+ groupingInfos[i].collapsed = collapse;
393
+ }
394
+ } else {
395
+ toggledGroupsByLevel[level] = {};
396
+ groupingInfos[level].collapsed = collapse;
397
+ }
398
+ refresh();
399
+ }
400
+
401
+ /**
402
+ * @param level {Number} Optional level to collapse. If not specified, applies to all levels.
403
+ */
404
+ function collapseAllGroups(level) {
405
+ expandCollapseAllGroups(level, true);
406
+ }
407
+
408
+ /**
409
+ * @param level {Number} Optional level to expand. If not specified, applies to all levels.
410
+ */
411
+ function expandAllGroups(level) {
412
+ expandCollapseAllGroups(level, false);
413
+ }
414
+
415
+ function expandCollapseGroup(level, groupingKey, collapse) {
416
+ toggledGroupsByLevel[level][groupingKey] = groupingInfos[level].collapsed ^ collapse;
417
+ refresh();
418
+ }
419
+
420
+ /**
421
+ * @param varArgs Either a Slick.Group's "groupingKey" property, or a
422
+ * variable argument list of grouping values denoting a unique path to the row. For
423
+ * example, calling collapseGroup('high', '10%') will collapse the '10%' subgroup of
424
+ * the 'high' setGrouping.
425
+ */
426
+ function collapseGroup(varArgs) {
427
+ var args = Array.prototype.slice.call(arguments);
428
+ var arg0 = args[0];
429
+ if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) {
430
+ expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, true);
431
+ } else {
432
+ expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), true);
433
+ }
434
+ }
435
+
436
+ /**
437
+ * @param varArgs Either a Slick.Group's "groupingKey" property, or a
438
+ * variable argument list of grouping values denoting a unique path to the row. For
439
+ * example, calling expandGroup('high', '10%') will expand the '10%' subgroup of
440
+ * the 'high' setGrouping.
441
+ */
442
+ function expandGroup(varArgs) {
443
+ var args = Array.prototype.slice.call(arguments);
444
+ var arg0 = args[0];
445
+ if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) {
446
+ expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, false);
447
+ } else {
448
+ expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), false);
449
+ }
450
+ }
451
+
452
+ function getGroups() {
453
+ return groups;
454
+ }
455
+
456
+ function extractGroups(rows, parentGroup) {
457
+ var group;
458
+ var val;
459
+ var groups = [];
460
+ var groupsByVal = [];
461
+ var r;
462
+ var level = parentGroup ? parentGroup.level + 1 : 0;
463
+ var gi = groupingInfos[level];
464
+
465
+ for (var i = 0, l = gi.predefinedValues.length; i < l; i++) {
466
+ val = gi.predefinedValues[i];
467
+ group = groupsByVal[val];
468
+ if (!group) {
469
+ group = new Slick.Group();
470
+ group.value = val;
471
+ group.level = level;
472
+ group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val;
473
+ groups[groups.length] = group;
474
+ groupsByVal[val] = group;
475
+ }
476
+ }
477
+
478
+ for (var i = 0, l = rows.length; i < l; i++) {
479
+ r = rows[i];
480
+ val = gi.getterIsAFn ? gi.getter(r) : r[gi.getter];
481
+ group = groupsByVal[val];
482
+ if (!group) {
483
+ group = new Slick.Group();
484
+ group.value = val;
485
+ group.level = level;
486
+ group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val;
487
+ groups[groups.length] = group;
488
+ groupsByVal[val] = group;
489
+ }
490
+
491
+ group.rows[group.count++] = r;
492
+ }
493
+
494
+ if (level < groupingInfos.length - 1) {
495
+ for (var i = 0; i < groups.length; i++) {
496
+ group = groups[i];
497
+ group.groups = extractGroups(group.rows, group);
498
+ }
499
+ }
500
+
501
+ groups.sort(groupingInfos[level].comparer);
502
+
503
+ return groups;
504
+ }
505
+
506
+ // TODO: lazy totals calculation
507
+ function calculateGroupTotals(group) {
508
+ // TODO: try moving iterating over groups into compiled accumulator
509
+ var gi = groupingInfos[group.level];
510
+ var isLeafLevel = (group.level == groupingInfos.length);
511
+ var totals = new Slick.GroupTotals();
512
+ var agg, idx = gi.aggregators.length;
513
+ while (idx--) {
514
+ agg = gi.aggregators[idx];
515
+ agg.init();
516
+ gi.compiledAccumulators[idx].call(agg,
517
+ (!isLeafLevel && gi.aggregateChildGroups) ? group.groups : group.rows);
518
+ agg.storeResult(totals);
519
+ }
520
+ totals.group = group;
521
+ group.totals = totals;
522
+ }
523
+
524
+ function calculateTotals(groups, level) {
525
+ level = level || 0;
526
+ var gi = groupingInfos[level];
527
+ var idx = groups.length, g;
528
+ while (idx--) {
529
+ g = groups[idx];
530
+
531
+ if (g.collapsed && !gi.aggregateCollapsed) {
532
+ continue;
533
+ }
534
+
535
+ // Do a depth-first aggregation so that parent setGrouping aggregators can access subgroup totals.
536
+ if (g.groups) {
537
+ calculateTotals(g.groups, level + 1);
538
+ }
539
+
540
+ if (gi.aggregators.length && (
541
+ gi.aggregateEmpty || g.rows.length || (g.groups && g.groups.length))) {
542
+ calculateGroupTotals(g);
543
+ }
544
+ }
545
+ }
546
+
547
+ function finalizeGroups(groups, level) {
548
+ level = level || 0;
549
+ var gi = groupingInfos[level];
550
+ var groupCollapsed = gi.collapsed;
551
+ var toggledGroups = toggledGroupsByLevel[level];
552
+ var idx = groups.length, g;
553
+ while (idx--) {
554
+ g = groups[idx];
555
+ g.collapsed = groupCollapsed ^ toggledGroups[g.groupingKey];
556
+ g.title = gi.formatter ? gi.formatter(g) : g.value;
557
+
558
+ if (g.groups) {
559
+ finalizeGroups(g.groups, level + 1);
560
+ // Let the non-leaf setGrouping rows get garbage-collected.
561
+ // They may have been used by aggregates that go over all of the descendants,
562
+ // but at this point they are no longer needed.
563
+ g.rows = [];
564
+ }
565
+ }
566
+ }
567
+
568
+ function flattenGroupedRows(groups, level) {
569
+ level = level || 0;
570
+ var gi = groupingInfos[level];
571
+ var groupedRows = [], rows, gl = 0, g;
572
+ for (var i = 0, l = groups.length; i < l; i++) {
573
+ g = groups[i];
574
+ groupedRows[gl++] = g;
575
+
576
+ if (!g.collapsed) {
577
+ rows = g.groups ? flattenGroupedRows(g.groups, level + 1) : g.rows;
578
+ for (var j = 0, jj = rows.length; j < jj; j++) {
579
+ groupedRows[gl++] = rows[j];
580
+ }
581
+ }
582
+
583
+ if (g.totals && gi.displayTotalsRow && (!g.collapsed || gi.aggregateCollapsed)) {
584
+ groupedRows[gl++] = g.totals;
585
+ }
586
+ }
587
+ return groupedRows;
588
+ }
589
+
590
+ function getFunctionInfo(fn) {
591
+ var fnRegex = /^function[^(]*\(([^)]*)\)\s*{([\s\S]*)}$/;
592
+ var matches = fn.toString().match(fnRegex);
593
+ return {
594
+ params: matches[1].split(","),
595
+ body: matches[2]
596
+ };
597
+ }
598
+
599
+ function compileAccumulatorLoop(aggregator) {
600
+ var accumulatorInfo = getFunctionInfo(aggregator.accumulate);
601
+ var fn = new Function(
602
+ "_items",
603
+ "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" +
604
+ accumulatorInfo.params[0] + " = _items[_i]; " +
605
+ accumulatorInfo.body +
606
+ "}"
607
+ );
608
+ fn.displayName = fn.name = "compiledAccumulatorLoop";
609
+ return fn;
610
+ }
611
+
612
+ function compileFilter() {
613
+ var filterInfo = getFunctionInfo(filter);
614
+
615
+ var filterBody = filterInfo.body
616
+ .replace(/return false\s*([;}]|$)/gi, "{ continue _coreloop; }$1")
617
+ .replace(/return true\s*([;}]|$)/gi, "{ _retval[_idx++] = $item$; continue _coreloop; }$1")
618
+ .replace(/return ([^;}]+?)\s*([;}]|$)/gi,
619
+ "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }$2");
620
+
621
+ // This preserves the function template code after JS compression,
622
+ // so that replace() commands still work as expected.
623
+ var tpl = [
624
+ //"function(_items, _args) { ",
625
+ "var _retval = [], _idx = 0; ",
626
+ "var $item$, $args$ = _args; ",
627
+ "_coreloop: ",
628
+ "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
629
+ "$item$ = _items[_i]; ",
630
+ "$filter$; ",
631
+ "} ",
632
+ "return _retval; "
633
+ //"}"
634
+ ].join("");
635
+ tpl = tpl.replace(/\$filter\$/gi, filterBody);
636
+ tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
637
+ tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
638
+
639
+ var fn = new Function("_items,_args", tpl);
640
+ fn.displayName = fn.name = "compiledFilter";
641
+ return fn;
642
+ }
643
+
644
+ function compileFilterWithCaching() {
645
+ var filterInfo = getFunctionInfo(filter);
646
+
647
+ var filterBody = filterInfo.body
648
+ .replace(/return false\s*([;}]|$)/gi, "{ continue _coreloop; }$1")
649
+ .replace(/return true\s*([;}]|$)/gi, "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }$1")
650
+ .replace(/return ([^;}]+?)\s*([;}]|$)/gi,
651
+ "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }$2");
652
+
653
+ // This preserves the function template code after JS compression,
654
+ // so that replace() commands still work as expected.
655
+ var tpl = [
656
+ //"function(_items, _args, _cache) { ",
657
+ "var _retval = [], _idx = 0; ",
658
+ "var $item$, $args$ = _args; ",
659
+ "_coreloop: ",
660
+ "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
661
+ "$item$ = _items[_i]; ",
662
+ "if (_cache[_i]) { ",
663
+ "_retval[_idx++] = $item$; ",
664
+ "continue _coreloop; ",
665
+ "} ",
666
+ "$filter$; ",
667
+ "} ",
668
+ "return _retval; "
669
+ //"}"
670
+ ].join("");
671
+ tpl = tpl.replace(/\$filter\$/gi, filterBody);
672
+ tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
673
+ tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
674
+
675
+ var fn = new Function("_items,_args,_cache", tpl);
676
+ fn.displayName = fn.name = "compiledFilterWithCaching";
677
+ return fn;
678
+ }
679
+
680
+ function uncompiledFilter(items, args) {
681
+ var retval = [], idx = 0;
682
+
683
+ for (var i = 0, ii = items.length; i < ii; i++) {
684
+ if (filter(items[i], args)) {
685
+ retval[idx++] = items[i];
686
+ }
687
+ }
688
+
689
+ return retval;
690
+ }
691
+
692
+ function uncompiledFilterWithCaching(items, args, cache) {
693
+ var retval = [], idx = 0, item;
694
+
695
+ for (var i = 0, ii = items.length; i < ii; i++) {
696
+ item = items[i];
697
+ if (cache[i]) {
698
+ retval[idx++] = item;
699
+ } else if (filter(item, args)) {
700
+ retval[idx++] = item;
701
+ cache[i] = true;
702
+ }
703
+ }
704
+
705
+ return retval;
706
+ }
707
+
708
+ function getFilteredAndPagedItems(items) {
709
+ if (filter) {
710
+ var batchFilter = options.inlineFilters ? compiledFilter : uncompiledFilter;
711
+ var batchFilterWithCaching = options.inlineFilters ? compiledFilterWithCaching : uncompiledFilterWithCaching;
712
+
713
+ if (refreshHints.isFilterNarrowing) {
714
+ filteredItems = batchFilter(filteredItems, filterArgs);
715
+ } else if (refreshHints.isFilterExpanding) {
716
+ filteredItems = batchFilterWithCaching(items, filterArgs, filterCache);
717
+ } else if (!refreshHints.isFilterUnchanged) {
718
+ filteredItems = batchFilter(items, filterArgs);
719
+ }
720
+ } else {
721
+ // special case: if not filtering and not paging, the resulting
722
+ // rows collection needs to be a copy so that changes due to sort
723
+ // can be caught
724
+ filteredItems = pagesize ? items : items.concat();
725
+ }
726
+
727
+ // get the current page
728
+ var paged;
729
+ if (pagesize) {
730
+ if (filteredItems.length < pagenum * pagesize) {
731
+ pagenum = Math.floor(filteredItems.length / pagesize);
732
+ }
733
+ paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize);
734
+ } else {
735
+ paged = filteredItems;
736
+ }
737
+
738
+ return {totalRows: filteredItems.length, rows: paged};
739
+ }
740
+
741
+ function getRowDiffs(rows, newRows) {
742
+ var item, r, eitherIsNonData, diff = [];
743
+ var from = 0, to = newRows.length;
744
+
745
+ if (refreshHints && refreshHints.ignoreDiffsBefore) {
746
+ from = Math.max(0,
747
+ Math.min(newRows.length, refreshHints.ignoreDiffsBefore));
748
+ }
749
+
750
+ if (refreshHints && refreshHints.ignoreDiffsAfter) {
751
+ to = Math.min(newRows.length,
752
+ Math.max(0, refreshHints.ignoreDiffsAfter));
753
+ }
754
+
755
+ for (var i = from, rl = rows.length; i < to; i++) {
756
+ if (i >= rl) {
757
+ diff[diff.length] = i;
758
+ } else {
759
+ item = newRows[i];
760
+ r = rows[i];
761
+
762
+ if ((groupingInfos.length && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) &&
763
+ item.__group !== r.__group ||
764
+ item.__group && !item.equals(r))
765
+ || (eitherIsNonData &&
766
+ // no good way to compare totals since they are arbitrary DTOs
767
+ // deep object comparison is pretty expensive
768
+ // always considering them 'dirty' seems easier for the time being
769
+ (item.__groupTotals || r.__groupTotals))
770
+ || item[idProperty] != r[idProperty]
771
+ || (updated && updated[item[idProperty]])
772
+ ) {
773
+ diff[diff.length] = i;
774
+ }
775
+ }
776
+ }
777
+ return diff;
778
+ }
779
+
780
+ function recalc(_items) {
781
+ rowsById = null;
782
+
783
+ if (refreshHints.isFilterNarrowing != prevRefreshHints.isFilterNarrowing ||
784
+ refreshHints.isFilterExpanding != prevRefreshHints.isFilterExpanding) {
785
+ filterCache = [];
786
+ }
787
+
788
+ var filteredItems = getFilteredAndPagedItems(_items);
789
+ totalRows = filteredItems.totalRows;
790
+ var newRows = filteredItems.rows;
791
+
792
+ groups = [];
793
+ if (groupingInfos.length) {
794
+ groups = extractGroups(newRows);
795
+ if (groups.length) {
796
+ calculateTotals(groups);
797
+ finalizeGroups(groups);
798
+ newRows = flattenGroupedRows(groups);
799
+ }
800
+ }
801
+
802
+ var diff = getRowDiffs(rows, newRows);
803
+
804
+ rows = newRows;
805
+
806
+ return diff;
807
+ }
808
+
809
+ function refresh() {
810
+ if (suspend) {
811
+ return;
812
+ }
813
+
814
+ var countBefore = rows.length;
815
+ var totalRowsBefore = totalRows;
816
+
817
+ var diff = recalc(items, filter); // pass as direct refs to avoid closure perf hit
818
+
819
+ // if the current page is no longer valid, go to last page and recalc
820
+ // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
821
+ if (pagesize && totalRows < pagenum * pagesize) {
822
+ pagenum = Math.max(0, Math.ceil(totalRows / pagesize) - 1);
823
+ diff = recalc(items, filter);
824
+ }
825
+
826
+ updated = null;
827
+ prevRefreshHints = refreshHints;
828
+ refreshHints = {};
829
+
830
+ if (totalRowsBefore != totalRows) {
831
+ onPagingInfoChanged.notify(getPagingInfo(), null, self);
832
+ }
833
+ if (countBefore != rows.length) {
834
+ onRowCountChanged.notify({previous: countBefore, current: rows.length}, null, self);
835
+ }
836
+ if (diff.length > 0) {
837
+ onRowsChanged.notify({rows: diff}, null, self);
838
+ }
839
+ }
840
+
841
+ function syncGridSelection(grid, preserveHidden) {
842
+ var self = this;
843
+ var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());;
844
+ var inHandler;
845
+
846
+ function update() {
847
+ if (selectedRowIds.length > 0) {
848
+ inHandler = true;
849
+ var selectedRows = self.mapIdsToRows(selectedRowIds);
850
+ if (!preserveHidden) {
851
+ selectedRowIds = self.mapRowsToIds(selectedRows);
852
+ }
853
+ grid.setSelectedRows(selectedRows);
854
+ inHandler = false;
855
+ }
856
+ }
857
+
858
+ grid.onSelectedRowsChanged.subscribe(function(e, args) {
859
+ if (inHandler) { return; }
860
+ selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());
861
+ });
862
+
863
+ this.onRowsChanged.subscribe(update);
864
+
865
+ this.onRowCountChanged.subscribe(update);
866
+ }
867
+
868
+ function syncGridCellCssStyles(grid, key) {
869
+ var hashById;
870
+ var inHandler;
871
+
872
+ // since this method can be called after the cell styles have been set,
873
+ // get the existing ones right away
874
+ storeCellCssStyles(grid.getCellCssStyles(key));
875
+
876
+ function storeCellCssStyles(hash) {
877
+ hashById = {};
878
+ for (var row in hash) {
879
+ var id = rows[row][idProperty];
880
+ hashById[id] = hash[row];
881
+ }
882
+ }
883
+
884
+ function update() {
885
+ if (hashById) {
886
+ inHandler = true;
887
+ ensureRowsByIdCache();
888
+ var newHash = {};
889
+ for (var id in hashById) {
890
+ var row = rowsById[id];
891
+ if (row != undefined) {
892
+ newHash[row] = hashById[id];
893
+ }
894
+ }
895
+ grid.setCellCssStyles(key, newHash);
896
+ inHandler = false;
897
+ }
898
+ }
899
+
900
+ grid.onCellCssStylesChanged.subscribe(function(e, args) {
901
+ if (inHandler) { return; }
902
+ if (key != args.key) { return; }
903
+ if (args.hash) {
904
+ storeCellCssStyles(args.hash);
905
+ }
906
+ });
907
+
908
+ this.onRowsChanged.subscribe(update);
909
+
910
+ this.onRowCountChanged.subscribe(update);
911
+ }
912
+
913
+ $.extend(this, {
914
+ // methods
915
+ "beginUpdate": beginUpdate,
916
+ "endUpdate": endUpdate,
917
+ "setPagingOptions": setPagingOptions,
918
+ "getPagingInfo": getPagingInfo,
919
+ "getItems": getItems,
920
+ "setItems": setItems,
921
+ "setFilter": setFilter,
922
+ "sort": sort,
923
+ "fastSort": fastSort,
924
+ "reSort": reSort,
925
+ "setGrouping": setGrouping,
926
+ "getGrouping": getGrouping,
927
+ "groupBy": groupBy,
928
+ "setAggregators": setAggregators,
929
+ "collapseAllGroups": collapseAllGroups,
930
+ "expandAllGroups": expandAllGroups,
931
+ "collapseGroup": collapseGroup,
932
+ "expandGroup": expandGroup,
933
+ "getGroups": getGroups,
934
+ "getIdxById": getIdxById,
935
+ "getRowById": getRowById,
936
+ "getItemById": getItemById,
937
+ "getItemByIdx": getItemByIdx,
938
+ "mapRowsToIds": mapRowsToIds,
939
+ "mapIdsToRows": mapIdsToRows,
940
+ "setRefreshHints": setRefreshHints,
941
+ "setFilterArgs": setFilterArgs,
942
+ "refresh": refresh,
943
+ "updateItem": updateItem,
944
+ "insertItem": insertItem,
945
+ "addItem": addItem,
946
+ "deleteItem": deleteItem,
947
+ "syncGridSelection": syncGridSelection,
948
+ "syncGridCellCssStyles": syncGridCellCssStyles,
949
+
950
+ // data provider methods
951
+ "getLength": getLength,
952
+ "getItem": getItem,
953
+ "getItemMetadata": getItemMetadata,
954
+
955
+ // events
956
+ "onRowCountChanged": onRowCountChanged,
957
+ "onRowsChanged": onRowsChanged,
958
+ "onPagingInfoChanged": onPagingInfoChanged
959
+ });
960
+ }
961
+
962
+ function AvgAggregator(field) {
963
+ this.field_ = field;
964
+
965
+ this.init = function () {
966
+ this.count_ = 0;
967
+ this.nonNullCount_ = 0;
968
+ this.sum_ = 0;
969
+ };
970
+
971
+ this.accumulate = function (item) {
972
+ var val = item[this.field_];
973
+ this.count_++;
974
+ if (val != null && val !== "" && val !== NaN) {
975
+ this.nonNullCount_++;
976
+ this.sum_ += parseFloat(val);
977
+ }
978
+ };
979
+
980
+ this.storeResult = function (groupTotals) {
981
+ if (!groupTotals.avg) {
982
+ groupTotals.avg = {};
983
+ }
984
+ if (this.nonNullCount_ != 0) {
985
+ groupTotals.avg[this.field_] = this.sum_ / this.nonNullCount_;
986
+ }
987
+ };
988
+ }
989
+
990
+ function MinAggregator(field) {
991
+ this.field_ = field;
992
+
993
+ this.init = function () {
994
+ this.min_ = null;
995
+ };
996
+
997
+ this.accumulate = function (item) {
998
+ var val = item[this.field_];
999
+ if (val != null && val !== "" && val !== NaN) {
1000
+ if (this.min_ == null || val < this.min_) {
1001
+ this.min_ = val;
1002
+ }
1003
+ }
1004
+ };
1005
+
1006
+ this.storeResult = function (groupTotals) {
1007
+ if (!groupTotals.min) {
1008
+ groupTotals.min = {};
1009
+ }
1010
+ groupTotals.min[this.field_] = this.min_;
1011
+ }
1012
+ }
1013
+
1014
+ function MaxAggregator(field) {
1015
+ this.field_ = field;
1016
+
1017
+ this.init = function () {
1018
+ this.max_ = null;
1019
+ };
1020
+
1021
+ this.accumulate = function (item) {
1022
+ var val = item[this.field_];
1023
+ if (val != null && val !== "" && val !== NaN) {
1024
+ if (this.max_ == null || val > this.max_) {
1025
+ this.max_ = val;
1026
+ }
1027
+ }
1028
+ };
1029
+
1030
+ this.storeResult = function (groupTotals) {
1031
+ if (!groupTotals.max) {
1032
+ groupTotals.max = {};
1033
+ }
1034
+ groupTotals.max[this.field_] = this.max_;
1035
+ }
1036
+ }
1037
+
1038
+ function SumAggregator(field) {
1039
+ this.field_ = field;
1040
+
1041
+ this.init = function () {
1042
+ this.sum_ = null;
1043
+ };
1044
+
1045
+ this.accumulate = function (item) {
1046
+ var val = item[this.field_];
1047
+ if (val != null && val !== "" && val !== NaN) {
1048
+ this.sum_ += parseFloat(val);
1049
+ }
1050
+ };
1051
+
1052
+ this.storeResult = function (groupTotals) {
1053
+ if (!groupTotals.sum) {
1054
+ groupTotals.sum = {};
1055
+ }
1056
+ groupTotals.sum[this.field_] = this.sum_;
1057
+ }
1058
+ }
1059
+
1060
+ // TODO: add more built-in aggregators
1061
+ // TODO: merge common aggregators in one to prevent needles iterating
1062
+
1063
+ })(jQuery);