slickgrid-rails 0.2.0 → 0.3.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 (36) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +9 -0
  3. data/Rakefile +33 -0
  4. data/slickgrid-rails.gemspec +1 -2
  5. data/vendor/assets/javascripts/slick/controls/columnpicker.css +31 -0
  6. data/vendor/assets/javascripts/slick/controls/columnpicker.js +32 -0
  7. data/vendor/assets/javascripts/slick/controls/pager.css +41 -0
  8. data/vendor/assets/javascripts/slick/controls/pager.js +4 -8
  9. data/vendor/assets/javascripts/slick/core.js +35 -1
  10. data/vendor/assets/javascripts/slick/dataview.js +240 -85
  11. data/vendor/assets/javascripts/slick/editors.js +5 -5
  12. data/vendor/assets/javascripts/slick/formatters.js +5 -1
  13. data/vendor/assets/javascripts/slick/grid.css +157 -0
  14. data/vendor/assets/javascripts/slick/grid.js +770 -269
  15. data/vendor/assets/javascripts/slick/groupitemmetadataprovider.js +15 -10
  16. data/vendor/assets/javascripts/slick/plugins/autotooltips.js +49 -14
  17. data/vendor/assets/javascripts/slick/plugins/cellrangeselector.js +9 -8
  18. data/vendor/assets/javascripts/slick/plugins/cellselectionmodel.js +62 -2
  19. data/vendor/assets/javascripts/slick/plugins/checkboxselectcolumn.js +8 -9
  20. data/vendor/assets/javascripts/slick/plugins/headerbuttons.css +39 -0
  21. data/vendor/assets/javascripts/slick/plugins/headerbuttons.js +177 -0
  22. data/vendor/assets/javascripts/slick/plugins/headermenu.css +59 -0
  23. data/vendor/assets/javascripts/slick/plugins/headermenu.js +275 -0
  24. data/vendor/assets/javascripts/slick/plugins/rowmovemanager.js +17 -11
  25. data/vendor/assets/javascripts/slick/plugins/rowselectionmodel.js +1 -1
  26. data/vendor/assets/javascripts/slick/remotemodel.js +164 -0
  27. data/vendor/assets/stylesheets/slick/controls/columnpicker.css +31 -0
  28. data/vendor/assets/stylesheets/slick/controls/pager.css +41 -0
  29. data/vendor/assets/stylesheets/slick/grid.css +157 -0
  30. data/vendor/assets/stylesheets/slick/plugins/headerbuttons.css +39 -0
  31. data/vendor/assets/stylesheets/slick/plugins/headermenu.css +59 -0
  32. metadata +51 -17
  33. data/.rvmrc +0 -1
  34. data/Gemfile.lock +0 -85
  35. data/fetch.sh +0 -8
  36. data/lib/slickgrid/rails/version.rb +0 -6
data/.gitignore CHANGED
@@ -1 +1,3 @@
1
1
  pkg/
2
+ tmp/
3
+ Gemfile.lock
data/README.md CHANGED
@@ -125,6 +125,15 @@ gem):
125
125
  @grid.onCellChange.subscribe (e, args) =>
126
126
  @model.writeData(args)
127
127
 
128
+ ## Update SlickGrid
129
+
130
+ To upgrade SlickGrid version just run
131
+
132
+ rake slickgrid:update
133
+
134
+ It will clone the current SlickGrid master and will copy all javascript and stylesheet files into this repository.
135
+ Don't forget to update the gemspec version!
136
+
128
137
  ## Contributing
129
138
 
130
139
  1. Fork it
