dataclips 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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);