@refinitiv-ui/efx-grid 6.0.53 → 6.0.55

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.
@@ -8,6 +8,8 @@ declare namespace FieldDefinition {
8
8
 
9
9
  function get(field: string): any;
10
10
 
11
+ function getFieldInfo(field: string): any;
12
+
11
13
  function hasFieldInfo(field: string): boolean;
12
14
 
13
15
  function getTimeSeriesChildren(field: string): any;
@@ -18,8 +20,6 @@ declare namespace FieldDefinition {
18
20
 
19
21
  function setSynapseConfig(config: Grid.SynapseConfig|null): void;
20
22
 
21
- function setFieldCaching(caching: boolean): void;
22
-
23
23
  function disableTimeSeriesExpansion(disabled: boolean): void;
24
24
 
25
25
  function isFormula(field: string): boolean;
@@ -10,9 +10,9 @@ import { Deferred } from "../../tr-grid-util/es6/Deferred.js";
10
10
  * @const
11
11
  */
12
12
  var SYNAPSE_URL =
13
- '/synapse/service/suggestions/suggest/?'
14
- + 'hits=1' // search only 1 result
15
- + '&profile=' + encodeURIComponent('Field Selector');
13
+ "/synapse/service/suggestions/suggest/?"
14
+ + "hits=1" // search only 1 result
15
+ + "&profile=" + encodeURIComponent("Field Selector");
16
16
 
17
17
  /* @namespace */
18
18
  var FieldDefinition = {};
@@ -129,18 +129,13 @@ FieldDefinition._timeSeriesChildren = {};
129
129
  * @private
130
130
  * @const
131
131
  */
132
- FieldDefinition._synapse = '';
132
+ FieldDefinition._synapse = "";
133
133
  /**
134
134
  * @type {string}
135
135
  * @private
136
136
  * @const
137
137
  */
138
- FieldDefinition._lang = 'en';
139
- /**
140
- * @type {boolean}
141
- * @private
142
- */
143
- FieldDefinition._caching = false;
138
+ FieldDefinition._lang = "en";
144
139
  /**
145
140
  * @type {boolean}
146
141
  * @private
@@ -167,17 +162,22 @@ FieldDefinition.set = function(field, def) {
167
162
  }
168
163
  }
169
164
  };
170
- /** @public
165
+ /** Get field definition object
166
+ * @public
171
167
  * @function
172
168
  * @param {string} field
173
169
  * @return {Object}
174
170
  */
175
171
  FieldDefinition.get = function(field) {
176
- if(this._caching) {
177
- return FieldDefinition._defs[field];
178
- }
179
- return null;
172
+ return FieldDefinition._defs[field] || null;
180
173
  };
174
+ /** Get field definition object
175
+ * @public
176
+ * @function
177
+ * @param {string} field
178
+ * @return {Object}
179
+ */
180
+ FieldDefinition.getFieldInfo = FieldDefinition.get;
181
181
 
182
182
  /** @public
183
183
  * @function
@@ -186,7 +186,7 @@ FieldDefinition.get = function(field) {
186
186
  */
187
187
  FieldDefinition.hasFieldInfo = function(field) {
188
188
  var val = FieldDefinition.get(field);
189
- return val && val.field; // Already preventing an error caused by accessing a property on a null value
189
+ return (val && val.field) ? true : false; // Already preventing an error caused by accessing a property on a null value
190
190
  };
191
191
 
192
192
  /** @public
@@ -222,15 +222,6 @@ FieldDefinition.remove = function(field) {
222
222
  */
223
223
  FieldDefinition.setSynapseConfig = function (config) {
224
224
  FieldDefinition._synapse = config;
225
-
226
- };
227
-
228
- /** @public
229
- * @function
230
- * @param {boolean} caching
231
- */
232
- FieldDefinition.setFieldCaching = function (caching) {
233
- FieldDefinition._caching = caching;
234
225
  };
235
226
 
236
227
  /** @public
@@ -355,7 +346,7 @@ FieldDefinition.getFieldProperty = function(field, propertyName) {
355
346
  return null;
356
347
  };
357
348
 
358
- /** to get more info about field via synapse service
349
+ /** To get more info about field via synapse service
359
350
  * @private
360
351
  * @param {string} field
361
352
  * @returns {Promise}
@@ -387,12 +378,12 @@ FieldDefinition.loadFieldInfo = function (field) {
387
378
  // everything fine, call synapse service
388
379
  else {
389
380
  var queryUrl = SYNAPSE_URL
390
- + '&api-key=' + encodeURIComponent(synapse.apiKey)
391
- + '&contextApp=' + encodeURIComponent(synapse.contextApp)
392
- + '&language=' + encodeURIComponent(FieldDefinition._lang)
393
- + '&query=' + encodeURIComponent(field);
381
+ + "&api-key=" + encodeURIComponent(synapse.apiKey)
382
+ + "&contextApp=" + encodeURIComponent(synapse.contextApp)
383
+ + "&language=" + encodeURIComponent(FieldDefinition._lang)
384
+ + "&query=" + encodeURIComponent(field);
394
385
  if (synapse.auth) {
395
- queryUrl += '&auth=' + encodeURIComponent(synapse.auth);
386
+ queryUrl += "&auth=" + encodeURIComponent(synapse.auth);
396
387
  }
397
388
  // TODO: handle client timeout
398
389
  var xmr = new XMLHttpRequest();
@@ -401,9 +392,9 @@ FieldDefinition.loadFieldInfo = function (field) {
401
392
  FieldDefinition._loadingField[field] = defer;
402
393
 
403
394
  // for now we care only successful case
404
- xmr.addEventListener('loadend', onLoadEnd);
405
- xmr.addEventListener('timeout', onError);
406
- xmr.addEventListener('error', onError);
395
+ xmr.addEventListener("loadend", onLoadEnd);
396
+ xmr.addEventListener("timeout", onError);
397
+ xmr.addEventListener("error", onError);
407
398
  xmr.open("GET", queryUrl, true); // true for asynchronous
408
399
  xmr.send();
409
400
  }
@@ -431,7 +422,7 @@ FieldDefinition._mockOnLoadEndData = function (field, defer) {
431
422
  var fieldUpperCase = field.toUpperCase();
432
423
  var fieldLowerCase = fieldUpperCase.toLowerCase();
433
424
  var label;
434
- if (fieldLowerCase.indexOf('_') === 2) { // transform XX_ABCD -> Abcd
425
+ if (fieldLowerCase.indexOf("_") === 2) { // transform XX_ABCD -> Abcd
435
426
  label = fieldUpperCase[3] + fieldLowerCase.substring(4);
436
427
  } else {
437
428
  label = fieldUpperCase[0] + fieldLowerCase.substring(1);
@@ -493,49 +484,49 @@ FieldDefinition._mockOnLoadEndData = function (field, defer) {
493
484
  function onLoadEnd(e) {
494
485
  var xmr = e.currentTarget;
495
486
  var defer = xmr._defer;
496
- var resolve = defer.resolve;
497
- var reject = defer.reject;
487
+ var field = xmr._field;
498
488
 
499
- delete FieldDefinition._loadingField[xmr._field];
489
+ delete FieldDefinition._loadingField[field];
500
490
 
501
491
  if (xmr.status === 200) { // case success
502
- var returnObj = {
503
- field: xmr._field,
504
- fieldDefinition: null
505
- };
506
492
  var res = JSON.parse(xmr.responseText);
493
+ var fieldDef = null;
507
494
 
508
495
  var result = res.result && res.result[0];
509
496
  if (result) {
510
497
  var item = result.hits && result.hits[0];
511
498
  if (item && item.p) {
512
499
  var profile = item.p;
513
- if (profile.fn.toUpperCase() === xmr._field.toUpperCase()) {
514
- var fieldDef = FieldDefinition.get(xmr._field) || {};
500
+ if (profile.fn.toUpperCase() === field.toUpperCase()) {
501
+ fieldDef = FieldDefinition.get(field) || {};
515
502
 
516
503
  fieldDef.field = profile.fn; // name
504
+ fieldDef.name = profile.fl; // label
517
505
  fieldDef.rank = profile.Rank;
518
506
  fieldDef.fieldDataType = profile.fdt; // data type
507
+ fieldDef.id = profile.fid;
519
508
  fieldDef.IsMonitorOnlyField = profile.fimo;
520
509
  fieldDef.IsRealtimePortfolioField = profile.firp;
521
510
  fieldDef.IsRealtimeField = profile.firt;
522
- fieldDef.name = profile.fl; // label
523
511
  fieldDef.source = profile.fsrc;
524
512
  fieldDef.sourceRank = profile.fsrnk;
525
513
  fieldDef.description = item.title;
526
514
 
527
- FieldDefinition.set(xmr._field, fieldDef);
528
- returnObj.fieldDefinition = fieldDef;
515
+ FieldDefinition.set(field, fieldDef);
529
516
  }
530
517
  }
531
518
  }
532
- resolve(returnObj);
519
+ if(fieldDef) {
520
+ defer.resolve(fieldDef);
521
+ } else {
522
+ defer.reject("No definition for " + field);
523
+ }
533
524
  } else if (xmr._error) { // case error && time out
534
- xmr._error.field = xmr._field;
535
- reject(xmr._error);
525
+ xmr._error.field = field;
526
+ defer.reject(xmr._error);
536
527
  } else { // case status not 200
537
- reject({
538
- field: xmr._field,
528
+ defer.reject({
529
+ field: field,
539
530
  status: xmr.status,
540
531
  statusText: xmr.statusText,
541
532
  request: xmr
@@ -133,9 +133,13 @@ declare class Grid extends EventDispatcher {
133
133
 
134
134
  public replaceColumn(columnOption: ColumnDefinition.Options|string|null, colRef: Grid.ColumnReference|null): void;
135
135
 
136
+ public getFieldInfo(field: string): any;
137
+
138
+ public loadFieldInfo(field: string): Promise<any>|null;
139
+
136
140
  public setColumns(columns: (any)[]|null): void;
137
141
 
138
- public restoreColumns(columns: (any)[]|null): void;
142
+ public restoreColumns(columns: (any)[]|null, byId?: boolean|null): void;
139
143
 
140
144
  public setFields(ary: (string)[]|null): void;
141
145
 
@@ -198,15 +198,6 @@ var cloneRowData = function(fromRowDef, toRowDef) {
198
198
  }
199
199
  };
200
200
 
201
- /** @private
202
- * @param {string} sortField
203
- * @param {Object} elemData
204
- * @param {number} index
205
- */
206
- var mapRowOrder = function (sortField, elemData, index) { // edit name
207
- elemData[sortField] = index; // Make column for sort with user data array
208
- };
209
-
210
201
  /** @private
211
202
  * @param {RowDefinition} rowDef
212
203
  * @return {boolean}
@@ -291,6 +282,19 @@ var _hasFieldOrId = function(colDef, str) {
291
282
  return (colDef.getField() === str) || (colDef.getId() === str);
292
283
  };
293
284
 
285
+ /** Compare the difference in the 'id' property.
286
+ * @private
287
+ * @param {Object} obj1
288
+ * @param {Object} obj2
289
+ * @returns {boolean} If the id property of two objects is equal, the return will be true, otherwise it will be false.
290
+ */
291
+ var _hasMatchingId = function(obj1, obj2) {
292
+ if(!obj1 || !obj2 || !obj1.id || !obj2.id) { // Handle nullable, if the object or id have null, it's means difference value
293
+ return false;
294
+ }
295
+ return obj1.id === obj2.id;
296
+ };
297
+
294
298
  /** @constructor
295
299
  * @extends {EventDispatcher}
296
300
  * @param {(Element|null)=} placeholder
@@ -840,7 +844,6 @@ Grid.prototype.initialize = function(gridOption) {
840
844
 
841
845
  if (gridOption["fieldCaching"]) {
842
846
  t._fieldCaching = gridOption["fieldCaching"];
843
- FieldDefinition.setFieldCaching(t._fieldCaching);
844
847
  }
845
848
 
846
849
  if(gridOption["timeSeriesExpansion"] != null) {
@@ -1590,8 +1593,8 @@ Grid.prototype.replaceColumn = function (columnOption, colRef) {
1590
1593
  * @param {Object} response
1591
1594
  */
1592
1595
  Grid.prototype._onFieldLoadedSuccess = function (field, colDef, response) {
1593
- if (response && response.fieldDefinition) {
1594
- var fieldDef = response.fieldDefinition;
1596
+ if (response && response.id) {
1597
+ var fieldDef = response;
1595
1598
  if (colDef && colDef.getField() === field) {
1596
1599
  if (colDef.isDefaultName() && fieldDef.name) {
1597
1600
  colDef.setName(fieldDef.name);
@@ -1639,6 +1642,34 @@ Grid.prototype._setScrollbarParent = function (host) {
1639
1642
  this._grid.getHScrollbar().attachToExternalElement(host);
1640
1643
  };
1641
1644
 
1645
+ /** Get stored field information. If field information has not been requested or no data has been received yet, null value is returned.
1646
+ * @public
1647
+ * @function
1648
+ * @param {string} field
1649
+ * @return {Object}
1650
+ */
1651
+ Grid.prototype.getFieldInfo = function(field) {
1652
+ return FieldDefinition.getFieldInfo(field);
1653
+ };
1654
+ /** Request field information from Synapse service. If field information already exists, a resolved promise is returned. Synapse config must be supplied before the request can be made.
1655
+ * @public
1656
+ * @function
1657
+ * @param {string} field
1658
+ * @return {Promise}
1659
+ * @example
1660
+ * var gridConfig = {
1661
+ * synapse: { // define synapse configuration
1662
+ * apiKey: "xxx",
1663
+ * contextApp: "xxx",
1664
+ * auth: "xxx" (optional)
1665
+ * }
1666
+ * };
1667
+ * var promise = grid.loadFieldInfo("CF_LAST");
1668
+ */
1669
+ Grid.prototype.loadFieldInfo = function(field) {
1670
+ return FieldDefinition.loadFieldInfo(field);
1671
+ };
1672
+
1642
1673
  /**
1643
1674
  * @private
1644
1675
  * @param {string} field
@@ -1697,8 +1728,9 @@ Grid.prototype.setColumns = function(columns) {
1697
1728
  /** Remove, add and keep column based on the given column data
1698
1729
  * @public
1699
1730
  * @param {Array.<Object>} columns Array of column options
1731
+ * @param {boolean=} byId=false, if enable it, this method will only check for differences in the 'id' property
1700
1732
  */
1701
- Grid.prototype.restoreColumns = function(columns) {
1733
+ Grid.prototype.restoreColumns = function(columns, byId) {
1702
1734
  var grid = this._grid;
1703
1735
  grid.startBatch("reset");
1704
1736
  var configObj = this.getConfigObject();
@@ -1707,6 +1739,7 @@ Grid.prototype.restoreColumns = function(columns) {
1707
1739
  var preColLen = previousColumns.length;
1708
1740
  var newColLen = columns.length;
1709
1741
 
1742
+ var compareLogic = byId ? _hasMatchingId : deepEqual;
1710
1743
  var removingFields = [];
1711
1744
  var keepingColumns = [];
1712
1745
  var columnOrdering = [];
@@ -1716,7 +1749,7 @@ Grid.prototype.restoreColumns = function(columns) {
1716
1749
  for (i = 0; i < preColLen; i++) {
1717
1750
  found = false;
1718
1751
  for (j = 0; j < newColLen; j++) {
1719
- if (deepEqual(previousColumns[i], columns[j])) {
1752
+ if(compareLogic(previousColumns[i], columns[j])) {
1720
1753
  keepingColumns.push(previousColumns[i]);
1721
1754
  found = true;
1722
1755
  break;
@@ -1737,7 +1770,7 @@ Grid.prototype.restoreColumns = function(columns) {
1737
1770
  for (i = 0; i < newColLen; i++) {
1738
1771
  found = false;
1739
1772
  for (j = 0; j < keepingLen; j++) { // loop only keeping column
1740
- if (deepEqual(columns[i], keepingColumns[j])) {
1773
+ if(compareLogic(columns[i], keepingColumns[j])) {
1741
1774
  found = true;
1742
1775
  var colIndex = this.getColumnIndex(columns[i].field); // We cannot use 'i' (colIndex) in this case, as it will sort the columns. Instead, we need to obtain a new column index from the field.
1743
1776
  columnOrdering.push(this.getColumnId(colIndex));
@@ -2416,20 +2449,11 @@ Grid.prototype.updateDataSet = function(records, rowIdentifier) {
2416
2449
  }
2417
2450
 
2418
2451
  // Map new data index
2419
- var newDataMap = {};
2420
2452
  var recordCount = records.length;
2421
- var record, i;
2422
- for (i = 0; i < recordCount; i++) {
2423
- record = records[i];
2424
- newDataMap[record[rowIdentifier]] = record; // Assign a new data map to compare to the previous data
2425
- }
2426
-
2427
2453
  var fieldSorting = "ROW_ORDER"; // TODO: Should be config by options
2428
- records.forEach(mapRowOrder.bind(null, fieldSorting));
2429
-
2430
2454
  var oldDataMap = {};
2431
- var rowDef, id;
2432
- var rowDefs = this.getRowDefinitions(); // WARNING: Filtered and hidden rows are not included
2455
+ var rowDef, id, record, i;
2456
+ var rowDefs = this.getAllRowDefinitions(); // Include the filter/hidden rows
2433
2457
  var rowDefCount = rowDefs.length;
2434
2458
  for (i = 0; i < rowDefCount; i++) {
2435
2459
  rowDef = rowDefs[i];
@@ -2443,26 +2467,35 @@ Grid.prototype.updateDataSet = function(records, rowIdentifier) {
2443
2467
  }
2444
2468
  }
2445
2469
 
2446
- // Check Remove previous data
2447
- for (var rowIdentifierName in oldDataMap) {
2448
- if (oldDataMap[rowIdentifierName] && !newDataMap[rowIdentifierName]) {
2449
- this.removeRow(oldDataMap[rowIdentifierName]); // Slow
2450
- }
2451
- }
2452
-
2453
2470
  // Check Update and Insert
2454
- for (i = 0; i < recordCount; i++) {
2471
+ var idMap = {};
2472
+ var newDataMap = {};
2473
+ for (i = recordCount - 1; i >= 0; i--) {
2455
2474
  record = records[i];
2456
2475
  id = record[rowIdentifier];
2457
2476
  rowDef = oldDataMap[id];
2477
+ newDataMap[id] = record; // Assign a new data map to compare to the previous data
2478
+ record[fieldSorting] = i;
2479
+ if(idMap[id]) { // Prevent assign data in duplicate row
2480
+ continue;
2481
+ }
2482
+ idMap[id] = true;
2458
2483
  if (!rowDef) {
2459
- this.insertRow({ values: newDataMap[id]}); // Insert last position
2484
+ this.insertRow({ values: record}); // Insert last position
2460
2485
  } else {
2461
2486
  rowDef.setRowData(record);
2462
2487
  }
2463
2488
  }
2489
+
2490
+ // Check Remove previous data
2491
+ for (var rowIdentifierName in oldDataMap) {
2492
+ if (oldDataMap[rowIdentifierName] && !newDataMap[rowIdentifierName]) {
2493
+ this.removeRow(oldDataMap[rowIdentifierName]); // Slow
2494
+ }
2495
+ }
2496
+
2464
2497
  // Sorting
2465
- this._dt.sortOnce("ROW_DEF", "a", compareNumber, fieldSorting);
2498
+ this._dt.sortOnce(ROW_DEF, "a", compareNumber, fieldSorting);
2466
2499
  };
2467
2500
  /** @private
2468
2501
  * @param {Array|Object} item
@@ -702,7 +702,7 @@ StatisticsRowPlugin.prototype._onPostSectionDataBinding = function (e) {
702
702
  }
703
703
  // Render stuff
704
704
  for(c = 0; c < colCount; ++c) {
705
- this._renderStatistics(c, statistics[c], e.actualUpdate);
705
+ this._renderStatistics(c, statistics[c], e ? e.actualUpdate : false);
706
706
  }
707
707
 
708
708
  if(this.hasListener("postRendering")) {
@@ -4,22 +4,22 @@ import { GridPlugin } from "../../tr-grid-util/es6/GridPlugin.js";
4
4
  declare namespace AutoTooltipPlugin {
5
5
 
6
6
  type Options = {
7
- header?: boolean,
8
- title?: boolean,
9
- footer?: boolean,
10
- content?: boolean,
11
- quickMode?: boolean
7
+ header?: boolean|null,
8
+ title?: boolean|null,
9
+ footer?: boolean|null,
10
+ content?: boolean|null,
11
+ quickMode?: boolean|null
12
12
  };
13
13
 
14
14
  type ColumnOptions = {
15
- autoTooltip?: boolean
15
+ autoTooltip?: boolean|null
16
16
  };
17
17
 
18
18
  }
19
19
 
20
20
  declare class AutoTooltipPlugin extends GridPlugin {
21
21
 
22
- constructor(options?: AutoTooltipPlugin.Options);
22
+ constructor(options?: AutoTooltipPlugin.Options|null);
23
23
 
24
24
  public getName(): string;
25
25
 
@@ -31,9 +31,9 @@ declare class AutoTooltipPlugin extends GridPlugin {
31
31
 
32
32
  public getConfigObject(gridOptions?: any): any;
33
33
 
34
- public applyTooltip(colIndex: number, fromR?: number, toR?: number): void;
34
+ public applyTooltip(colIndex: number, fromR?: number|null, toR?: number|null): void;
35
35
 
36
- public applyTooltipToColumns(colIndices?: (number)[]): void;
36
+ public applyTooltipToColumns(colIndices?: (number)[]|null): void;
37
37
 
38
38
  public applyTooltipToAllColumns(): void;
39
39
 
@@ -279,12 +279,14 @@ AutoTooltipPlugin.prototype.applyTooltipToAllColumns = function () {
279
279
  AutoTooltipPlugin.prototype._applyTooltipToSection = function (section, colIndex, fromR, toR) {
280
280
  if (!section) { return false; }
281
281
 
282
- if (!fromR) {
282
+ if (fromR == null) {
283
+ fromR = section.getFirstIndexInView();
284
+ } else if(!fromR) {
283
285
  fromR = 0;
284
286
  }
285
287
 
286
288
  if (toR == null) {
287
- toR = section.getRowCount();
289
+ toR = section.getLastIndexInView() + 1;
288
290
  }
289
291
 
290
292
  if (fromR >= toR) {
@@ -296,6 +298,10 @@ AutoTooltipPlugin.prototype._applyTooltipToSection = function (section, colIndex
296
298
  return false;
297
299
  }
298
300
 
301
+ if(columnObj.getVisibility && !columnObj.getVisibility()) {
302
+ return false;
303
+ }
304
+
299
305
  var colElem = columnObj.getElement();
300
306
  if (!colElem) {
301
307
  return false;
@@ -93,6 +93,8 @@ declare class ColumnGroupingPlugin extends GridPlugin {
93
93
 
94
94
  public unpinGroup(groupId: string, dest?: (number|string)|null): void;
95
95
 
96
+ public selectGroupHeaders(colIndices: (number)[]|null): void;
97
+
96
98
  public static getObjectIndex(column: any): number;
97
99
 
98
100
  public static getObjectId(column: any): string;
@@ -71,6 +71,10 @@ ColumnGroupingPlugin.prototype._rerenderTimerId = 0;
71
71
  /** @type {number}
72
72
  * @private
73
73
  */
74
+ ColumnGroupingPlugin.prototype._groupHeaderTimerId = 0;
75
+ /** @type {number}
76
+ * @private
77
+ */
74
78
  ColumnGroupingPlugin.prototype._addingTimerId = 0;
75
79
  /** @type {Array}
76
80
  * @private
@@ -84,6 +88,11 @@ ColumnGroupingPlugin.prototype._legacyColumnGroups = null;
84
88
  * @private
85
89
  */
86
90
  ColumnGroupingPlugin.prototype._inPinning = false;
91
+ /** @type {!Object.<string, boolean>}
92
+ * @description A map of selected groups
93
+ * @private
94
+ */
95
+ ColumnGroupingPlugin.prototype._selectedColMap;
87
96
 
88
97
  /** @private
89
98
  * @param {number} a
@@ -298,6 +307,10 @@ ColumnGroupingPlugin.prototype.unload = function (host) {
298
307
  clearTimeout(this._rerenderTimerId);
299
308
  this._rerenderTimerId = 0;
300
309
  }
310
+ if (this._groupHeaderTimerId) {
311
+ clearTimeout(this._groupHeaderTimerId);
312
+ this._groupHeaderTimerId = 0;
313
+ }
301
314
  if (this._addingTimerId) {
302
315
  clearTimeout(this._addingTimerId);
303
316
  this._addingTimerId = 0;
@@ -485,6 +498,7 @@ ColumnGroupingPlugin.prototype._applyGrouping = function () {
485
498
  if (cell) {
486
499
  // TODO: The style and class from the user will not be reset, for example, the color or background
487
500
  cell.removeClass("no-sort");
501
+ cell.removeClass("selected-group");
488
502
  cell.removeAttribute("group-id");
489
503
  }
490
504
  }
@@ -655,6 +669,14 @@ ColumnGroupingPlugin.prototype._requestApplyGrouping = function () {
655
669
  this._rerenderTimerId = setTimeout(this._applyGrouping.bind(this), 10);
656
670
  }
657
671
  };
672
+ /** Set a timer to call renderGroups only once to avoid performance issue
673
+ * @private
674
+ */
675
+ ColumnGroupingPlugin.prototype._requestGroupRendering = function () {
676
+ if (!this._groupHeaderTimerId) {
677
+ this._groupHeaderTimerId = setTimeout(this.renderGroups.bind(this), 10);
678
+ }
679
+ };
658
680
  /** Apply nearest grouping to column.
659
681
  * @private
660
682
  * @param {number} colIndex
@@ -753,11 +775,16 @@ ColumnGroupingPlugin.prototype._spanGroupHorizontally = function (titleSection)
753
775
  spanIndices = [];
754
776
  groupDef = visibleGroupMap[id];
755
777
  colIds = GroupDefinitions.getLeafDescendants(visibleGroupMap, id);
778
+ var isAllSelected = this._selectedColMap ? true : false;
756
779
  for (i = 0; i < colIds.length; i++) {
757
- index = this._getColumnIndexById(colIds[i]);
780
+ var colId = colIds[i];
781
+ index = this._getColumnIndexById(colId);
758
782
  if (index > -1) {
759
783
  spanIndices.push(index);
760
784
  }
785
+ if (isAllSelected && this._selectedColMap) {
786
+ isAllSelected &= this._selectedColMap[colId];
787
+ }
761
788
  }
762
789
  spanIndices.sort(ColumnGroupingPlugin._ascSortLogic);
763
790
  len = spanIndices.length;
@@ -767,6 +794,10 @@ ColumnGroupingPlugin.prototype._spanGroupHorizontally = function (titleSection)
767
794
  var end = spanIndices[len - 1];
768
795
  section.setCellColSpan(start, groupDef["onRow"], end - start + 1);
769
796
  groupDef["colIndex"] = start;
797
+ if (isAllSelected) {
798
+ var cell = section.getCell(start, groupDef["onRow"]);
799
+ cell.addClass("selected-group");
800
+ }
770
801
  }
771
802
  }
772
803
  };
@@ -774,6 +805,7 @@ ColumnGroupingPlugin.prototype._spanGroupHorizontally = function (titleSection)
774
805
  * @public
775
806
  */
776
807
  ColumnGroupingPlugin.prototype.renderGroups = function () {
808
+ this._groupHeaderTimerId = 0;
777
809
  var hostCount = this._hosts.length;
778
810
  var groupMap = this._visibleGroupMap;
779
811
  for (var i = 0; i < hostCount; ++i) {
@@ -798,6 +830,17 @@ ColumnGroupingPlugin.prototype._renderGroup = function (groupDef, section) {
798
830
  }
799
831
  var rowIndex = groupDef["onRow"];
800
832
  var cell = section.getCell(colIndex, rowIndex);
833
+ var colIds = GroupDefinitions.getLeafDescendants(this._visibleGroupMap, groupDef["id"]);
834
+ var isAllSelected = false;
835
+ if (this._selectedColMap) {
836
+ isAllSelected = true;
837
+ for (var i = 0; i < colIds.length; i++) {
838
+ if (!this._selectedColMap[colIds[i]]) {
839
+ isAllSelected = false;
840
+ break;
841
+ }
842
+ }
843
+ }
801
844
  if (cell) {
802
845
  // Overide the defaults
803
846
  cell.setStyle("text-align", groupDef["alignment"] || "");
@@ -807,6 +850,11 @@ ColumnGroupingPlugin.prototype._renderGroup = function (groupDef, section) {
807
850
  // Additional cell settings must be removed in the _applyGrouping() method
808
851
  cell.addClass("no-sort");
809
852
  cell.setAttribute("group-id", groupDef["id"]);
853
+ if (isAllSelected) {
854
+ cell.addClass("selected-group");
855
+ } else {
856
+ cell.removeClass("selected-group");
857
+ }
810
858
  if (groupDef["legacyRender"]) {
811
859
  // Built-in version if render receive colIndex, cell, groupDefinition as arguments
812
860
  groupDef["legacyRender"](colIndex, cell, groupDef);
@@ -1710,6 +1758,26 @@ ColumnGroupingPlugin.prototype.unpinGroup = function (groupId, dest) {
1710
1758
  this.moveGroup(groupId, destId);
1711
1759
  }
1712
1760
  };
1761
+ /** @public
1762
+ * @param {Array<number>} colIndices
1763
+ */
1764
+ ColumnGroupingPlugin.prototype.selectGroupHeaders = function (colIndices) {
1765
+ var host = this._hosts[0];
1766
+ var count = colIndices.length;
1767
+ if (host && count) {
1768
+ var colIds = [];
1769
+ for (var i = 0; i < count; i++) {
1770
+ colIds.push(host.getColumnId(colIndices[i]));
1771
+ }
1772
+ var colMap = host.createColumnMap(colIds);
1773
+ if (colMap) {
1774
+ this._selectedColMap = colMap;
1775
+ }
1776
+ } else {
1777
+ this._selectedColMap = null;
1778
+ }
1779
+ this._requestGroupRendering();
1780
+ };
1713
1781
 
1714
1782
  /** Get column index from column object
1715
1783
  * @public