data/Rakefile CHANGED
@@ -1,2 +1,35 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+
4
+ namespace :slickgrid do
5
+ desc "Update SlickGrid library from current master"
6
+ task :update => "tmp/SlickGrid" do
7
+ cd "tmp/SlickGrid" do
8
+ js_files = Dir.glob("*.js") +
9
+ Dir.glob("plugins/*.js") +
10
+ Dir.glob("controls/*.js")
11
+
12
+ js_files.each do |file|
13
+ mkdir_p "../../vendor/assets/javascript/slick/#{File.dirname(file)}"
14
+ sh "cp #{file} ../../vendor/assets/javascripts/slick/#{file.gsub("slick.", "")}"
15
+ end
16
+
17
+ css_files = Dir.glob("*.css") +
18
+ Dir.glob("plugins/*.css") +
19
+ Dir.glob("controls/*.css")
20
+
21
+ css_files.each do |file|
22
+ mkdir_p "../../vendor/assets/stylesheets/slick/#{File.dirname(file)}"
23
+ sh "cp #{file} ../../vendor/assets/stylesheets/slick/#{file.gsub("slick.", "")}"
24
+ end
25
+ end
26
+ end
27
+
28
+ file "tmp/SlickGrid" do
29
+ mkdir_p "tmp"
30
+
31
+ cd "tmp" do
32
+ sh "git clone https://github.com/mleibman/SlickGrid.git"
33
+ end
34
+ end
35
+ end
@@ -1,9 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/slickgrid/rails/version', __FILE__)
3
2
 
4
3
  Gem::Specification.new do |gem|
5
4
  gem.name = "slickgrid-rails"
6
- gem.version = SlickGrid::Rails::VERSION
5
+ gem.version = "0.3.0"
7
6
  gem.authors = ["Benedikt Böhm"]
8
7
  gem.email = ["benedikt.boehm@madvertise.com"]
9
8
  gem.description = %q{SlickGrid Integration for Rails 3.x}
@@ -0,0 +1,31 @@
1
+ .slick-columnpicker {
2
+ border: 1px solid #718BB7;
3
+ background: #f0f0f0;
4
+ padding: 6px;
5
+ -moz-box-shadow: 2px 2px 2px silver;
6
+ -webkit-box-shadow: 2px 2px 2px silver;
7
+ box-shadow: 2px 2px 2px silver;
8
+ min-width: 100px;
9
+ cursor: default;
10
+ }
11
+
12
+ .slick-columnpicker li {
13
+ list-style: none;
14
+ margin: 0;
15
+ padding: 0;
16
+ background: none;
17
+ }
18
+
19
+ .slick-columnpicker input {
20
+ margin: 4px;
21
+ }
22
+
23
+ .slick-columnpicker li a {
24
+ display: block;
25
+ padding: 4px;
26
+ font-weight: bold;
27
+ }
28
+
29
+ .slick-columnpicker li a:hover {
30
+ background: white;
31
+ }
@@ -9,6 +9,7 @@
9
9
 
10
10
  function init() {
11
11
  grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu);
12
+ grid.onColumnsReordered.subscribe(updateColumnOrder);
12
13
  options = $.extend({}, defaults, options);
13
14
 
14
15
  $menu = $("<span class='slick-columnpicker' style='display:none;position:absolute;z-index:20;' />").appendTo(document.body);
@@ -23,6 +24,7 @@
23
24
  function handleHeaderContextMenu(e, args) {
24
25
  e.preventDefault();
25
26
  $menu.empty();
27
+ updateColumnOrder();
26
28
  columnCheckboxes = [];
27
29
 
28
30
  var $li, $input;
@@ -68,6 +70,28 @@
68
70
  .fadeIn(options.fadeSpeed);
69
71
  }
70
72
 
73
+ function updateColumnOrder() {
74
+ // Because columns can be reordered, we have to update the `columns`
75
+ // to reflect the new order, however we can't just take `grid.getColumns()`,
76
+ // as it does not include columns currently hidden by the picker.
77
+ // We create a new `columns` structure by leaving currently-hidden
78
+ // columns in their original ordinal position and interleaving the results
79
+ // of the current column sort.
80
+ var current = grid.getColumns().slice(0);
81
+ var ordered = new Array(columns.length);
82
+ for (var i = 0; i < ordered.length; i++) {
83
+ if ( grid.getColumnIndex(columns[i].id) === undefined ) {
84
+ // If the column doesn't return a value from getColumnIndex,
85
+ // it is hidden. Leave it in this position.
86
+ ordered[i] = columns[i];
87
+ } else {
88
+ // Otherwise, grab the next visible column.
89
+ ordered[i] = current.shift();
90
+ }
91
+ }
92
+ columns = ordered;
93
+ }
94
+
71
95
  function updateColumn(e) {
72
96
  if ($(e.target).data("option") == "autoresize") {
73
97
  if (e.target.checked) {
@@ -105,7 +129,15 @@
105
129
  }
106
130
  }
107
131
 
132
+ function getAllColumns() {
133
+ return columns;
134
+ }
135
+
108
136
  init();
137
+
138
+ return {
139
+ "getAllColumns": getAllColumns
140
+ };
109
141
  }
