@refinitiv-ui/efx-grid 6.0.35 → 6.0.37

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 (40) hide show
  1. package/lib/core/dist/core.css +1 -1
  2. package/lib/core/dist/core.js +150 -6
  3. package/lib/core/dist/core.min.js +1 -1
  4. package/lib/core/es6/data/DataCache.js +20 -1
  5. package/lib/core/es6/grid/Core.js +25 -2
  6. package/lib/core/es6/grid/ILayoutGrid.js +4 -0
  7. package/lib/core/es6/grid/LayoutGrid.d.ts +4 -0
  8. package/lib/core/es6/grid/LayoutGrid.js +95 -3
  9. package/lib/core/es6/grid/VirtualizedLayoutGrid.js +6 -0
  10. package/lib/core/es6/tr-grid-theme.js +1 -1
  11. package/lib/grid/index.js +1 -1
  12. package/lib/grid/themes/base.less +1 -0
  13. package/lib/grid/themes/halo/dark/efx-grid.js +1 -1
  14. package/lib/grid/themes/halo/dark/es5/all-elements.js +1 -1
  15. package/lib/grid/themes/halo/efx-grid.less +2 -0
  16. package/lib/grid/themes/halo/light/efx-grid.js +1 -1
  17. package/lib/grid/themes/halo/light/es5/all-elements.js +1 -1
  18. package/lib/grid/themes/solar/charcoal/efx-grid.js +1 -1
  19. package/lib/grid/themes/solar/charcoal/es5/all-elements.js +1 -1
  20. package/lib/grid/themes/solar/pearl/efx-grid.js +1 -1
  21. package/lib/grid/themes/solar/pearl/es5/all-elements.js +1 -1
  22. package/lib/rt-grid/dist/rt-grid.js +302 -107
  23. package/lib/rt-grid/dist/rt-grid.min.js +1 -1
  24. package/lib/rt-grid/es6/FieldDefinition.js +0 -41
  25. package/lib/rt-grid/es6/Grid.js +4 -5
  26. package/lib/rt-grid/es6/RowDefinition.d.ts +2 -2
  27. package/lib/rt-grid/es6/RowDefinition.js +102 -52
  28. package/lib/tr-grid-column-stack/es6/ColumnStack.d.ts +1 -0
  29. package/lib/tr-grid-column-stack/es6/ColumnStack.js +579 -607
  30. package/lib/tr-grid-range-bar/es6/RangeBar.d.ts +4 -1
  31. package/lib/tr-grid-range-bar/es6/RangeBar.js +99 -39
  32. package/lib/tr-grid-util/es6/GroupDefinitions.d.ts +7 -1
  33. package/lib/tr-grid-util/es6/GroupDefinitions.js +39 -3
  34. package/lib/tr-grid-util/es6/jet/MockQuotes2.js +7 -0
  35. package/lib/types/es6/ColumnStack.d.ts +1 -0
  36. package/lib/types/es6/Core/grid/Core.d.ts +12 -0
  37. package/lib/types/es6/Core/grid/LayoutGrid.d.ts +4 -0
  38. package/lib/types/es6/RealtimeGrid/RowDefinition.d.ts +2 -2
  39. package/lib/versions.json +3 -3
  40. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { GridPlugin } from "../../tr-grid-util/es6/GridPlugin.js";
3
3
  import { Conflator } from "../../tr-grid-util/es6/Conflator.js";
4
4
  import { Icon } from "../../tr-grid-util/es6/Icon.js";
5
5
  import { ElfUtil } from "../../tr-grid-util/es6/ElfUtil.js";
6
+ import { GroupDefinitions } from "../../tr-grid-util/es6/GroupDefinitions.js";
6
7
  import { Dom } from "../../tr-grid-util/es6/Dom.js";
7
8
  import { injectCss, prettifyCss } from "../../tr-grid-util/es6/Util.js";
8
9
  import { preventDefault } from "../../tr-grid-util/es6/EventDispatcher.js";
@@ -43,7 +44,7 @@ import { preventDefault } from "../../tr-grid-util/es6/EventDispatcher.js";
43
44
  * @description Available options describing grouped column displaying
44
45
  * @property {boolean=} spreading=false If specified true, this group will be running in collapsing mode
45
46
  * @property {boolean=} collapsed=true If disabled, this group will be expanded at the first time
46
- * @property {string=} activeColumn="" Column index or field for set as active column
47
+ * @property {string=} activeColumn="" Column index, column id, or field for set as active column
47
48
  */
48
49
 
49
50
  /** @event ColumnStackPlugin#clicked
@@ -58,6 +59,52 @@ import { preventDefault } from "../../tr-grid-util/es6/EventDispatcher.js";
58
59
  * @property {Event} event Native event argument from click event
59
60
  */
60
61
 
62
+ /** @private
63
+ * @function
64
+ * @param {Array} refA
65
+ * @param {Array} refB
66
+ * @return {number}
67
+ */
68
+ var compareRef = function(refA, refB) {
69
+ var valA = refA[0];
70
+ var valB = refB[0];
71
+ var invalidA = (valA == null || valA < 0);
72
+ var invalidB = (valB == null || valB < 0);
73
+
74
+ if(invalidA) {
75
+ return invalidB ? 0 : 1;
76
+ } else if(invalidB) {
77
+ return -1;
78
+ }
79
+ if(valA < valB) {
80
+ return -1;
81
+ }
82
+ if(valB < valA) {
83
+ return 1;
84
+ }
85
+
86
+ return 0;
87
+ };
88
+ /** @private
89
+ * @description Resolve active column from the given stack object, if activeColumn is invalid
90
+ * @param {Object} stackOpt
91
+ * @return {boolean} Return true if there is any change
92
+ */
93
+ var _resolveActiveColumn = function(stackOpt) {
94
+ if(stackOpt) {
95
+ var children = stackOpt.children;
96
+ if(children && children.length) {
97
+ var activeColumn = stackOpt.activeColumn;
98
+ // TODO: If columns are stored as column id and activeColumn is a field, active index could not be found
99
+ if(!activeColumn || children.indexOf(activeColumn) < 0) {
100
+ stackOpt.activeColumn = stackOpt.spreading ? children[children.length - 1] : children[0];
101
+ return true;
102
+ }
103
+ }
104
+ }
105
+ return false;
106
+ };
107
+
61
108
  /** @constructor
62
109
  * @extends {GridPlugin}
63
110
  */
@@ -66,22 +113,27 @@ var ColumnStackPlugin = function () {
66
113
  this._onColumnRemoved = this._onColumnRemoved.bind(this);
67
114
  this._onColumnMoved = this._onColumnMoved.bind(this);
68
115
  this._onColumnAdded = this._onColumnAdded.bind(this);
116
+ this._onBeforeBatchOperation = this._onBeforeBatchOperation.bind(this);
117
+ this._onAfterBatchOperation = this._onAfterBatchOperation.bind(this);
118
+
69
119
  this._onStackButtonClicked = this._onStackButtonClicked.bind(this);
70
120
  this._updateUI = this._updateUI.bind(this);
71
121
  this._requestStackingByFields = this._requestStackingByFields.bind(this);
122
+ this._toIdOrField = this._toIdOrField.bind(this);
123
+
72
124
  this._hosts = [];
73
- this._stacks = {};
125
+ this._groupDefs = new GroupDefinitions();
74
126
 
75
- this._conflator = new Conflator(100, this._updateUI);
127
+ this._conflator = new Conflator(50, this._updateUI);
76
128
  this._stackConflator = new Conflator(100, this._requestStackingByFields);
77
129
  };
78
130
 
79
131
  Ext.inherits(ColumnStackPlugin, GridPlugin);
80
132
 
81
- /** @type {!Object.<string, Object>}
133
+ /** @type {!GroupDefinitions}
82
134
  * @private
83
135
  */
84
- ColumnStackPlugin.prototype._stacks;
136
+ ColumnStackPlugin.prototype._groupDefs;
85
137
  /** @type {Object.<string, string>}
86
138
  * @private
87
139
  */
@@ -102,7 +154,11 @@ ColumnStackPlugin.prototype._autoStacking = false;
102
154
  /** @type {boolean}
103
155
  * @private
104
156
  */
105
- ColumnStackPlugin.prototype._stacking = false;
157
+ ColumnStackPlugin.prototype._inReordering = false;
158
+ /** @type {boolean}
159
+ * @private
160
+ */
161
+ ColumnStackPlugin.prototype._inResetting = false;
106
162
 
107
163
 
108
164
  /** @type {number}
@@ -114,7 +170,7 @@ ColumnStackPlugin._runningId = 0;
114
170
  */
