slickgrid-rails 0.2.0 → 0.3.0

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