110
142
 
111
143
  // Slick.Controls.ColumnPicker
@@ -0,0 +1,41 @@
1
+ .slick-pager {
2
+ width: 100%;
3
+ height: 26px;
4
+ border: 1px solid gray;
5
+ border-top: 0;
6
+ background: url('../images/header-columns-bg.gif') repeat-x center bottom;
7
+ vertical-align: middle;
8
+ }
9
+
10
+ .slick-pager .slick-pager-status {
11
+ display: inline-block;
12
+ padding: 6px;
13
+ }
14
+
15
+ .slick-pager .ui-icon-container {
16
+ display: inline-block;
17
+ margin: 2px;
18
+ border-color: gray;
19
+ }
20
+
21
+ .slick-pager .slick-pager-nav {
22
+ display: inline-block;
23
+ float: left;
24
+ padding: 2px;
25
+ }
26
+
27
+ .slick-pager .slick-pager-settings {
28
+ display: block;
29
+ float: right;
30
+ padding: 2px;
31
+ }
32
+
33
+ .slick-pager .slick-pager-settings * {
34
+ vertical-align: middle;
35
+ }
36
+
37
+ .slick-pager .slick-pager-settings a {
38
+ padding: 2px;
39
+ text-decoration: underline;
40
+ cursor: pointer;
41
+ }
@@ -14,18 +14,14 @@
14
14
  function getNavState() {
15
15
  var cannotLeaveEditMode = !Slick.GlobalEditorLock.commitCurrentEdit();
16
16
  var pagingInfo = dataView.getPagingInfo();
17
- var lastPage = Math.ceil(pagingInfo.totalRows / pagingInfo.pageSize) - 1;
18
- if (lastPage < 0) {
19
- lastPage = 0;
20
- }
17
+ var lastPage = pagingInfo.totalPages - 1;
21
18
 
22
19
  return {
23
20
  canGotoFirst: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0,
24
21
  canGotoLast: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum != lastPage,
25
22
  canGotoPrev: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0,
26
23
  canGotoNext: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum < lastPage,
27
- pagingInfo: pagingInfo,
28
- lastPage: lastPage
24
+ pagingInfo: pagingInfo
29
25
  }
30
26
  }
31
27
 
@@ -45,7 +41,7 @@
45
41
  function gotoLast() {
46
42
  var state = getNavState();
47
43
  if (state.canGotoLast) {
48
- dataView.setPagingOptions({pageNum: state.lastPage});
44
+ dataView.setPagingOptions({pageNum: state.pagingInfo.totalPages - 1});
49
45
  }
50
46
  }
51
47
 
@@ -139,7 +135,7 @@
139
135
  if (pagingInfo.pageSize == 0) {
140
136
  $status.text("Showing all " + pagingInfo.totalRows + " rows");
141
137
  } else {
142
- $status.text("Showing page " + (pagingInfo.pageNum + 1) + " of " + (state.lastPage + 1));
138
+ $status.text("Showing page " + (pagingInfo.pageNum + 1) + " of " + pagingInfo.totalPages);
143
139
  }
144
140
  }
145
141
 
@@ -139,6 +139,8 @@
139
139
  handler: handler
140
140
  });
141
141
  event.subscribe(handler);
142
+
143
+ return this; // allow chaining
142
144
  };
143
145
 
144
146
  this.unsubscribe = function (event, handler) {
@@ -151,6 +153,8 @@
151
153
  return;
152
154
  }