115
171
  ColumnStackPlugin.prototype._generateStackId = function() {
116
172
  var sid = "Stack" + ColumnStackPlugin._runningId++;
117
- while(this._stacks[sid]) {
173
+ while(this._groupDefs.getGroup(sid)) {
118
174
  sid = "Stack" + ColumnStackPlugin._runningId++;
119
175
  }
120
176
  return sid;
@@ -156,6 +212,8 @@ ColumnStackPlugin.prototype.initialize = function (host, options) {
156
212
  host.listen("columnRemoved", this._onColumnRemoved);
157
213
  host.listen("columnMoved", this._onColumnMoved);
158
214
  host.listen("columnAdded", this._onColumnAdded);
215
+ host.listen("beforeBatchOperation", this._onBeforeBatchOperation);
216
+ host.listen("afterBatchOperation", this._onAfterBatchOperation);
159
217
  }
160
218
  host.listen("preSectionRender", this._onPreSectionRender);
161
219
 
@@ -173,12 +231,14 @@ ColumnStackPlugin.prototype.unload = function (host) {
173
231
  host.unlisten("columnRemoved", this._onColumnRemoved);
174
232
  host.unlisten("columnMoved", this._onColumnMoved);
175
233
  host.unlisten("columnAdded", this._onColumnAdded);
234
+ host.unlisten("beforeBatchOperation", this._onBeforeBatchOperation);
235
+ host.unlisten("afterBatchOperation", this._onAfterBatchOperation);
176
236
  host.unlisten("preSectionRender", this._onPreSectionRender);
177
237
 
178
238
  if(this._hosts.length <= 0) {
179
239
  this._conflator.reset();
180
240
  this._stackConflator.reset();
181
- this._stacks = {};
241
+ this._groupDefs.removeAllGroups();
182
242
  }
183
243
  this._dispose();
184
244
  };
@@ -233,9 +293,6 @@ ColumnStackPlugin.prototype.config = function (options) {
233
293
 
234
294
  var i;
235
295
  var colCount = columns.length;
236
- for(i = 0; i < colCount; ++i) {
237
- this._setUniqueRef(i, {}); // used for further column index getting
238
- }
239
296
 
240
297
  var columnStack = options.columnStack;
241
298
  var stackId = "";
@@ -257,9 +314,10 @@ ColumnStackPlugin.prototype.config = function (options) {
257
314
  }
258
315
  this._idToFields[stackId] = columnStack.fields;
259
316
  stacks[stackId] = {
260
- colRefs: columnStack.fields,
317
+ id: stackId,
261
318
  spreading: columnStack.spreading === true,
262
- collapsed: columnStack.collapsed !== false
319
+ collapsed: columnStack.collapsed !== false,
320
+ children: columnStack.fields
263
321
  };
264
322
  } else if (columnStack.stacks && columnStack.stacks.length) {
265
323
  hasStack = true;
@@ -289,6 +347,7 @@ ColumnStackPlugin.prototype.config = function (options) {
289
347
  stackId = "";
290
348
  var spreading = false;
291
349
  var collapsed = true;
350
+ var activeColumn = "";
292
351
  if(typeof stackOpt === "string") {
293
352
  stackId = stackOpt;
294
353
  } else if(typeof stackOpt === "object") {
@@ -296,34 +355,31 @@ ColumnStackPlugin.prototype.config = function (options) {
296
355
  stackId = stackConfig["id"] || "";
297
356
  spreading = stackConfig["spreading"] === true; // WARNING: Only the first column in the stack has an effect
298
357
  collapsed = stackConfig["collapsed"] !== false;
358
+ activeColumn = stackConfig["activeColumn"] || "";
299
359
  }
300
360
  if(stackId) {
301
- if(!stacks[stackId]) {
302
- stacks[stackId] = {
303
- colRefs: [],
361
+ stackOpt = stacks[stackId];
362
+ if(!stackOpt ) {
363
+ stackOpt = stacks[stackId] = {
364
+ id: stackId,
304
365
  spreading: spreading, // Default is false (stacking mode)
305
366
  collapsed: collapsed // Default is true (columns are hidden)
306
367
  };
368
+ if(activeColumn) {
369
+ stackOpt.activeColumn = activeColumn;
370
+ }
307
371
  hasStack = true;
308
372
  }
309
- stacks[stackId].colRefs.push(i);
373
+ if(!stackOpt.children) {
374
+ stackOpt.children = [];
375
+ }
376
+ stackOpt.children.push(i);
310
377
  }
311
378
  }
312
379
  }
313
380
 
314
381
  if(this._initializedGrid) {
315
- if(hasStack){
316
- this.removeAllStacks(false); // No UI update
317
- for(stackId in stacks) {
318
- var config = stacks[stackId];
319
- if(!config.colRefs){
320
- this._transformStackConfig(config);
321
- }
322
- this.stackColumns(config.colRefs, stackId, config);
323
- }
324
- } else {
325
- this.removeAllStacks();
326
- }
382
+ this._applyUserConfigs(hasStack ? stacks : null);
327
383
  } else {
328
384
  this._pendingStacks = this._pendingStacks || stacks;
329
385
  }
@@ -340,15 +396,15 @@ ColumnStackPlugin.prototype.getConfigObject = function (gridOptions) {
340
396
 
341
397
  var columnOptions = obj["columns"];
342
398
 
343
- var stacks = this._stacks;
399
+ var stacks = this._groupDefs.getGroupMap();
344
400
  var stackOptions = [];
345
401
 
346
402
  for (var stackKey in stacks) {
347
403
  var stackOption = stacks[stackKey];
348
- var activeColIndex = this._getColumnIndex(stackOption.activeColumn);
404
+ var activeColIndex = this.getColumnIndex(stackOption.activeColumn);
349
405
 
350
406
  if(columnOptions && columnOptions.length){
351
- var memberIndices = this.getStackMemberIndices(stackOption.stackId);
407
+ var memberIndices = this.getStackMemberIndices(stackOption.id);
352
408
  for(var i = 0; i < memberIndices.length; i++){
353
409
  var colIndex = memberIndices[i];
354
410
  var colOption = columnOptions[colIndex];
@@ -359,7 +415,7 @@ ColumnStackPlugin.prototype.getConfigObject = function (gridOptions) {
359
415
  }
360
416
 
361
417
  var stackConfigObj = {
362
- id: stackOption.stackId
418
+ id: stackOption.id
363
419
  };
364
420
  var name = stackOption.name;
365
421
  var collapsed = stackOption.collapsed;
@@ -376,18 +432,17 @@ ColumnStackPlugin.prototype.getConfigObject = function (gridOptions) {
376
432
  }
377
433
 
378
434
  if (this._autoStacking) {
379
- var fields = this._idToFields[stackOption.stackId];
435
+ var fields = this._idToFields[stackOption.id];
380
436
  var activeColumnField = this._getField(activeColIndex);
381
437
 
382
438
  stackConfigObj.fields = fields;
383
439
  stackConfigObj.activeColumn = activeColumnField;
384
440
 
385
441
  } else {
386
- var children = this.getStackMemberIds(stackOption.stackId);
387
- var activeColumn = this.getColumnId(activeColIndex);
388
-
389
- stackConfigObj.children = children;
390
- stackConfigObj.activeColumn = activeColumn;
442
+ stackConfigObj.children = this.getStackMemberIds(stackOption.id);
443
+ if(stackOption.activeColumn) {
444
+ stackConfigObj.activeColumn = stackOption.activeColumn; // WARNING: Column Id or field
445
+ }
391
446
  }
392
447
  stackOptions.push(stackConfigObj);
393
448
  }
@@ -404,144 +459,87 @@ ColumnStackPlugin.prototype.getConfigObject = function (gridOptions) {
404
459
  */
405
460
  ColumnStackPlugin.prototype._afterInit = function () {
406
461
  if(this._pendingStacks) {
407
- this.removeAllStacks(false);
408
- for(var sid in this._pendingStacks) {
409
- var stackOpt = this._pendingStacks[sid];
410
- if(!stackOpt.colRefs){
411
- this._transformStackConfig(stackOpt);
412
- }
413
- this.stackColumns(stackOpt.colRefs, sid, stackOpt);
414
- }
462
+ this._applyUserConfigs(this._pendingStacks);
415
463
  this._pendingStacks = null;
416
464
  }
417
465
  // In case of lazy loading
418
466
  // DO something
419
467
  };
420
- /** WARNING: This method is slow
421
- * @private
422
- * @param {Object} stackRef
423
- * @param {number=} colCount
424
- * @return {number}
425
- */
426
- ColumnStackPlugin.prototype._getColumnIndex = function(stackRef, colCount) {
427
- if(stackRef) {
428
- if(colCount == null) {
429
- colCount = this.getColumnCount();
430
- }
431
- for(var i = 0; i < colCount; i++) {
432
- if(stackRef === this._getUniqueRef(i)) {
433
- return i;
434
- }
435
- }
436
- }
437
- return -1;
438
- };
439
- /** WARNING: This method is really slow
440
- * @private
441
- * @param {!Array.<Object>} stackRefs
442
- * @return {!Array.<number>}
443
- */
444
- ColumnStackPlugin.prototype._getColumnIndices = function(stackRefs) {
445
- var refCount = stackRefs ? stackRefs.length : 0;
446
- var ary = new Array(refCount);
447
- if(refCount) {
448
- var colCount = this.getColumnCount();
449
- for(var i = 0; i < refCount; i++) {
450
- ary[i] = this._getColumnIndex(stackRefs[i], colCount);
451
- }
452
- }
453
- return ary;
454
- };
455
- /** @private
456
- * @param {number} colIndex
457
- * @param {!Object} refObj
458
- */
459
- ColumnStackPlugin.prototype._setUniqueRef = function(colIndex, refObj) {
460
- this._newColumnData(colIndex)["stack"] = refObj;
461
- };
462
- /** @private
463
- * @param {number} colIndex
464
- * @return {Object}
465
- */
466
- ColumnStackPlugin.prototype._getUniqueRef = function(colIndex) {
467
- return this._getColumnOption(colIndex, "stack") || null;
468
- };
469
- /** @private
470
- * @return {!Array.<Object>}
471
- */
472
- ColumnStackPlugin.prototype._getUniqueRefs = function() {
473
- var colCount = this.getColumnCount();
474
- var ary = new Array(colCount);
475
- for(var c = 0; c < colCount; ++c) {
476
- ary[c] = this._getUniqueRef(c);
477
- }
478
- return ary;
479
- };
480
468
 
481
469
  /** @private
482
- * @param {number} colIndex
483
- * @param {Object} stackOptions
484
- */
485
- ColumnStackPlugin.prototype._setColumnStackOptions = function(colIndex, stackOptions) {
486
- var ref = this._getUniqueRef(colIndex);
487
- if(ref) {
488
- ref["stackOpt"] = stackOptions;
470
+ * @param {*} colRef
471
+ * @return {string} column id or field
472
+ */
473
+ ColumnStackPlugin.prototype._toIdOrField = function(colRef) {
474
+ if(typeof colRef !== "string") {
475
+ if(typeof colRef === "number") {
476
+ return this.getColumnId(colRef);
477
+ // return this._getField(colRef);
478
+ }
479
+ return "";
489
480
  }
481
+ return colRef || "";
490
482
  };
491
483
  /** @private
492
484
  * @param {Object} stackConfig
493
- * @return {Object} stack config object
494
485
  */
495
486
  ColumnStackPlugin.prototype._transformStackConfig = function(stackConfig) {
496
- stackConfig.colRefs = [];
497
487
  var children = stackConfig.children;
498
- var fields = stackConfig.fields;
499
- var field;
500
- if(children){
501
- var childLen = stackConfig.children.length;
502
- for(var j = 0; j < childLen; j++){
503
- var colIndex = this.getColumnIndex(children[j]);
504
- if(colIndex !== -1){
505
- field = this._getField(colIndex);
506
- if(field) {
507
- stackConfig.colRefs.push(field);
508
- }
488
+ var childCount = children ? children.length : 0;
489
+ if(childCount) { // Fields or ids
490
+ for(var i = 0; i < childCount; i++){
491
+ var childRef = this._toIdOrField(children[i]);
492
+ if(childRef) {
493
+ children[i] = childRef;
509
494
  }
510
495
  }
511
- } else if(fields) {
512
- stackConfig.colRefs = fields;
513
- }
514
- var activeColumn = stackConfig.activeColumn;
515
- if(activeColumn != null && !this._autoStacking){
516
- var activeColIndex = this.getColumnIndex(activeColumn);
517
- if(activeColIndex !== -1){
518
- field = this._getField(activeColIndex);
519
- if(field){
520
- stackConfig.activeColumn = field;
521
- }
496
+ } else if(stackConfig.fields) {
497
+ stackConfig.children = stackConfig.fields;
498
+ } else if(!stackConfig.children) {
499
+ stackConfig.children = [];
500
+ }
501
+ if(!this._autoStacking) { // TODO: This may not be necessary
502
+ var activeColumn = this._toIdOrField(stackConfig.activeColumn);
503
+ if(activeColumn) {
504
+ stackConfig.activeColumn = activeColumn;
522
505
  }
523
506
  }
524
- return stackConfig;
525
507
  };
526
508
  /** @private
527
- * @param {number} colIndex
528
- * @return {*}
529
- */
530
- ColumnStackPlugin.prototype._getColumnStackOptions = function(colIndex) {
531
- var refObj = this._getUniqueRef(colIndex);
532
- if(refObj){
533
- return refObj["stackOpt"] || null;
509
+ * @param {Object=} stacks
510
+ */
511
+ ColumnStackPlugin.prototype._applyUserConfigs = function(stacks) {
512
+ if(stacks) {
513
+ this.removeAllStacks(false); // No UI update
514
+ for(var stackId in stacks) {
515
+ var stack = stacks[stackId];
516
+ this._transformStackConfig(stack);
517
+ this.stackColumns(stack.children, stackId, stack);
518
+ }
519
+ } else {
520
+ this.removeAllStacks(); // with UI update
534
521
  }
535
- return null;
536
522
  };
523
+
537
524
  /** @private
538
525
  * @param {number} colIndex
526
+ * @return {Object}
539
527
  */
540
- ColumnStackPlugin.prototype._removeColumnStackOptions = function(colIndex) {
541
- var refObj = this._getUniqueRef(colIndex);
542
- if(refObj) {
543
- refObj["stackOpt"] = null;
528
+ ColumnStackPlugin.prototype._getColumnStackOptions = function(colIndex) {
529
+ if(colIndex >= 0) {
530
+ var colId = this.getColumnId(colIndex);
531
+ var stack = this._groupDefs.getParentGroup(colId);
532
+ if(stack) {
533
+ return stack;
534
+ }
535
+
536
+ var field = this._getField(colIndex);
537
+ stack = this._groupDefs.getParentGroup(field);
538
+ if(stack) {
539
+ return stack;
540
+ }
544
541
  }
542
+ return null;
545
543
  };
546
544
  /** @private
547
545
  * @return {!Array.<number>} Sorted column indices
@@ -550,6 +548,34 @@ ColumnStackPlugin.prototype._getSelectedColumns = function() {
550
548
  var csp = this._getPlugin("ColumnSelectionPlugin");
551
549
  return (csp && csp.isEnabled()) ? csp.getSelectedColumns() : [];
552
550
  };
551
+ /** Hide the specified column to prevent it from flashing when it is added as a member of the stack
552
+ * @private
553
+ * @param {Object} stack
554
+ * @param {(number|Array.<number>)=} colRefs
555
+ * @param {number=} activeRef
556
+ */
557
+ ColumnStackPlugin.prototype._hideStackedColumns = function(stack, colRefs, activeRef) {
558
+ if(stack.spreading && !stack.collapsed) {
559
+ return; // In spreading mode, columns in an expanded stack don't need to be hiden
560
+ }
561
+ // WARNING: activeIndex may not be valid
562
+ var activeIndex = (typeof activeRef === "number") ? activeRef : this.getColumnIndex(stack.activeColumn);
563
+ var colIndices = null;
564
+ if(typeof colRefs === "number") {
565
+ colIndices = [colRefs];
566
+ } else if(colIndices == null) {
567
+ colIndices = this.getColumnIndices(stack.children);
568
+ } else {
569
+ colIndices = colRefs;
570
+ }
571
+ var validCount = colIndices.length;
572
+ for(var i = 0; i < validCount; ++i) {
573
+ var colIndex = colIndices[i];
574
+ if(colIndex !== activeIndex) {
575
+ this._setColumnVisibility(colIndex, false); // Hide a column
576
+ }
577
+ }
578
+ };
553
579
  /** @private
554
580
  * @param {number} colIndex
555
581
  * @param {boolean} shown
@@ -562,12 +588,12 @@ ColumnStackPlugin.prototype._setColumnVisibility = function(colIndex, shown) {
562
588
  }
563
589
  };
564
590
  /** @private
565
- * @param {Array.<Object>} stackRefs
591
+ * @param {Array} colRefs Array of column index, id, or field
566
592
  */
567
- ColumnStackPlugin.prototype._moveStackedColumns = function (stackRefs) {
568
- var ary = this._getColumnIndices(stackRefs);
569
- if(ary.length > 1) {
570
- this.reorderColumns(ary, ary[0]);
593
+ ColumnStackPlugin.prototype._moveStackedColumns = function (colRefs) {
594
+ var colIndices = this.getColumnIndices(colRefs);
595
+ if(colIndices.length > 1) {
596
+ this.reorderColumns(colIndices, colIndices[0]);
571
597
  }
572
598
  };
573
599
  /** @private
@@ -587,12 +613,12 @@ ColumnStackPlugin.prototype._isIconAvailable = function() {
587
613
  };
588
614
  /** @private
589
615
  * @param {number} colIndex
590
- * @param {Object} colData
616
+ * @param {Object} stackOpt
591
617
  */
592
- ColumnStackPlugin.prototype._updateIcon = function(colIndex, colData) {
593
- var stackRefs = (colData) ? colData.stackRefs : null;
594
- var spreading = (colData) ? colData.spreading : false;
618
+ ColumnStackPlugin.prototype._updateIcon = function(colIndex, stackOpt) {
619
+ var spreading = (stackOpt) ? stackOpt.spreading : false;
595
620
  var gridCount = this._hosts.length;
621
+
596
622
  for(var g = 0; g < gridCount; ++g) {
597
623
  var host = this._hosts[g];
598
624
  var tSect = host.getSection("title");
@@ -601,18 +627,19 @@ ColumnStackPlugin.prototype._updateIcon = function(colIndex, colData) {
601
627
  if(!cell) { continue; }
602
628
 
603
629
  var stackIcon = cell._stackIcon;
604
- if(stackRefs) {
605
- if(!stackIcon && this.isActiveStackedColumn(colIndex)) {
630
+ if(stackOpt) {
631
+ var activeStackedColumn = this.isActiveStackedColumn(colIndex);
632
+ if(!stackIcon && activeStackedColumn) {
606
633
  if(spreading) {
607
634
  cell.addClass("grouping");
608
635
  if(this._expandIconName) {
609
- if(colData.collapsed) {
636
+ if(stackOpt.collapsed) {
610
637
  stackIcon = Icon.create(this._expandIconName);
611
638
  } else {
612
639
  stackIcon = Icon.create(this._collapseIconName);
613
640
  }
614
641
  } else {
615
- if(colData.collapsed) {
642
+ if(stackOpt.collapsed) {
616
643
  stackIcon = Dom.text(">");
617
644
  } else {
618
645
  stackIcon = Dom.text("<");
@@ -637,7 +664,7 @@ ColumnStackPlugin.prototype._updateIcon = function(colIndex, colData) {
637
664
  cell._stackIcon = stackIcon;
638
665
 
639
666
  this._dispatch("iconCreated", {"icon": stackIcon, "colIndex": colIndex, "grid": host, "cell": cell});
640
- } else if(stackIcon && this.isInactiveStackedColumn(colIndex)) {
667
+ } else if(stackIcon && !activeStackedColumn) {
641
668
  cell.removeFloatingIcon(cell._stackIcon);
642
669
  cell._stackIcon = null;
643
670
  }
@@ -663,21 +690,22 @@ ColumnStackPlugin.prototype._updateUI = function() {
663
690
  this._updating = true;
664
691
  var colCount = this._getColumnCount();
665
692
  var gridCount = this._hosts.length;
666
- var colData, spreading;
693
+ var stackOpt, spreading;
667
694
  for(var c = 0; c < colCount; ++c) {
668
- colData = this._getColumnStackOptions(c);
669
- spreading = (colData) ? colData.spreading : false;
695
+ stackOpt = this._getColumnStackOptions(c);
696
+ spreading = (stackOpt) ? stackOpt.spreading : false;
697
+ spreading = spreading === true;
670
698
  for(var g = 0; g < gridCount; ++g) {
671
699
  var host = this._hosts[g];
672
- host.enableColumnClass(c, "grouped", spreading === true);
700
+ host.enableColumnClass(c, "grouped", spreading);
673
701
  }
674
702
 
675
- this._updateIcon(c, colData);
676
- if(colData) {
677
- if(colData.spreading) {
678
- this._collapseMember(c, colData.collapsed);
703
+ this._updateIcon(c, stackOpt);
704
+ if(stackOpt) {
705
+ if(stackOpt.spreading) {
706
+ this._collapseMember(c, stackOpt.collapsed);
679
707
  } else {
680
- this._setColumnVisibility(c, this.isActiveStackedColumn(c)); // a little slow
708
+ this._setColumnVisibility(c, this._isActiveStackedColumn(c, stackOpt)); // a little slow
681
709
  }
682
710
  }
683
711
  }
@@ -688,8 +716,8 @@ ColumnStackPlugin.prototype._updateUI = function() {
688
716
  * @param {boolean} collapsed
689
717
  */
690
718
  ColumnStackPlugin.prototype._collapseMember = function(colIndex, collapsed) {
691
- var colData = this._getColumnStackOptions(colIndex);
692
- if(!colData) {
719
+ var stackOpt = this._getColumnStackOptions(colIndex);
720
+ if(!stackOpt) {
693
721
  return;
694
722
  }
695
723
 
@@ -703,9 +731,9 @@ ColumnStackPlugin.prototype._collapseMember = function(colIndex, collapsed) {
703
731
  var rowCount = tSect.getRowCount();
704
732
  var cell = tSect.getCell(colIndex, rowCount - 1, true);
705
733
  if(collapsed) {
706
- if(colData.collapsed !== collapsed || !colData._origWidth) { // Prevent from getting width while column is collapsed
707
- colData._origWidth = this._getColumnWidth(host, colIndex);
708
- colData._scalable = host.getColumnScalability(colIndex);
734
+ if(stackOpt.collapsed !== collapsed || !stackOpt._origWidth) { // Prevent from getting width while column is collapsed
735
+ stackOpt._origWidth = this._getColumnWidth(host, colIndex);
736
+ stackOpt._scalable = host.getColumnScalability(colIndex);
709
737
  }
710
738
 
711
739
  host.setColumnWidth(colIndex, tSect.getRowHeight(0), false);
@@ -718,8 +746,8 @@ ColumnStackPlugin.prototype._collapseMember = function(colIndex, collapsed) {
718
746
  }
719
747
  }
720
748
  } else {
721
- if(colData._origWidth != null) {
722
- host.setColumnWidth(colIndex, colData._origWidth, colData._scalable);
749
+ if(stackOpt._origWidth != null) {
750
+ host.setColumnWidth(colIndex, stackOpt._origWidth, stackOpt._scalable);
723
751
  }
724
752
 
725
753
  if(cell._stackIcon) {
@@ -734,8 +762,6 @@ ColumnStackPlugin.prototype._collapseMember = function(colIndex, collapsed) {
734
762
  host.setColumnVisibility(colIndex, !collapsed, 2);
735
763
  }
736
764
  }
737
-
738
- colData.collapsed = collapsed;
739
765
  };
740
766
  /** Get column width that is currently defined in the layout inside grid <br>
741
767
  * (not included the effect by this extension). <br>
@@ -758,12 +784,11 @@ ColumnStackPlugin.prototype._getColumnWidth = function(host, colIndex) {
758
784
  * @return {Array.<number>} Null if there is no column data
759
785
  */
760
786
  ColumnStackPlugin.prototype.getMemberIndices = function(colIndex) {
761
- var colData = this._getColumnStackOptions(colIndex);
762
- if(!colData) {
763
- return null;
787
+ var stackOpt = this._getColumnStackOptions(colIndex);
788
+ if(stackOpt) {
789
+ return this.getColumnIndices(stackOpt.children);
764
790
  }
765
-
766
- return this._getColumnIndices(colData.stackRefs);
791
+ return null;
767
792
  };
768
793
  /** @public
769
794
  * @param {number} colIndex
@@ -771,31 +796,34 @@ ColumnStackPlugin.prototype.getMemberIndices = function(colIndex) {
771
796
  * @return {Array.<number>} Null if it is non-grouped column
772
797
  */
773
798
  ColumnStackPlugin.prototype.collapseGroup = function(colIndex, collapsed) {
774
- var groupMembers = this.getMemberIndices(colIndex);
775
- if(!groupMembers) {
799
+ var stackOpt = this._getColumnStackOptions(colIndex);
800
+ if(!stackOpt) {
801
+ return null;
802
+ }
803
+ if(!stackOpt.spreading) {
776
804
  return null;
777
805
  }
778
806
 
779
807
  collapsed = collapsed !== false;
780
- for(var j = groupMembers.length; --j >= 0;) {
781
- this._collapseMember(groupMembers[j], collapsed);
808
+ if(stackOpt.collapsed !== collapsed) {
809
+ var groupMembers = this.getColumnIndices(stackOpt.children);
810
+ if(groupMembers) {
811
+ for(var j = groupMembers.length; --j >= 0;) {
812
+ this._collapseMember(groupMembers[j], collapsed);
813
+ }
814
+ stackOpt.collapsed = collapsed;
815
+
816
+ return groupMembers;
817
+ }
782
818
  }
783
- return groupMembers;
819
+ return null;
784
820
  };
785
821
  /** @public
786
822
  * @param {number} colIndex
787
823
  * @return {Array.<number>} Null if it is non-grouped column
788
824
  */
789
825
  ColumnStackPlugin.prototype.expandGroup = function(colIndex) {
790
- var groupMembers = this.getMemberIndices(colIndex);
791
- if(!groupMembers) {
792
- return null;
793
- }
794
-
795
- for(var j = groupMembers.length; --j >= 0;) {
796
- this._collapseMember(groupMembers[j], false);
797
- }
798
- return groupMembers;
826
+ return this.collapseGroup(colIndex, false);
799
827
  };
800
828
  /** Intended for determining if the selected columns can be stacked from the context menu
801
829
  * @public
@@ -806,11 +834,9 @@ ColumnStackPlugin.prototype.isColumnStackable = function(colIndices) {
806
834
  if(!colIndices) {
807
835
  return false;
808
836
  }
809
-
810
837
  var len = colIndices.length;
811
838
  for(var i = 0; i < len; ++i) {
812
- var colIndex = colIndices[i];
813
- if(this.isColumnInCollection(colIndex)) {
839
+ if(this._getColumnStackOptions(colIndices[i])) {
814
840
  return false;
815
841
  }
816
842
  }
@@ -827,36 +853,46 @@ ColumnStackPlugin.prototype.isColumnStacked = function(colIndices) {
827
853
  }
828
854
  var len = colIndices.length;
829
855
  for(var i = 0; i < len; ++i) {
830
- var colData = this._getColumnStackOptions(colIndices[i]);
831
- if(colData && colData.stackRefs) {
856
+ if(this._getColumnStackOptions(colIndices[i])) {
832
857
  return true;
833
858
  }
834
859
  }
835
860
  return false;
836
861
  };
837
- /** This is used for removing some column state while the column is inactive
862
+ /** This method is deprecated in favor of isColumnActive method. This method checks if the given column is inactive or doesn't have the icon.
838
863
  * @public
839
864
  * @param {number} colIndex
840
865
  * @return {boolean} Return true if the columns is grouped and inactive
866
+ * @see {@link ColumnStackPlugin#isColumnActive}
841
867
  */
842
868
  ColumnStackPlugin.prototype.isInactiveStackedColumn = function(colIndex) {
843
869
  var stackOpt = this._getColumnStackOptions(colIndex);
844
- if(stackOpt && stackOpt.stackRefs) {
845
- return colIndex !== this._getColumnIndex(stackOpt.activeColumn);
870
+ if(stackOpt) {
871
+ return !this._isActiveStackedColumn(colIndex, stackOpt);
846
872
  }
847
873
  return false;
848
874
  };
849
- /** Check if the given column is active (visible) in the stack
875
+ /** This method is deprecated in favor of isColumnActive method. This method checks if the given column is active and has the icon.
850
876
  * @public
851
877
  * @param {number} colIndex
852
878
  * @return {boolean}
879
+ * @see {@link ColumnStackPlugin#isColumnActive}
853
880
  */
854
881
  ColumnStackPlugin.prototype.isActiveStackedColumn = function(colIndex) {
855
- var stackOpt = this._getColumnStackOptions(colIndex);
856
- if(stackOpt && stackOpt.stackRefs) {
857
- return colIndex === this._getColumnIndex(stackOpt.activeColumn);
882
+ return this._isActiveStackedColumn(colIndex, this._getColumnStackOptions(colIndex));
883
+ };
884
+ /** @private
885
+ * @param {number} colIndex
886
+ * @param {Object} stackOpt
887
+ * @return {boolean}
888
+ */
889
+ ColumnStackPlugin.prototype._isActiveStackedColumn = function(colIndex, stackOpt) {
890
+ if(stackOpt) {
891
+ _resolveActiveColumn(stackOpt);
892
+
893
+ return colIndex === this.getColumnIndex(stackOpt.activeColumn);
858
894
  }
859
- return true;
895
+ return false;
860
896
  };
861
897
 
862
898
  /** @public
@@ -886,11 +922,7 @@ ColumnStackPlugin.prototype.isCollapsingMode = function(colIndex) {
886
922
  * @return {boolean}
887
923
  */
888
924
  ColumnStackPlugin.prototype.isColumnInCollection = function(colIndex) {
889
- var stackOpt = this._getColumnStackOptions(colIndex);
890
- if(stackOpt) {
891
- return true;
892
- }
893
- return false;
925
+ return this._getColumnStackOptions(colIndex) ? true : false;
894
926
  };
895
927
  /** Alias of isColumnInCollection
896
928
  * @public
@@ -925,7 +957,7 @@ ColumnStackPlugin.prototype.isColumnActive = function(colIndex) {
925
957
  return false;
926
958
  }
927
959
  } else {
928
- return colIndex === this._getColumnIndex(stackOpt.activeColumn);
960
+ return colIndex === this.getColumnIndex(stackOpt.activeColumn);
929
961
  }
930
962
  }
931
963
  return false;
@@ -936,8 +968,8 @@ ColumnStackPlugin.prototype.isColumnActive = function(colIndex) {
936
968
  * @return {string}
937
969
  */
938
970
  ColumnStackPlugin.prototype.getStackId = function(colIndex) {
939
- var colData = this._getColumnStackOptions(colIndex);
940
- return colData ? colData.stackId : "";
971
+ var stackOpt = this._getColumnStackOptions(colIndex);
972
+ return stackOpt ? stackOpt.id : "";
941
973
  };
942
974
  /** @public
943
975
  * @param {Array.<number|string>=} colRefs Names of fields or column indices. If not specified, selected columns will be used.
@@ -946,109 +978,98 @@ ColumnStackPlugin.prototype.getStackId = function(colIndex) {
946
978
  * @return {boolean} Return true if all of the given columns is stacked together
947
979
  */
948
980
  ColumnStackPlugin.prototype.stackColumns = function(colRefs, stackId, options) {
949
- var fields = [];
950
- var i, colIndex, sid;
981
+ var i, sid;
951
982
  options = options || {};
952
983
 
953
984
  if(stackId) {
954
- if(this._stacks[stackId]) {
985
+ if(this._groupDefs.getGroup(stackId)) {
955
986
  return false; // Cannot store the same stack Id
956
987
  }
957
988
  sid = stackId;
958
989
  } else {
959
- sid = this._generateStackId();
960
- }
961
-
962
- // If grid is not initialize, add setting to pending stacks
963
- if(!this._initializedGrid) {
964
- var pendingStacks = this._pendingStacks || {};
965
- pendingStacks[sid] = {
966
- colRefs: colRefs,
967
- spreading: false,
968
- collapsed: false,
969
- activeColumn: options.activeColumn || colRefs[0]
970
- };
971
- this._pendingStacks = pendingStacks;
972
- return false;
990
+ sid = stackId = this._generateStackId();
973
991
  }
974
992
 
975
993
  if(!colRefs) {
976
994
  colRefs = this._getSelectedColumns();
977
995
  }
978
996
 
979
- if(colRefs.length) {
980
- if(typeof colRefs[0] === "string") {
981
- fields = colRefs.slice();
982
- colRefs = this.getColumnIndices(colRefs);
983
- } else {
984
- colRefs.sort(function(a, b) { return a - b; }); // Only sort in the case of column index stack
985
- for(i = 0; i < colRefs.length; i++){
986
- var field = this._getField(colRefs[i]);
987
- fields.push(field);
988
- }
997
+ var refCount = colRefs ? colRefs.length : 0;
998
+ if(refCount < 2) {
999
+ return false; // Only two or more columns can be stacked
1000
+ }
1001
+
1002
+ // Clone user data
1003
+ var isSpreading = options.spreading === true;
1004
+ var isCollapsed = options.collapsed !== false;
1005
+ var activeColumn = options.activeColumn || ""; // field or id
1006
+ var stack = {};
1007
+ stack.id = sid;
1008
+ stack.name = options.name || "";
1009
+ stack.spreading = isSpreading;
1010
+ stack.collapsed = isCollapsed;
1011
+ stack.activeColumn = activeColumn; // field or id
1012
+
1013
+ // If grid is not initialize, add setting to pending stacks
1014
+ if(!this._initializedGrid) {
1015
+ var pendingStacks = this._pendingStacks;
1016
+ if(!pendingStacks) {
1017
+ pendingStacks = this._pendingStacks = {};
989
1018
  }
1019
+ stack.children = colRefs;
1020
+ pendingStacks[sid] = stack;
1021
+ return false;
990
1022
  }
991
1023
 
992
- // Save stack fields for
1024
+ var children = colRefs.map(this._toIdOrField);
1025
+ var colIndices = this.getColumnIndices(colRefs); // WARNING: Invalid columns are filtered out
1026
+ var validCount = colIndices.length;
1027
+
1028
+ // Save stack fields for autoStacking
993
1029
  if(this._autoStacking){
994
1030
  if(!this._idToFields) {
995
1031
  this._idToFields = {};
996
1032
  }
997
- this._idToFields[sid] = fields;
1033
+ this._idToFields[sid] = children;
998
1034
  }
999
1035
 
1000
- var len = colRefs.length;
1001
- if(len <= 1) {
1002
- return false; // Only two or more columns can be stacked
1003
- }
1004
- if(!this.isColumnStackable(colRefs)) {
1036
+ // Prevent columns already in a stack from moving out to another stack
1037
+ if(!this.isColumnStackable(colIndices)) {
1005
1038
  return false;
1006
1039
  }
1007
1040
 
1008
- var activeIndex = colRefs[0];
1009
- if(options.activeColumn != null) {
1010
- var idx = this.getColumnIndex(options.activeColumn);
1011
- if(idx > -1 && colRefs.indexOf(idx) > -1) {
1012
- activeIndex = idx;
1041
+ // TODO: If columns are stored as column id and activeColumn is a field, active index could not be found
1042
+ stack.children = children;
1043
+ var activeIndex = -1;
1044
+ if(activeColumn && typeof activeColumn === "string") {
1045
+ activeIndex = this.getColumnIndex(activeColumn);
1046
+ if(children.indexOf(activeColumn) < 0) { // children and activeColumn may have different type
1047
+ var field = this._getField(activeIndex);
1048
+ if(field === activeColumn) {
1049
+ stack.activeColumn = activeColumn = this.getColumnId(activeIndex);
1050
+ }
1013
1051
  }
1052
+ } else if(typeof activeColumn === "number"){
1053
+ activeIndex = activeColumn;
1014
1054
  }
1015
1055
 
1016
- // Collecting data
1017
- var stack = {};
1018
- stack.stackId = sid;
1019
- stack.name = options.name || "";
1020
- stack.spreading = options.spreading === true;
1021
- if(stack.spreading) {
1022
- activeIndex = colRefs[len - 1];
1056
+ if(_resolveActiveColumn(stack)) {
1057
+ activeColumn = stack.activeColumn;
1058
+ activeIndex = this.getColumnIndex(activeColumn);
1023
1059
  }
1024
- stack.collapsed = options.collapsed !== false;
1025
- stack.stackRefs = new Array(len);
1026
- for(i = 0; i < len; ++i) {
1027
- colIndex = colRefs[i];
1028
- this._setColumnStackOptions(colIndex, stack);
1029
- stack.stackRefs[i] = this._getUniqueRef(colIndex);
1030
1060
 
1031
- if(colIndex == activeIndex){
1032
- stack.activeColumn = stack.stackRefs[i];
1033
- }
1034
-
1035
- // Prevent from flashing in stack mode
1036
- if(colIndex !== activeIndex && stack.collapsed !== false) {
1037
- this._setColumnVisibility(colIndex, false);
1038
- }
1039
- }
1061
+ this._hideStackedColumns(stack, colIndices, activeIndex);
1040
1062
 
1041
1063
  // Make sure that all columns stay packed together
1042
- this._moveStackedColumns(stack.stackRefs);
1064
+ this.reorderColumns(colIndices, colIndices[0]);
1043
1065
 
1044
- if(stack.spreading) {
1045
- stack.activeColumn = stack.stackRefs[stack.stackRefs.length - 1]; // Right most column is the active column
1046
- } else {
1066
+ // Update column selection
1067
+ if(!isSpreading) {
1047
1068
  var csp = this._getPlugin("ColumnSelectionPlugin");
1048
1069
  if(csp && csp.isEnabled()){
1049
1070
  var stackSelection = false;
1050
- for(i = 0; i < len; ++i){
1051
- colIndex = colRefs[i];
1071
+ for(i = 0; i < validCount; ++i){
1072
+ colIndex = colIndices[i];
1052
1073
  if(colIndex === activeIndex){
1053
1074
  continue;
1054
1075
  }
@@ -1059,12 +1080,14 @@ ColumnStackPlugin.prototype.stackColumns = function(colRefs, stackId, options) {
1059
1080
  }
1060
1081
  if(stackSelection) {
1061
1082
  csp.selectSingleColumn(activeIndex);
1062
- csp.dispatchSelectionChanged();
1083
+ if(csp.dispatchSelectionChanged) {
1084
+ csp.dispatchSelectionChanged();
1085
+ }
1063
1086
  }
1064
1087
  }
1065
1088
  }
1066
1089
 
1067
- this._stacks[sid] = stack;
1090
+ this._groupDefs.setGroup(sid, stack);
1068
1091
 
1069
1092
  var cfp = this._getPlugin("ColumnFilterPlugin");
1070
1093
  if(cfp) {
@@ -1084,7 +1107,6 @@ ColumnStackPlugin.prototype.stackColumns = function(colRefs, stackId, options) {
1084
1107
  * @return {boolean} If the stack has been updated, return true.
1085
1108
  */
1086
1109
  ColumnStackPlugin.prototype.setStack = function(colRefs, activeColRef) {
1087
-
1088
1110
  var sid = "_uniqueStack"; // WARNING : This hardcode for assign uniqe stacking id
1089
1111
 
1090
1112
  this.removeAllStacks();
@@ -1116,39 +1138,19 @@ ColumnStackPlugin.prototype.unstackColumns = function(colIndices) {
1116
1138
  colIndex = colIndices[i];
1117
1139
  if(colIndex < colCount) {
1118
1140
  var stackOpt = this._getColumnStackOptions(colIndex);
1119
- if(!stackOpt) {
1120
- continue; // Invalid index
1121
- }
1122
- if(stackOpt.collapsed === true && stackOpt.spreading === true) {
1123
- this.expandGroup(colIndex);
1141
+ if(stackOpt) {
1142
+ stacks[stackOpt.id] = 1; // Exclude duplicate stacks
1124
1143
  }
1125
- stacks[stackOpt.stackId] = 1; // Exclude duplicate stacks
1126
1144
  }
1127
1145
  }
1128
1146
 
1129
1147
  var dirty = false;
1130
-
1131
1148
  for(var sid in stacks) {
1132
- var stack = this._stacks[sid];
1133
- if(!stack) {
1134
- continue; // Invalid stackId
1135
- }
1136
1149
  dirty = true;
1137
-
1138
- var stackRefs = stack.stackRefs;
1139
- len = stackRefs.length;
1140
-
1141
- for(i = 0; i < len; ++i) {
1142
- var stackRef = stackRefs[i];
1143
- colIndex = this._getColumnIndex(stackRef);
1144
- this._removeColumnStackOptions(colIndex);
1145
- this._setColumnVisibility(colIndex, true);
1146
- }
1147
-
1150
+ this._removeStack(sid);
1148
1151
  if(this._idToFields) {
1149
1152
  delete this._idToFields[sid]; // TODO: Clear the map whenever it no longer has a member
1150
1153
  }
1151
- delete this._stacks[sid]; // Remove all reference to the stack
1152
1154
  }
1153
1155
  if(dirty) {
1154
1156
  var cfp = this._getPlugin("ColumnFilterPlugin");
@@ -1165,23 +1167,22 @@ ColumnStackPlugin.prototype.unstackColumns = function(colIndices) {
1165
1167
  * @return {boolean} Returns true if there is any change
1166
1168
  */
1167
1169
  ColumnStackPlugin.prototype._removeStack = function(stackId) {
1168
- var stack = this._stacks[stackId];
1170
+ var stack = this._groupDefs.getGroup(stackId);
1169
1171
  if(!stack) {
1170
1172
  return false;
1171
1173
  }
1172
1174
 
1173
- var colIndices = this._getColumnIndices(stack.stackRefs);
1175
+ var colIndices = this.getColumnIndices(stack.children);
1174
1176
  var len = colIndices.length;
1175
1177
  for(var i = 0; i < len; ++i) {
1176
- var colIndex = colIndices[i];
1177
- if(colIndex >= 0) {
1178
- if(stack.collapsed && stack.spreading) {
1179
- this.collapseGroup(colIndex, false);
1180
- }
1181
- this._removeColumnStackOptions(colIndex);
1182
- this._setColumnVisibility(colIndex, true);
1178
+ var colIndex = colIndices[i]; // colIndex is guaranteed to be positive (verified)
1179
+ if(stack.spreading && stack.collapsed) {
1180
+ this._collapseMember(colIndex, false);
1183
1181
  }
1182
+
1183
+ this._setColumnVisibility(colIndex, true);
1184
1184
  }
1185
+ this._groupDefs.removeGroup(stackId);
1185
1186
  return true;
1186
1187
  };
1187
1188
  /** @public
@@ -1205,19 +1206,20 @@ ColumnStackPlugin.prototype.removeStack = function(stackId) {
1205
1206
  * @return {boolean} true if at least one stacking removed
1206
1207
  */
1207
1208
  ColumnStackPlugin.prototype.removeAllStacks = function(enableUpdateUI) {
1208
- var dirty = false;
1209
- for(var stackId in this._stacks) {
1210
- dirty = true;
1211
- this._removeStack(stackId);
1209
+ var groupIds = this._groupDefs.getGroupIds();
1210
+ var groupCount = groupIds.length;
1211
+ for(var i = 0; i < groupCount; ++i) {
1212
+ this._removeStack(groupIds[i]);
1212
1213
  }
1213
- if(dirty) {
1214
+ if(groupCount) {
1214
1215
  this._idToFields = null;
1215
- this._stacks = {};
1216
+ this._groupDefs.removeAllGroups(); // TODO: May not necessary
1217
+
1216
1218
  if(enableUpdateUI !== false) {
1217
1219
  this._updateUI(); // asyncronous
1218
1220
  }
1219
1221
  }
1220
- return dirty;
1222
+ return groupCount ? true : false;
1221
1223
  };
1222
1224
  /** @public
1223
1225
  * @param {number|Event} colRef
@@ -1236,26 +1238,26 @@ ColumnStackPlugin.prototype.swapColumn = function(colRef, swappingIndex) {
1236
1238
  }
1237
1239
  }
1238
1240
 
1239
- var colData = this._getColumnStackOptions(colIndex);
1240
- if(!colData) {
1241
+ var stackOpt = this._getColumnStackOptions(colIndex);
1242
+ if(!stackOpt) {
1241
1243
  return false; // Invalid column index
1242
1244
  }
1243
- var stackRefs = colData.stackRefs;
1244
- if(!stackRefs) {
1245
+ var children = stackOpt.children;
1246
+ if(!children) {
1245
1247
  return false; // Invalid column type
1246
1248
  }
1247
- var newActiveColumn = stackRefs[swappingIndex];
1249
+ var newActiveColumn = children[swappingIndex];
1248
1250
  if(!newActiveColumn) {
1249
1251
  return false; // Invalid selected index
1250
1252
  }
1251
1253
 
1252
- if(newActiveColumn === colData.activeColumn) {
1254
+ if(newActiveColumn === stackOpt.activeColumn) {
1253
1255
  return false; // The given index is already active stacked column
1254
1256
  }
1255
1257
 
1256
- var prevActiveColumnIndex = this._getColumnIndex(colData.activeColumn);
1257
- var newActiveColumnIndex = this._getColumnIndex(newActiveColumn);
1258
- colData.activeColumn = newActiveColumn; // Change active column
1258
+ var prevActiveColumnIndex = this.getColumnIndex(stackOpt.activeColumn);
1259
+ var newActiveColumnIndex = this.getColumnIndex(newActiveColumn);
1260
+ stackOpt.activeColumn = newActiveColumn; // Change active column
1259
1261
 
1260
1262
  this._setColumnVisibility(newActiveColumnIndex, true);
1261
1263
  this._setColumnVisibility(prevActiveColumnIndex, false); // Hide current active column
@@ -1263,16 +1265,18 @@ ColumnStackPlugin.prototype.swapColumn = function(colRef, swappingIndex) {
1263
1265
  var csp = this._getPlugin("ColumnSelectionPlugin");
1264
1266
  if(csp && csp.isEnabled()) {
1265
1267
  csp.selectSingleColumn(newActiveColumnIndex);
1266
- csp.dispatchSelectionChanged();
1268
+ if(csp.dispatchSelectionChanged) {
1269
+ csp.dispatchSelectionChanged();
1270
+ }
1267
1271
  }
1268
1272
  var cfp = this._getPlugin("ColumnFilterPlugin");
1269
1273
  if(cfp) {
1270
1274
  cfp["refresh"]();
1271
1275
  }
1272
1276
 
1273
- colData = this._getColumnStackOptions(prevActiveColumnIndex);
1274
- this._updateIcon(prevActiveColumnIndex, colData);
1275
- this._updateIcon(newActiveColumnIndex, colData);
1277
+ stackOpt = this._getColumnStackOptions(prevActiveColumnIndex);
1278
+ this._updateIcon(prevActiveColumnIndex, stackOpt);
1279
+ this._updateIcon(newActiveColumnIndex, stackOpt);
1276
1280
 
1277
1281
  return true;
1278
1282
  };
@@ -1290,151 +1294,83 @@ ColumnStackPlugin.prototype._onPreSectionRender = function (e) {
1290
1294
  * @param {Object} e
1291
1295
  */
1292
1296
  ColumnStackPlugin.prototype._onColumnRemoved = function (e) {
1293
- var colData = /** @type{Object} */(e.columnData);
1294
- if(!colData) { return; }
1295
-
1296
- var stackRef = colData["stack"];
1297
- if(!stackRef) { return; }
1297
+ if(this._inResetting) {
1298
+ return;
1299
+ }
1298
1300
 
1299
- var stackOpt = stackRef["stackOpt"];
1300
- if(!stackOpt) { return; }
1301
+ var colRef = "";
1302
+ var colId = e.colId;
1303
+ var stackOpt = this._groupDefs.getParentGroup(colId);
1301
1304
 
1302
- // update members
1303
- if(stackOpt.stackRefs) {
1304
- var index = stackOpt.stackRefs.indexOf(stackRef);
1305
- if(index > -1) {
1306
- stackOpt.stackRefs.splice(index, 1);
1307
- if(stackOpt.stackRefs.length < 2) {
1308
- // delete group
1309
- var colIndex = this._getColumnIndex(stackOpt.stackRefs[0]);
1310
- if(colIndex > -1) {
1311
- this.unstackColumns([colIndex]);
1312
- } else {
1313
- // All stacked columns are removed
1314
- delete this._stacks[stackOpt.stackId];
1315
- }
1316
- } else {
1317
- this._updateActiveColumn(stackOpt);
1318
- }
1305
+ if(stackOpt) {
1306
+ colRef = colId;
1307
+ } else {
1308
+ var colData = /** @type{Object} */(e.columnData);
1309
+ var field = colData ? colData.field : "";
1310
+ stackOpt = this._groupDefs.getParentGroup(fields);
1311
+ if(stackOpt) {
1312
+ colRef = field;
1313
+ } else {
1314
+ return;
1319
1315
  }
1320
1316
  }
1321
-
1322
- colData["stack"] = null;
1317
+ // update members
1318
+ var children = stackOpt.children;
1319
+ if(children.length <= 2) {
1320
+ this._removeStack(stackOpt.id);
1321
+ this._updateUI();
1322
+ } else {
1323
+ this._groupDefs.removeGroupChild(stackOpt.id, colRef);
1324
+ this._updateActiveColumn(stackOpt); // This may trigger _updateUI
1325
+ }
1323
1326
  };
1324
-
1325
1327
  /** @private
1326
1328
  * @param {Object} e
1327
1329
  */
1328
1330
  ColumnStackPlugin.prototype._onColumnMoved = function (e) {
1329
- if(this._stacking) { // during the stacking, there is no need to recalculate stacks
1330
- return;
1331
+ if(this._inReordering || this._inResetting) {
1332
+ return; // during the reordering or resetting, there is no need to recalculate stacks
1331
1333
  }
1332
1334
 
1333
1335
  var toIndex = e.toColIndex;
1334
- var stackOpt = this._getColumnStackOptions(toIndex);
1335
-
1336
- // column swaping
1337
- if(stackOpt && !stackOpt.spreading) {
1338
- return;
1336
+ var colRef = "";
1337
+ var colId = this.getColumnId(toIndex);
1338
+ var stackOpt = this._groupDefs.getParentGroup(colId);
1339
+ if(stackOpt) {
1340
+ colRef = colId;
1341
+ } else {
1342
+ var field = this._getField(toIndex);
1343
+ stackOpt = this._groupDefs.getParentGroup(field);
1344
+ if(stackOpt) {
1345
+ colRef = field;
1346
+ }
1339
1347
  }
1340
1348
 
1341
- var stackRef = this._getUniqueRef(toIndex);
1342
-
1343
1349
  var leftStackOpt = this._getColumnStackOptions(toIndex - 1);
1344
1350
  var rightStackOpt = this._getColumnStackOptions(toIndex + 1);
1345
- var index, leftColStackRef;
1346
- // move inside
1347
- if(stackOpt) {
1348
- if(stackOpt === leftStackOpt) {
1349
- leftStackOpt.stackRefs.splice(leftStackOpt.stackRefs.indexOf(stackRef), 1);
1350
- leftColStackRef = this._getUniqueRef(toIndex - 1);
1351
- index = leftStackOpt.stackRefs.indexOf(leftColStackRef);
1352
- if(index > -1) {
1353
- leftStackOpt.stackRefs.splice(index + 1, 0, stackRef);
1354
- if(leftStackOpt.spreading) {
1355
- leftStackOpt.activeColumn = leftStackOpt.stackRefs[leftStackOpt.stackRefs.length - 1];
1356
- } else {
1357
- leftStackOpt.activeColumn = leftStackOpt.stackRefs[0];
1358
- }
1359
- }
1360
- this._updateUI();
1361
- return;
1362
- } else if(stackOpt === rightStackOpt) {
1363
- rightStackOpt.stackRefs.splice(rightStackOpt.stackRefs.indexOf(stackRef), 1);
1364
- var rightColStackRef = this._getUniqueRef(toIndex + 1);
1365
- index = rightStackOpt.stackRefs.indexOf(rightColStackRef);
1366
- if(index > -1) {
1367
- rightStackOpt.stackRefs.splice(index, 0, stackRef);
1368
- if(rightStackOpt.spreading) {
1369
- rightStackOpt.activeColumn = rightStackOpt.stackRefs[rightStackOpt.stackRefs.length - 1];
1370
- } else {
1371
- rightStackOpt.activeColumn = rightStackOpt.stackRefs[0];
1372
- }
1373
- }
1374
- this._updateUI();
1375
- return;
1376
- }
1377
- }
1378
1351
 
1352
+ var stackChange = true;
1379
1353
  var dirty = false;
1380
-
1381
- var fromIndex = e.fromColIndex;
1382
- var rIndex = fromIndex + 1;
1383
- if(toIndex > fromIndex) {
1384
- // move right
1385
- rIndex = fromIndex;
1386
- }
1387
- var lIndex = rIndex - 1;
1388
-
1389
- // move out checking
1390
- leftStackOpt = this._getColumnStackOptions(lIndex);
1391
- if(leftStackOpt && leftStackOpt.spreading) {
1392
- index = leftStackOpt.stackRefs.indexOf(stackRef);
1393
- if(index > -1) {
1394
- leftStackOpt.stackRefs.splice(index, 1);
1395
- if(leftStackOpt.spreading) {
1396
- leftStackOpt.activeColumn = leftStackOpt.stackRefs[leftStackOpt.stackRefs.length - 1];
1397
- } else {
1398
- leftStackOpt.activeColumn = leftStackOpt.stackRefs[0];
1399
- }
1400
- this._setColumnStackOptions(toIndex, null);
1401
- dirty = true;
1402
- }
1403
- }
1404
-
1405
- rightStackOpt = this._getColumnStackOptions(rIndex);
1406
- if(rightStackOpt && rightStackOpt.spreading) {
1407
- index = rightStackOpt.stackRefs.indexOf(stackRef);
1408
- if(index > -1) {
1409
- rightStackOpt.stackRefs.splice(index, 1);
1410
- if(rightStackOpt.spreading) {
1411
- rightStackOpt.activeColumn = rightStackOpt.stackRefs[rightStackOpt.stackRefs.length - 1];
1354
+ if(stackOpt) {
1355
+ // The move was within the same stack
1356
+ if(stackOpt === leftStackOpt || stackOpt === rightStackOpt) {
1357
+ this._repositionMembers(stackOpt);
1358
+ stackChange = false;
1359
+ } else { // The move was out from its own stack
1360
+ if(stackOpt.children.length <= 2) {
1361
+ this._removeStack(stackOpt.id);
1412
1362
  } else {
1413
- rightStackOpt.activeColumn = rightStackOpt.stackRefs[0];
1363
+ this._removeRefFromStack(stackOpt, colRef, toIndex);
1414
1364
  }
1415
- this._setColumnStackOptions(toIndex, null);
1416
- dirty = true;
1417
1365
  }
1366
+ dirty = true;
1418
1367
  }
1419
1368
 
1420
- // move in checking
1421
- rIndex = toIndex + 1;
1422
- lIndex = rIndex - 2;
1423
- leftStackOpt = this._getColumnStackOptions(lIndex);
1424
- rightStackOpt = this._getColumnStackOptions(rIndex);
1425
- if(leftStackOpt && leftStackOpt.spreading &&
1426
- (leftStackOpt === rightStackOpt)) {
1427
- leftColStackRef = this._getUniqueRef(toIndex - 1);
1428
- index = leftStackOpt.stackRefs.indexOf(leftColStackRef);
1429
- if(index > -1) {
1430
- leftStackOpt.stackRefs.splice(index + 1, 0, stackRef);
1431
- if(leftStackOpt.spreading) {
1432
- leftStackOpt.activeColumn = leftStackOpt.stackRefs[leftStackOpt.stackRefs.length - 1];
1433
- } else {
1434
- leftStackOpt.activeColumn = leftStackOpt.stackRefs[0];
1369
+ if(stackChange) {
1370
+ if(this._isWithinStack(toIndex)) {
1371
+ if(this._addRefToStack(leftStackOpt, toIndex)) {
1372
+ dirty = true;
1435
1373
  }
1436
- this._setColumnStackOptions(toIndex, leftStackOpt);
1437
- dirty = true;
1438
1374
  }
1439
1375
  }
1440
1376
 
@@ -1442,30 +1378,20 @@ ColumnStackPlugin.prototype._onColumnMoved = function (e) {
1442
1378
  this._updateUI();
1443
1379
  }
1444
1380
  };
1445
-
1446
1381
  /** @private
1447
1382
  * @param {Object} e
1448
1383
  */
1449
1384
  ColumnStackPlugin.prototype._onColumnAdded = function (e) {
1450
1385
  var colIndex = e.colIndex;
1451
- this._setUniqueRef(colIndex, {});
1452
1386
 
1453
1387
  if (this._autoStacking) {
1454
1388
  if(this._idToFields) {
1455
1389
  this._requestStackingByFields();
1456
1390
  }
1457
- } else { // TODO: This logic is too complicated
1458
- // add to group
1459
- var leftStackOpt = this._getColumnStackOptions(colIndex - 1);
1460
- var rightStackOpt = this._getColumnStackOptions(colIndex + 1);
1461
- if(leftStackOpt && (leftStackOpt === rightStackOpt)) {
1462
- var leftColStackRef = this._getUniqueRef(colIndex - 1);
1463
- var stackRef = this._getUniqueRef(colIndex);
1464
- var index = leftStackOpt.stackRefs.indexOf(leftColStackRef);
1465
- if(index > -1) { // TODO: Reuse existing logic instead of modifying states like this
1466
- leftStackOpt.stackRefs.splice(index + 1, 0, stackRef);
1467
- leftStackOpt.activeColumn = leftStackOpt.stackRefs[leftStackOpt.stackRefs.length - 1];
1468
- this._setColumnStackOptions(colIndex, leftStackOpt);
1391
+ } else if(!this._inResetting) {
1392
+ var stackOpt = this._isWithinStack(colIndex);
1393
+ if(stackOpt) {
1394
+ if(this._addRefToStack(stackOpt, colIndex)) {
1469
1395
  this._updateUI();
1470
1396
  }
1471
1397
  }
@@ -1474,6 +1400,110 @@ ColumnStackPlugin.prototype._onColumnAdded = function (e) {
1474
1400
  /** @private
1475
1401
  * @param {Object} e
1476
1402
  */
1403
+ ColumnStackPlugin.prototype._onBeforeBatchOperation = function (e) {
1404
+ if(e.batchType === "reset") {
1405
+ this._inResetting = true;
1406
+ } else if(e.batchType === "move") {
1407
+ this._inReordering = true;
1408
+ }
1409
+ };
1410
+ /** @private
1411
+ * @param {Object} e
1412
+ */
1413
+ ColumnStackPlugin.prototype._onAfterBatchOperation = function (e) {
1414
+ if(e.batchType === "reset") {
1415
+ this._inResetting = false;
1416
+ // TODO: Revalidate members in stacks
1417
+ // TODO: Reposition members and stacks
1418
+ var groups = this._groupDefs.getGroups();
1419
+ var groupCount = groups.length;
1420
+ for(var i = 0; i < groupCount; ++i) {
1421
+ var group = groups[i];
1422
+ this._updateActiveColumn(group);
1423
+ this._hideStackedColumns(group);
1424
+ }
1425
+ } else if(e.batchType === "move") {
1426
+ this._inReordering = false;
1427
+ }
1428
+ };
1429
+
1430
+ /** @private
1431
+ * @param {number} colIndex
1432
+ * @return {Object} stackOption
1433
+ */
1434
+ ColumnStackPlugin.prototype._isWithinStack = function (colIndex) {
1435
+ var leftStackOpt = this._getColumnStackOptions(colIndex - 1);
1436
+ if(leftStackOpt) {
1437
+ var rightStackOpt = this._getColumnStackOptions(colIndex + 1);
1438
+ if(leftStackOpt === rightStackOpt) {
1439
+ return leftStackOpt;
1440
+ }
1441
+ }
1442
+ return null;
1443
+ };
1444
+ /** Add unique ref of the specified index to the proper position within the stack
1445
+ * @private
1446
+ * @param {Object} stackOption
1447
+ * @param {number} colIndex
1448
+ * @return {boolean}
1449
+ */
1450
+ ColumnStackPlugin.prototype._addRefToStack = function (stackOption, colIndex) {
1451
+ var colId = this._toIdOrField(colIndex);
1452
+
1453
+ this._hideStackedColumns(stackOption, colIndex);
1454
+
1455
+ // Find a position to be placed in the stack
1456
+ var rightColRef = this._toIdOrField(colIndex + 1);
1457
+ var children = stackOption.children;
1458
+ var pos = children.indexOf(rightColRef); // WARNING This does not work for field
1459
+ return this._groupDefs.addGroupChild(stackOption.id, colId, pos);
1460
+ };
1461
+ /** Remove unique ref of the specified index from the stack
1462
+ * @private
1463
+ * @param {Object} stackOption
1464
+ * @param {string} colRef For removing state
1465
+ * @param {number} colIndex For updating UI
1466
+ * @return {boolean}
1467
+ */
1468
+ ColumnStackPlugin.prototype._removeRefFromStack = function (stackOption, colRef, colIndex) {
1469
+ if(!this._groupDefs.removeGroupChild(stackOption.id, colRef)) {
1470
+ return false;
1471
+ }
1472
+
1473
+ var isCollapsed = stackOption.spreading && stackOption.collapsed;
1474
+ if(isCollapsed) {
1475
+ this._collapseMember(colIndex, false);
1476
+ } else {
1477
+ this._setColumnVisibility(colIndex, true);
1478
+ }
1479
+ this._updateActiveColumn(stackOption); // This may trigger _updateUI
1480
+ return true;
1481
+ };
1482
+
1483
+ /** @private
1484
+ * @param {Object} stackOption
1485
+ */
1486
+ ColumnStackPlugin.prototype._repositionMembers = function (stackOption) {
1487
+ var children = stackOption.children;
1488
+ var refCount = children ? children.length : 0;
1489
+ if(!refCount) {
1490
+ return;
1491
+ }
1492
+
1493
+ var i;
1494
+ var indexToIds = new Array(refCount);
1495
+ for(i = 0; i < refCount; i++) {
1496
+ var colRef = children[i];
1497
+ indexToIds[i] = [this.getColumnIndex(colRef), colRef];
1498
+ }
1499
+ indexToIds.sort(compareRef);
1500
+ for(i = 0; i < refCount; i++) {
1501
+ children[i] = indexToIds[i][1];
1502
+ }
1503
+ };
1504
+ /** @private
1505
+ * @param {Object} e
1506
+ */
1477
1507
  ColumnStackPlugin.prototype._onStackButtonClicked = function(e) {
1478
1508
  var elem = e.currentTarget;
1479
1509
  if(e.button) { // right click or middle click
@@ -1493,11 +1523,11 @@ ColumnStackPlugin.prototype._onStackButtonClicked = function(e) {
1493
1523
  return;
1494
1524
  }
1495
1525
 
1496
- var stackRefs = colData.stackRefs;
1497
- var activeIndex = stackRefs.indexOf(colData.activeColumn);
1526
+ var children = colData.children;
1527
+ var activeIndex = children.indexOf(colData.activeColumn);
1498
1528
 
1499
- var len = stackRefs.length;
1500
- var colIndices = this._getColumnIndices(stackRefs);
1529
+ var len = children.length;
1530
+ var colIndices = this.getColumnIndices(children);
1501
1531
  var menuData = new Array(len);
1502
1532
  for(var i = len; --i >= 0;) {
1503
1533
  menuData[i] = {
@@ -1510,9 +1540,9 @@ ColumnStackPlugin.prototype._onStackButtonClicked = function(e) {
1510
1540
  pos["menuData"] = menuData;
1511
1541
  pos["activeIndex"] = activeIndex;
1512
1542
  pos["columnIndices"] = colIndices;
1513
- pos["activeColIndex"] = this._getColumnIndex(colData.activeColumn);
1543
+ pos["activeColIndex"] = this.getColumnIndex(colData.activeColumn);
1514
1544
  pos["event"] = e;
1515
- pos["stackId"] = colData.stackId;
1545
+ pos["stackId"] = colData.id;
1516
1546
 
1517
1547
  elem.focus();
1518
1548
  this._dispatch("clicked", pos);
@@ -1546,9 +1576,9 @@ ColumnStackPlugin.prototype._requestStackingByFields = function() {
1546
1576
  */
1547
1577
  ColumnStackPlugin.prototype.getStackMemberIndices = function(stackId) {
1548
1578
  if(stackId) {
1549
- var stack = this._stacks[stackId];
1579
+ var stack = this._groupDefs.getGroup(stackId);
1550
1580
  if(stack){
1551
- return this._getColumnIndices(stack.stackRefs);
1581
+ return this.getColumnIndices(stack.children);
1552
1582
  }
1553
1583
  }
1554
1584
  return [];
@@ -1560,8 +1590,12 @@ ColumnStackPlugin.prototype.getStackMemberIndices = function(stackId) {
1560
1590
  * @return {!Array.<string>} Member column ids
1561
1591
  */
1562
1592
  ColumnStackPlugin.prototype.getStackMemberIds = function(stackId) {
1563
- var memberIndices = this.getStackMemberIndices(stackId);
1564
- return this.getColumnIdsByIndex(memberIndices);
1593
+ var colIndices = this.getStackMemberIndices(stackId);
1594
+ if(colIndices.length) {
1595
+ return colIndices.map(this._toIdOrField);
1596
+ }
1597
+
1598
+ return colIndices;
1565
1599
  };
1566
1600
 
1567
1601
  /** @public
@@ -1621,44 +1655,23 @@ ColumnStackPlugin.prototype.getColumnIdsByFields = function(fields) {
1621
1655
  * @param {string} stackId
1622
1656
  */
1623
1657
  ColumnStackPlugin.prototype.addColumnToStack = function(colRef, stackId) {
1624
- if(typeof colRef === "string") {
1625
- colRef = this.getColumnIndex(colRef);
1626
- if(colRef < 0) {
1627
- return;
1628
- }
1629
- }
1658
+ var colId = this._toIdOrField(colRef);
1630
1659
 
1631
- var stack = this._stacks[stackId];
1632
- var isColumnStackable = this.isColumnStackable([colRef]);
1660
+ var stack = this._groupDefs.getGroup(stackId);
1661
+ var isColumnStackable = !this._groupDefs.getParentGroup(colId);
1633
1662
 
1634
1663
  // if column not stackable or destination stack not exist, do nothing
1635
1664
  if(!isColumnStackable || !stack){
1636
1665
  return;
1637
1666
  }
1638
1667
 
1639
- var isCollapsed = stack.spreading && stack.collapsed;
1640
- if(isCollapsed){
1641
- this.expandGroup(this._getColumnIndex(stack.activeColumn));
1642
- }
1643
-
1644
- // Prevent from flashing in stack mode
1645
- if(stack.collapsed !== false) {
1646
- this._setColumnVisibility(colRef, false);
1647
- }
1668
+ this._hideStackedColumns(stack, this.getColumnIndex(colId));
1648
1669
 
1649
1670
  // apply stacking
1650
- this._setColumnStackOptions(colRef, stack);
1651
- stack.stackRefs.push(this._getUniqueRef(colRef));
1652
-
1653
- if(stack.spreading) {
1654
- stack.activeColumn = stack.stackRefs[stack.stackRefs.length - 1];
1655
- stack.collapsed = isCollapsed;
1656
- } else {
1657
- stack.activeColumn = stack.stackRefs[0];
1658
- }
1671
+ this._groupDefs.addGroupChild(stackId, colId);
1659
1672
 
1660
1673
  // Pack stacked columns together
1661
- this._moveStackedColumns(stack.stackRefs);
1674
+ this._moveStackedColumns(stack.children);
1662
1675
 
1663
1676
  this._updateUI();
1664
1677
  };
@@ -1673,46 +1686,37 @@ ColumnStackPlugin.prototype.removeColumnFromStack = function(colRef) {
1673
1686
  return;
1674
1687
  }
1675
1688
 
1676
- var stackId = this.getStackId(colIndex);
1677
- var stack = this._stacks[stackId];
1689
+ var colId = this.getColumnId(colIndex);
1690
+ var stack = this._groupDefs.getParentGroup(colId);
1691
+ if(stack) {
1692
+ colRef = colId;
1693
+ } else {
1694
+ var field = this._getField(colIndex);
1695
+ stack = this._groupDefs.getParentGroup(field);
1696
+ if(stack) {
1697
+ colRef = field;
1698
+ }
1699
+ }
1700
+
1678
1701
  if(!stack) {
1679
1702
  return;
1680
1703
  }
1681
1704
 
1682
- var stackRefs = stack.stackRefs;
1683
- var memberCount = stackRefs.length;
1705
+ var children = stack.children;
1706
+ var memberCount = children.length;
1684
1707
  if(memberCount <= 2) {
1685
1708
  if(memberCount === 2) {
1686
- var uniqueObj = this._getUniqueRef(colIndex);
1687
- if(uniqueObj === stackRefs[0]) { // If the first column is removed from the stack, move it to the end of stack
1709
+ if(colRef === children[0]) { // If the first column is removed from the stack, move it to the end of stack
1688
1710
  // This assumes that the column order is already in correct position
1689
- this.moveColumnById(colIndex, this._getColumnIndex(stackRefs[1]) + 1);
1711
+ this.moveColumnById(colIndex, this.getColumnIndex(children[1]) + 1);
1690
1712
  }
1691
1713
  }
1692
- this.removeStack(stackId); // updateUI will be called
1714
+ this.removeStack(stack.id); // updateUI will be called
1693
1715
  return;
1694
1716
  }
1695
1717
 
1696
- var colIndices = this.getStackMemberIndices(stackId);
1697
- var memberIdx = colIndices.indexOf(colIndex);
1698
- var isCollapsed = stack.spreading && stack.collapsed;
1699
- if(memberIdx >= 0) {
1700
- if(isCollapsed) {
1701
- this._collapseMember(colIndices[memberIdx], false); // TODO: This may change the column index
1702
- }
1703
- stackRefs.splice(memberIdx, 1); // WARNING: colIndices is out of sync with stackRefs after this line
1704
- }
1705
-
1706
- this._removeColumnStackOptions(colIndex);
1707
- this._setColumnVisibility(colIndex, true);
1708
-
1709
- if(stack.spreading) {
1710
- stack.activeColumn = stackRefs[stackRefs.length - 1];
1711
- stack.collapsed = isCollapsed;
1712
- } else {
1713
- stack.activeColumn = stackRefs[0];
1714
- }
1715
- this._stacks[stackId] = stack; // TODO: This is unnecessary
1718
+ var colIndices = this.getColumnIndices(children);
1719
+ this._removeRefFromStack(stack, colRef, colIndex);
1716
1720
 
1717
1721
  this.moveColumnById(colIndex, colIndices[memberCount - 1] + 1); // This assumes that the column order is already in correct position
1718
1722
  this._updateUI();
@@ -1724,7 +1728,7 @@ ColumnStackPlugin.prototype.removeColumnFromStack = function(colRef) {
1724
1728
  * @param {string} stackId
1725
1729
  */
1726
1730
  ColumnStackPlugin.prototype.reorderStackColumns = function(colRefs, stackId) {
1727
- var stack = this._stacks[stackId];
1731
+ var stack = this._groupDefs.getGroup(stackId);
1728
1732
  if(!stack) {
1729
1733
  return;
1730
1734
  }
@@ -1742,14 +1746,14 @@ ColumnStackPlugin.prototype.reorderStackColumns = function(colRefs, stackId) {
1742
1746
  var i, colIndex;
1743
1747
  for(i = 0; i < len; i++ ){
1744
1748
  colIndex = colRefs[i];
1745
- if(stackMemberIndices.indexOf(colIndex) !== -1){
1749
+ if(stackMemberIndices.indexOf(colIndex) >= 0){
1746
1750
  newStackMembers.push(colIndex);
1747
1751
  }
1748
1752
  }
1749
1753
  if(newStackMembers.length !== stackMemberCount){
1750
1754
  for(i = 0; i < stackMemberCount; i++ ){
1751
1755
  colIndex = stackMemberIndices[i];
1752
- if(newStackMembers.indexOf(colIndex) === -1){
1756
+ if(newStackMembers.indexOf(colIndex) < 0){
1753
1757
  newStackMembers.push(colIndex);
1754
1758
  if(newStackMembers.length === stackMemberCount){
1755
1759
  break;
@@ -1767,9 +1771,8 @@ ColumnStackPlugin.prototype.reorderStackColumns = function(colRefs, stackId) {
1767
1771
  newStackMembers[i] = this._getField(newStackMembers[i]);
1768
1772
  }
1769
1773
 
1770
- this.unstackColumns(stackMemberIndices);
1774
+ this.removeStack(stackId);
1771
1775
  this.stackColumns(newStackMembers, stackId, options);
1772
-
1773
1776
  };
1774
1777
 
1775
1778
  /** @public
@@ -1778,12 +1781,7 @@ ColumnStackPlugin.prototype.reorderStackColumns = function(colRefs, stackId) {
1778
1781
  * @param {string} name
1779
1782
  */
1780
1783
  ColumnStackPlugin.prototype.setStackName = function(stackId, name) {
1781
- if(stackId !== null) {
1782
- var stack = this._stacks[stackId];
1783
- if(stack){
1784
- stack.name = name;
1785
- }
1786
- }
1784
+ this._groupDefs.setGroupName(stackId, name);
1787
1785
  };
1788
1786
 
1789
1787
  /** @public
@@ -1792,14 +1790,7 @@ ColumnStackPlugin.prototype.setStackName = function(stackId, name) {
1792
1790
  * @return {string} Stack name
1793
1791
  */
1794
1792
  ColumnStackPlugin.prototype.getStackName = function(stackId) {
1795
- var stackName = "";
1796
- if(stackId !== null) {
1797
- var stack = this._stacks[stackId];
1798
- if(stack){
1799
- stackName = stack.name;
1800
- }
1801
- }
1802
- return stackName;
1793
+ return this._groupDefs.getGroupName(stackId);
1803
1794
  };
1804
1795
 
1805
1796
  /** @public
@@ -1808,12 +1799,7 @@ ColumnStackPlugin.prototype.getStackName = function(stackId) {
1808
1799
  * @return {string} active column field
1809
1800
  */
1810
1801
  ColumnStackPlugin.prototype.getActiveColumnField = function(stackId) {
1811
- var field = "";
1812
- var activeColIndex = this.getActiveColumnIndex(stackId);
1813
- if(activeColIndex !== -1){
1814
- field = this._getField(activeColIndex);
1815
- }
1816
- return field;
1802
+ return this._getField(this.getActiveColumnIndex(stackId));
1817
1803
  };
1818
1804
 
1819
1805
  /** @public
@@ -1822,14 +1808,11 @@ ColumnStackPlugin.prototype.getActiveColumnField = function(stackId) {
1822
1808
  * @return {number} active column index
1823
1809
  */
1824
1810
  ColumnStackPlugin.prototype.getActiveColumnIndex = function(stackId) {
1825
- var activeColIndex = -1;
1826
- if(stackId !== null) {
1827
- var stack = this._stacks[stackId];
1828
- if(stack){
1829
- activeColIndex = this._getColumnIndex(stack.activeColumn);
1830
- }
1811
+ var stack = this._groupDefs.getGroup(stackId);
1812
+ if(stack){
1813
+ return this.getColumnIndex(stack.activeColumn);
1831
1814
  }
1832
- return activeColIndex;
1815
+ return -1;
1833
1816
  };
1834
1817
  //getActiveColumnIndex
1835
1818
 
@@ -1867,11 +1850,11 @@ ColumnStackPlugin.prototype.unsetParent = ColumnStackPlugin.prototype.removeColu
1867
1850
  */
1868
1851
  ColumnStackPlugin.prototype.reorderColumns = function(colList, destCol) {
1869
1852
  var dirty = false;
1870
- this._stacking = true;
1853
+ this._inReordering = true;
1871
1854
 
1872
1855
  dirty = this._reorderColumns(colList, destCol);
1873
1856
 
1874
- this._stacking = false;
1857
+ this._inReordering = false;
1875
1858
  return dirty;
1876
1859
  };
1877
1860
  /** Move the specified column to position before the destination
@@ -1881,13 +1864,7 @@ ColumnStackPlugin.prototype.reorderColumns = function(colList, destCol) {
1881
1864
  * @return {boolean}
1882
1865
  */
1883
1866
  ColumnStackPlugin.prototype.moveColumnById = function(srcCol, destCol) {
1884
- var dirty = false;
1885
- this._stacking = false;
1886
-
1887
- this._moveColumnById(srcCol, destCol);
1888
-
1889
- this._stacking = true;
1890
- return dirty;
1867
+ return this._moveColumnById(srcCol, destCol);
1891
1868
  };
1892
1869
 
1893
1870
  /** @private
@@ -1896,40 +1873,35 @@ ColumnStackPlugin.prototype.moveColumnById = function(srcCol, destCol) {
1896
1873
  * @param {boolean} visible
1897
1874
  */
1898
1875
  ColumnStackPlugin.prototype._setStackVisibility = function(stackId, visible) {
1899
- var stackOption = this._stacks[stackId];
1876
+ var stackOption = this._groupDefs.getGroup(stackId);
1900
1877
  if(!stackOption){
1901
1878
  return;
1902
1879
  }
1903
1880
 
1904
- if(stackOption["spreading"] && !stackOption["collapsed"]){
1905
- var stackRefs = stackOption["stackRefs"];
1906
- for(var i = 0; i < stackRefs.length; i++){
1907
- var colIndex = this._getColumnIndex(stackRefs[i]);
1908
- this._setColumnVisibility(colIndex, visible);
1881
+ if(stackOption.spreading && !stackOption.collapsed){
1882
+ var children = stackOption.children;
1883
+ for(var i = 0; i < children.length; i++){
1884
+ this._setColumnVisibility(this.getColumnIndex(children[i]), visible);
1909
1885
  }
1910
1886
  } else {
1911
- var activeColIndex = this._getColumnIndex(stackOption.activeColumn);
1887
+ var activeColIndex = this.getColumnIndex(stackOption.activeColumn);
1912
1888
  this._setColumnVisibility(activeColIndex, visible);
1913
1889
  }
1914
1890
  };
1915
1891
 
1892
+
1916
1893
  /** @private
1917
1894
  * @description Check for active column in a stack if it needs an update.
1918
1895
  * @param {Object} stackOpt
1919
1896
  */
1920
1897
  ColumnStackPlugin.prototype._updateActiveColumn = function(stackOpt) {
1921
- if(!stackOpt) { return; }
1922
-
1923
- var stackRefs = stackOpt["stackRefs"];
1924
- if(!stackRefs || !stackRefs.length) { return; }
1925
-
1926
- if(stackRefs.indexOf(stackOpt.activeColumn) === -1) {
1927
- stackOpt.activeColumn = stackOpt["spreading"] ? stackRefs[stackRefs.length - 1] : stackRefs[0];
1928
-
1898
+ if(_resolveActiveColumn(stackOpt)) {
1929
1899
  // TODO: Add a proper way to set visibility to activeColumn when activeColumn is changed
1930
- var activeColIndex = this._getColumnIndex(stackOpt.activeColumn);
1931
- this._setColumnVisibility(activeColIndex, true);
1932
- this._updateUI();
1900
+ var activeColIndex = this.getColumnIndex(stackOpt.activeColumn);
1901
+ if(activeColIndex >= 0) {
1902
+ this._setColumnVisibility(activeColIndex, true);
1903
+ this._updateUI();
1904
+ }
1933
1905
  }
1934
1906
  };
1935
1907
 
@@ -1961,14 +1933,14 @@ ColumnStackPlugin.prototype.showStack = function(stackId) {
1961
1933
  * @return {boolean|null}
1962
1934
  */
1963
1935
  ColumnStackPlugin.prototype.isStackHidden = function(stackId) {
1964
- var stackOption = this._stacks[stackId];
1936
+ var stackOption = this._groupDefs.getGroup(stackId);
1965
1937
  var host = this._host || this._hosts[0];
1966
1938
 
1967
1939
  if(!stackOption || !host){
1968
1940
  return null;
1969
1941
  }
1970
1942
 
1971
- var activeColIndex = this._getColumnIndex(stackOption.activeColumn);
1943
+ var activeColIndex = this.getColumnIndex(stackOption.activeColumn);
1972
1944
  var isVisible = host.isColumnVisible(activeColIndex);
1973
1945
 
1974
1946
  return !isVisible;
@@ -1983,7 +1955,7 @@ ColumnStackPlugin.prototype.moveStack = function(stackId, destCol) {
1983
1955
  if(!stackId){
1984
1956
  return false;
1985
1957
  }
1986
- var colList = this.getStackMemberIds(stackId);
1958
+ var colList = this.getStackMemberIds(stackId); // WARNING: column ids or fields
1987
1959
  var dirty = this.reorderColumns(colList, destCol);
1988
1960
  return dirty;
1989
1961
  };