slickgrid-rails 0.1.0

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