153
155
  }
156
+
157
+ return this; // allow chaining
154
158
  };
155
159
 
156
160
  this.unsubscribeAll = function () {
@@ -159,6 +163,8 @@
159
163
  handlers[i].event.unsubscribe(handlers[i].handler);
160
164
  }
161
165
  handlers = [];
166
+
167
+ return this; // allow chaining
162
168
  }
163
169
  }
164
170
 
@@ -265,7 +271,13 @@
265
271
  */
266
272
  function Group() {
267
273
  this.__group = true;
268
- this.__updated = false;
274
+
275
+ /**
276
+ * Grouping level, starting with 0.
277
+ * @property level
278
+ * @type {Number}
279
+ */
280
+ this.level = 0;
269
281
 
270
282
  /***
271
283
  * Number of rows in the group.
@@ -301,6 +313,28 @@
301
313
  * @type {GroupTotals}
302
314
  */
303
315
  this.totals = null;
316
+
317
+ /**
318
+ * Rows that are part of the group.
319
+ * @property rows
320
+ * @type {Array}
321
+ */
322
+ this.rows = [];
323
+
324
+ /**
325
+ * Sub-groups that are part of the group.
326
+ * @property groups
327
+ * @type {Array}
328
+ */
329
+ this.groups = null;
330
+
331
+ /**
332
+ * A unique key used to identify the group. This key can be used in calls to DataView
333
+ * collapseGroup() or expandGroup().
334
+ * @property groupingKey
335
+ * @type {Object}
336
+ */
337
+ this.groupingKey = null;
304
338
  }
305
339
 
306
340
  Group.prototype = new NonDataItem();
@@ -50,15 +50,22 @@
50
50
  var filterCache = [];
51
51
 
52
52
  // grouping
53
- var groupingGetter;
54
- var groupingGetterIsAFn;
55
- var groupingFormatter;
56
- var groupingComparer;
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 = [];
57
66
  var groups = [];
58
- var collapsedGroups = {};
59
- var aggregators;
60
- var aggregateCollapsed = false;
61
- var compiledAccumulators;
67
+ var toggledGroupsByLevel = [];
68
+ var groupingDelimiter = ':|:';
62
69
 
63
70
  var pagesize = 0;
64
71
  var pagenum = 0;
@@ -129,11 +136,11 @@
129
136
  function setPagingOptions(args) {
130
137
  if (args.pageSize != undefined) {
131
138
  pagesize = args.pageSize;
132
- pagenum = Math.min(pagenum, Math.ceil(totalRows / pagesize));
139
+ pagenum = pagesize ? Math.min(pagenum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)) : 0;
133
140
  }
134
141
 
135
142
  if (args.pageNum != undefined) {
136
- pagenum = Math.min(args.pageNum, Math.ceil(totalRows / pagesize));
143
+ pagenum = Math.min(args.pageNum, Math.max(0, Math.ceil(totalRows / pagesize) - 1));
137
144
  }
138
145
 
139
146
  onPagingInfoChanged.notify(getPagingInfo(), null, self);
@@ -142,7 +149,8 @@
142
149
  }
143
150
 
144
151
  function getPagingInfo() {
145
- return {pageSize: pagesize, pageNum: pagenum, totalRows: totalRows};
152
+ var totalPages = pagesize ? Math.max(1, Math.ceil(totalRows / pagesize)) : 1;
153
+ return {pageSize: pagesize, pageNum: pagenum, totalRows: totalRows, totalPages: totalPages};
146
154
  }
147
155
 
148
156
  function sort(comparer, ascending) {
@@ -206,33 +214,65 @@
206
214
  refresh();
207
215
  }
208
216
 
209
- function groupBy(valueGetter, valueFormatter, sortComparer) {
217
+ function getGrouping() {
218
+ return groupingInfos;
219
+ }
220
+
221
+ function setGrouping(groupingInfo) {
210
222
  if (!options.groupItemMetadataProvider) {
211
223
  options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider();
212
224
  }
213
225
 
214
- groupingGetter = valueGetter;
215
- groupingGetterIsAFn = typeof groupingGetter === "function";
216
- groupingFormatter = valueFormatter;
217
- groupingComparer = sortComparer;
218
- collapsedGroups = {};
219
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
+
220
245
  refresh();
221
246
  }
222
247
 
223
- function setAggregators(groupAggregators, includeCollapsed) {
224
- aggregators = groupAggregators;
225
- aggregateCollapsed = (includeCollapsed !== undefined)
226
- ? includeCollapsed : aggregateCollapsed;
248
+ /**
249
+ * @deprecated Please use {@link setGrouping}.
250
+ */
251
+ function groupBy(valueGetter, valueFormatter, sortComparer) {
252
+ if (valueGetter == null) {
253
+ setGrouping([]);
254
+ return;
255
+ }
227
256
 
228
- // pre-compile accumulator loops
229
- compiledAccumulators = [];
230
- var idx = aggregators.length;
231
- while (idx--) {
232
- compiledAccumulators[idx] = compileAccumulatorLoop(aggregators[idx]);
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().");
233
270
  }
234
271
 
235
- refresh();
272
+ groupingInfos[0].aggregators = groupAggregators;
273
+ groupingInfos[0].aggregateCollapsed = includeCollapsed;
274
+
275
+ setGrouping(groupingInfos);
236
276
  }
237
277
 
238
278
  function getItemByIdx(i) {
@@ -332,7 +372,7 @@
332
372
  return null;
333
373
  }
334
374
 
335
- // overrides for group rows
375
+ // overrides for grouping rows
336
376
  if (item.__group) {
337
377
  return options.groupItemMetadataProvider.getGroupRowMetadata(item);
338
378
  }
@@ -345,37 +385,105 @@
345
385
  return null;
346
386
  }
347
387
 
348
- function collapseGroup(groupingValue) {
349
- collapsedGroups[groupingValue] = true;
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
+ }
350
398
  refresh();
351
399
  }
352
400
 
353
- function expandGroup(groupingValue) {
354
- delete collapsedGroups[groupingValue];
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;
355
417
  refresh();
356
418
  }
357
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
+
358
452
  function getGroups() {
359
453
  return groups;
360
454
  }
361
455
 
362
- function extractGroups(rows) {
456
+ function extractGroups(rows, parentGroup) {
363
457
  var group;
364
458
  var val;
365
459
  var groups = [];
366
460
  var groupsByVal = [];
367
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
+ }
368
477
 
369
478
  for (var i = 0, l = rows.length; i < l; i++) {
370
479
  r = rows[i];
371
- val = (groupingGetterIsAFn) ? groupingGetter(r) : r[groupingGetter];
372
- val = val || 0;
480
+ val = gi.getterIsAFn ? gi.getter(r) : r[gi.getter];
373
481
  group = groupsByVal[val];
374
482
  if (!group) {
375
483
  group = new Slick.Group();
376
- group.count = 0;
377
484
  group.value = val;
378
- group.rows = [];
485
+ group.level = level;
486
+ group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val;
379
487
  groups[groups.length] = group;
380
488
  groupsByVal[val] = group;
381
489
  }
@@ -383,57 +491,96 @@
383
491
  group.rows[group.count++] = r;
384
492
  }
385
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
+
386
503
  return groups;
387
504
  }
388
505
 
389
506
  // TODO: lazy totals calculation
390
507
  function calculateGroupTotals(group) {
391
- if (group.collapsed && !aggregateCollapsed) {
392
- return;
393
- }
394
-
395
508
  // TODO: try moving iterating over groups into compiled accumulator
509
+ var gi = groupingInfos[group.level];
510
+ var isLeafLevel = (group.level == groupingInfos.length);
396
511
  var totals = new Slick.GroupTotals();
397
- var agg, idx = aggregators.length;
512
+ var agg, idx = gi.aggregators.length;
398
513
  while (idx--) {
399
- agg = aggregators[idx];
514
+ agg = gi.aggregators[idx];
400
515
  agg.init();
401
- compiledAccumulators[idx].call(agg, group.rows);
516
+ gi.compiledAccumulators[idx].call(agg,
517
+ (!isLeafLevel && gi.aggregateChildGroups) ? group.groups : group.rows);
402
518
  agg.storeResult(totals);
403
519
  }
404
520
  totals.group = group;
405
521
  group.totals = totals;
406
522
  }
407
523
 
408
- function calculateTotals(groups) {
409
- var idx = groups.length;
524
+ function calculateTotals(groups, level) {
525
+ level = level || 0;
526
+ var gi = groupingInfos[level];
527
+ var idx = groups.length, g;
410
528
  while (idx--) {
411
- calculateGroupTotals(groups[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
+ }
412
544
  }
413
545
  }
414
546
 
415
- function finalizeGroups(groups) {
547
+ function finalizeGroups(groups, level) {
548
+ level = level || 0;
549
+ var gi = groupingInfos[level];
550
+ var groupCollapsed = gi.collapsed;
551
+ var toggledGroups = toggledGroupsByLevel[level];
416
552
  var idx = groups.length, g;
417
553
  while (idx--) {
418
554
  g = groups[idx];
419
- g.collapsed = (g.value in collapsedGroups);
420
- g.title = groupingFormatter ? groupingFormatter(g) : g.value;
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
+ }
421
565
  }
422
566
  }
423
567
 
424
- function flattenGroupedRows(groups) {
425
- var groupedRows = [], gl = 0, g;
568
+ function flattenGroupedRows(groups, level) {
569
+ level = level || 0;
570
+ var gi = groupingInfos[level];
571
+ var groupedRows = [], rows, gl = 0, g;
426
572
  for (var i = 0, l = groups.length; i < l; i++) {
427
573
  g = groups[i];
428
574
  groupedRows[gl++] = g;
429
575
 
430
576
  if (!g.collapsed) {
431
- for (var j = 0, jj = g.rows.length; j < jj; j++) {
432
- groupedRows[gl++] = g.rows[j];
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];
433
580
  }
434
581
  }
435
582
 
436
- if (g.totals && (!g.collapsed || aggregateCollapsed)) {
583
+ if (g.totals && gi.displayTotalsRow && (!g.collapsed || gi.aggregateCollapsed)) {
437
584
  groupedRows[gl++] = g.totals;
438
585
  }
439
586
  }
@@ -456,7 +603,7 @@
456
603
  "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" +
457
604
  accumulatorInfo.params[0] + " = _items[_i]; " +
458
605
  accumulatorInfo.body +
459
- "}"
606
+ "}"
460
607
  );
461
608
  fn.displayName = fn.name = "compiledAccumulatorLoop";
462
609
  return fn;
@@ -612,11 +759,10 @@
612
759
  item = newRows[i];
613
760
  r = rows[i];
614
761
 
615
- if ((groupingGetter && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) &&
762
+ if ((groupingInfos.length && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) &&
616
763
  item.__group !== r.__group ||
617
- item.__updated ||
618
764
  item.__group && !item.equals(r))
619
- || (aggregators && eitherIsNonData &&
765
+ || (eitherIsNonData &&
620
766
  // no good way to compare totals since they are arbitrary DTOs
621
767
  // deep object comparison is pretty expensive
622
768
  // always considering them 'dirty' seems easier for the time being
@@ -644,14 +790,11 @@
644
790
  var newRows = filteredItems.rows;
645
791
 
646
792
  groups = [];
647
- if (groupingGetter != null) {
793
+ if (groupingInfos.length) {
648
794
  groups = extractGroups(newRows);
649
795
  if (groups.length) {
796
+ calculateTotals(groups);
650
797
  finalizeGroups(groups);
651
- if (aggregators) {
652
- calculateTotals(groups);
653
- }
654
- groups.sort(groupingComparer);
655
798
  newRows = flattenGroupedRows(groups);
656
799
  }
657
800
  }
@@ -676,7 +819,7 @@
676
819
  // if the current page is no longer valid, go to last page and recalc
677
820
  // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
678
821
  if (pagesize && totalRows < pagenum * pagesize) {
679
- pagenum = Math.floor(totalRows / pagesize);
822
+ pagenum = Math.max(0, Math.ceil(totalRows / pagesize) - 1);
680
823
  diff = recalc(items, filter);
681
824
  }
682
825
 
@@ -700,12 +843,7 @@
700
843
  var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());;
701
844
  var inHandler;
702
845
 
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) {
846
+ function update() {
709
847
  if (selectedRowIds.length > 0) {
710
848
  inHandler = true;
711
849
  var selectedRows = self.mapIdsToRows(selectedRowIds);
@@ -715,7 +853,16 @@
715
853
  grid.setSelectedRows(selectedRows);
716
854
  inHandler = false;
717
855
  }
856
+ }
857
+
858
+ grid.onSelectedRowsChanged.subscribe(function(e, args) {
859
+ if (inHandler) { return; }
860
+ selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());
718
861
  });
862
+
863
+ this.onRowsChanged.subscribe(update);
864
+
865
+ this.onRowCountChanged.subscribe(update);
719
866
  }
720
867
 
721
868
  function syncGridCellCssStyles(grid, key) {
@@ -734,15 +881,7 @@
734
881
  }
735
882
  }
736
883
 
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) {
884
+ function update() {
746
885
  if (hashById) {
747
886
  inHandler = true;
748
887
  ensureRowsByIdCache();
@@ -756,7 +895,19 @@
756
895
  grid.setCellCssStyles(key, newHash);
757
896
  inHandler = false;
758
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
+ }
759
906
  });
907
+
908
+ this.onRowsChanged.subscribe(update);
909
+
910
+ this.onRowCountChanged.subscribe(update);
760
911
  }
761
912
 
762
913
  return {
@@ -771,8 +922,12 @@
771
922
  "sort": sort,
772
923
  "fastSort": fastSort,
773
924
  "reSort": reSort,
925
+ "setGrouping": setGrouping,
926
+ "getGrouping": getGrouping,
774
927
  "groupBy": groupBy,
775
928
  "setAggregators": setAggregators,
929
+ "collapseAllGroups": collapseAllGroups,
930
+ "expandAllGroups": expandAllGroups,
776
931
  "collapseGroup": collapseGroup,
777
932
  "expandGroup": expandGroup,
778
933
  "getGroups": getGroups,
@@ -816,7 +971,7 @@
816
971
  this.accumulate = function (item) {
817
972
  var val = item[this.field_];
818
973
  this.count_++;
819
- if (val != null && val != "" && val != NaN) {
974
+ if (val != null && val !== "" && val !== NaN) {
820
975
  this.nonNullCount_++;
821
976
  this.sum_ += parseFloat(val);
822
977
  }
@@ -841,7 +996,7 @@
841
996
 
842
997
  this.accumulate = function (item) {
843
998
  var val = item[this.field_];
844
- if (val != null && val != "" && val != NaN) {
999
+ if (val != null && val !== "" && val !== NaN) {
845
1000
  if (this.min_ == null || val < this.min_) {
846
1001
  this.min_ = val;
847
1002
  }
@@ -865,7 +1020,7 @@
865
1020
 
866
1021
  this.accumulate = function (item) {
867
1022
  var val = item[this.field_];
868
- if (val != null && val != "" && val != NaN) {
1023
+ if (val != null && val !== "" && val !== NaN) {
869
1024
  if (this.max_ == null || val > this.max_) {
870
1025
  this.max_ = val;
871
1026
  }
@@ -889,7 +1044,7 @@
889
1044
 
890
1045
  this.accumulate = function (item) {
891
1046
  var val = item[this.field_];
892
- if (val != null && val != "" && val != NaN) {
1047
+ if (val != null && val !== "" && val !== NaN) {
893
1048
  this.sum_ += parseFloat(val);
894
1049
  }
895
1050
  };
@@ -905,4 +1060,4 @@
905
1060
  // TODO: add more built-in aggregators
906
1061
  // TODO: merge common aggregators in one to prevent needles iterating
907
1062
 
908
- })(jQuery);
1063
+ })(jQuery);