@refinitiv-ui/efx-grid 6.0.123 → 6.0.124
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.
- package/lib/core/dist/core.js +33 -25
- package/lib/core/dist/core.min.js +1 -1
- package/lib/core/es6/grid/Core.js +33 -25
- package/lib/filter-dialog/lib/filter-dialog.js +8 -1
- package/lib/grid/index.js +1 -1
- package/lib/row-segmenting/es6/RowSegmenting.d.ts +4 -1
- package/lib/row-segmenting/es6/RowSegmenting.js +95 -5
- package/lib/tr-grid-checkbox/es6/Checkbox.js +3 -1
- package/lib/tr-grid-row-filtering/es6/RowFiltering.js +10 -1
- package/lib/types/es6/ConditionalColoring.d.ts +1 -1
- package/lib/versions.json +4 -4
- package/package.json +1 -1
@@ -621,7 +621,7 @@ Core.prototype._hasPendingRowChange = false;
|
|
621
621
|
* @return {string}
|
622
622
|
*/
|
623
623
|
Core.getVersion = function () {
|
624
|
-
return "5.1.
|
624
|
+
return "5.1.124";
|
625
625
|
};
|
626
626
|
/** {@link ElementWrapper#dispose}
|
627
627
|
* @override
|
@@ -3687,37 +3687,37 @@ Core.prototype.getYScrollVal = function (sectionRef, rowIndex, topOfView) {
|
|
3687
3687
|
section = this.getSection(sectionRef);
|
3688
3688
|
}
|
3689
3689
|
|
3690
|
+
let rowIndexOffset = (section) ? section.getRowOffset() : this._sectionStarts[this._startVScrollbarIndex];
|
3691
|
+
let heightOffset = this._layoutY.getLaneStart(this._startVScrollbarIndex);
|
3692
|
+
|
3690
3693
|
let rowCount = this._layoutY.getLaneCount();
|
3691
3694
|
if (rowIndex <= 0) { rowIndex = 0; }
|
3692
3695
|
else if (rowIndex >= rowCount) { rowIndex = rowCount - 1; }
|
3693
3696
|
|
3697
|
+
if (topOfView) {
|
3698
|
+
return this._layoutY.getLaneStart(rowIndex + rowIndexOffset) - heightOffset;
|
3699
|
+
}
|
3700
|
+
|
3701
|
+
let bufferSize = 2; // for more appealing space
|
3694
3702
|
let viewInfo = this.getVerticalViewInfo();
|
3695
3703
|
let firstFullRow = viewInfo.firstFullRow; // TODO: Make it work in zooming mode
|
3696
|
-
|
3697
|
-
|
3698
|
-
|
3699
|
-
|
3700
|
-
} else {
|
3701
|
-
if(rowIndex < firstFullRow) { // Scroll up
|
3702
|
-
scrollIndex = rowIndex - 2; // Have some spaces at the top for more appealing visual
|
3703
|
-
if(scrollIndex < 0) {
|
3704
|
-
scrollIndex = 0;
|
3705
|
-
}
|
3706
|
-
} else { // Scroll down
|
3707
|
-
let lastFullRow = viewInfo.lastFullRow;
|
3708
|
-
if (rowIndex > lastFullRow) {
|
3709
|
-
let viewIndexSize = lastFullRow - firstFullRow;
|
3710
|
-
scrollIndex = rowIndex - viewIndexSize + 2;
|
3711
|
-
if(scrollIndex < 0) {
|
3712
|
-
scrollIndex = 0;
|
3713
|
-
}
|
3714
|
-
}
|
3704
|
+
if(rowIndex < firstFullRow) { // Scroll up, as the target row is outside of view
|
3705
|
+
let targetIndex = rowIndex - bufferSize; // Have some spaces at the top for more appealing visual
|
3706
|
+
if(targetIndex < 0) {
|
3707
|
+
targetIndex = 0;
|
3715
3708
|
}
|
3709
|
+
|
3710
|
+
return this._layoutY.getLaneStart(targetIndex + rowIndexOffset) - heightOffset;
|
3716
3711
|
}
|
3717
3712
|
|
3718
|
-
let
|
3719
|
-
|
3720
|
-
|
3713
|
+
let lastFullRow = viewInfo.lastFullRow;
|
3714
|
+
if (rowIndex > lastFullRow) { // Scroll down, as the target row is outside of view
|
3715
|
+
let targetScroll = this._layoutY.getLaneStart(rowIndex + rowIndexOffset + bufferSize + 1);
|
3716
|
+
|
3717
|
+
return targetScroll - viewInfo.viewHeight - heightOffset;
|
3718
|
+
}
|
3719
|
+
|
3720
|
+
return null;
|
3721
3721
|
};
|
3722
3722
|
/** Scroll up or down to make specified row visible in the view
|
3723
3723
|
* @public
|
@@ -3742,7 +3742,12 @@ Core.prototype.getVerticalViewInfo = function() {
|
|
3742
3742
|
let viewBottom = viewTop + viewHeight;
|
3743
3743
|
|
3744
3744
|
let topRowIndex = this._layoutY.hitTest(viewTop) - rowIndexOffset;
|
3745
|
-
let bottomRowIndex = this._layoutY.hitTest(viewBottom - 0.1)
|
3745
|
+
let bottomRowIndex = this._layoutY.hitTest(viewBottom - 0.1);
|
3746
|
+
if(bottomRowIndex < 0) { // view is larger than existing row count
|
3747
|
+
bottomRowIndex = this._layoutY.getLaneCount() - 1;
|
3748
|
+
}
|
3749
|
+
bottomRowIndex -= rowIndexOffset;
|
3750
|
+
|
3746
3751
|
let laneTop = this._layoutY.getLaneStart(topRowIndex + rowIndexOffset);
|
3747
3752
|
let laneBottom = this._layoutY.getLaneEnd(bottomRowIndex + rowIndexOffset);
|
3748
3753
|
|
@@ -3750,7 +3755,10 @@ Core.prototype.getVerticalViewInfo = function() {
|
|
3750
3755
|
topRowIndex: topRowIndex,
|
3751
3756
|
firstFullRow: (laneTop < viewTop) ? topRowIndex + 1 : topRowIndex,
|
3752
3757
|
lastFullRow: (laneBottom > viewBottom) ? bottomRowIndex - 1 : bottomRowIndex,
|
3753
|
-
bottomRowIndex: bottomRowIndex
|
3758
|
+
bottomRowIndex: bottomRowIndex,
|
3759
|
+
viewHeight: viewHeight,
|
3760
|
+
viewTop: viewTop,
|
3761
|
+
viewBottom: viewBottom
|
3754
3762
|
};
|
3755
3763
|
};
|
3756
3764
|
/** @public
|
@@ -366,6 +366,7 @@ class FilterDialog extends BasicElement {
|
|
366
366
|
|
367
367
|
const root = this.shadowRoot;
|
368
368
|
|
369
|
+
this.setAttribute("tabindex", "0");
|
369
370
|
this._separator = root.getElementById("separator");
|
370
371
|
this._rootContainer = root.getElementById("root_panel");
|
371
372
|
this._dialogContent = root.getElementById("filterDialogContent");
|
@@ -412,7 +413,11 @@ class FilterDialog extends BasicElement {
|
|
412
413
|
this._filterBtns.addEventListener("click", this._onClickFilter.bind(this));
|
413
414
|
|
414
415
|
this.addEventListener("keydown", function(e){
|
415
|
-
if(e.
|
416
|
+
if(e.ctrlKey || e.altKey || e.metaKey){
|
417
|
+
return;
|
418
|
+
}
|
419
|
+
let code = e.code;
|
420
|
+
if(code === "Escape" || code === "Enter"){
|
416
421
|
this._onCancelBtnClick();
|
417
422
|
}
|
418
423
|
});
|
@@ -499,6 +504,7 @@ class FilterDialog extends BasicElement {
|
|
499
504
|
if(popupElem) {// After all changes, ensure that visibility is reset back
|
500
505
|
popupElem.style.visibility = "";
|
501
506
|
}
|
507
|
+
this.focus();
|
502
508
|
}
|
503
509
|
|
504
510
|
/** Initialize dialog
|
@@ -543,6 +549,7 @@ class FilterDialog extends BasicElement {
|
|
543
549
|
clearTimeout(this._winResizedTimer);
|
544
550
|
this._winResizedTimer = 0;
|
545
551
|
}
|
552
|
+
this.dispatchEvent(new CustomEvent("cancel"));
|
546
553
|
}
|
547
554
|
|
548
555
|
/**
|
package/lib/grid/index.js
CHANGED
@@ -21,7 +21,8 @@ declare namespace RowSegmentingPlugin {
|
|
21
21
|
nonSegmentSeparatorBinding?: ((...params: any[]) => any)|null,
|
22
22
|
sortingLogic?: ((...params: any[]) => any)|null,
|
23
23
|
rowSpanningField?: string|null,
|
24
|
-
segmentIdField?: string|null
|
24
|
+
segmentIdField?: string|null,
|
25
|
+
displayColumn?: (string|number)|null
|
25
26
|
};
|
26
27
|
|
27
28
|
}
|
@@ -48,6 +49,8 @@ declare class RowSegmentingPlugin extends GridPlugin {
|
|
48
49
|
|
49
50
|
public getSegmentParentRowId(rowRef: string|number|null): string;
|
50
51
|
|
52
|
+
public _resolveDisplayColumn(): number;
|
53
|
+
|
51
54
|
public setSegmentSeparator(rowRef: string|number|null, enabled?: boolean|null): boolean;
|
52
55
|
|
53
56
|
public setSegmentClassification(rowRef: string|number|null, fields: string|(string)[]|null): boolean;
|
@@ -19,6 +19,7 @@ import { Conflator } from "../../tr-grid-util/es6/Conflator.js";
|
|
19
19
|
* @property {Function=} sortingLogic=null Logic to be used by sortSegments method
|
20
20
|
* @property {string=} rowSpanningField="ROW_SPANNING" selected field for apply row spanning in row separator
|
21
21
|
* @property {string=} segmentIdField="SEGMENT_ID" selected field for set segment separator row
|
22
|
+
* @property {(string|number)=} displayColumn=null Render tags in the given column. It can be either the column index, column ID, or field.
|
22
23
|
*/
|
23
24
|
|
24
25
|
/** @callback RowSegmentingPlugin~SortingLogic
|
@@ -37,9 +38,11 @@ var RowSegmentingPlugin = function (options) {
|
|
37
38
|
t._onPreSectionDataBinding = t._onPreSectionDataBinding.bind(t);
|
38
39
|
t._updateHeader = t._updateHeader.bind(t);
|
39
40
|
t._onArrowClick = t._onArrowClick.bind(t);
|
41
|
+
t._onColumnIndexChanged = t._onColumnIndexChanged.bind(t);
|
40
42
|
t._rtSortingLogic = t._rtSortingLogic.bind(t);
|
41
43
|
t._refreshSegmentSeparator = t._refreshSegmentSeparator.bind(t);
|
42
44
|
t._separatorRefreshConflator = new Conflator(10, t._refreshSegmentSeparator);
|
45
|
+
t._columnIndexChangedConflator = new Conflator(10, t._onColumnIndexChanged);
|
43
46
|
|
44
47
|
t._hosts = [];
|
45
48
|
this.config({ rowSegmenting: options });
|
@@ -66,6 +69,10 @@ RowSegmentingPlugin.prototype._indentSizes = null;
|
|
66
69
|
* @private
|
67
70
|
*/
|
68
71
|
RowSegmentingPlugin.prototype._arrowSize = 9;
|
72
|
+
/** @type {number|string}
|
73
|
+
* @private
|
74
|
+
*/
|
75
|
+
RowSegmentingPlugin.prototype._displayColumn = null;
|
69
76
|
/** @type {Object}
|
70
77
|
* @private
|
71
78
|
*/
|
@@ -141,6 +148,9 @@ RowSegmentingPlugin.prototype.initialize = function (host, options) {
|
|
141
148
|
ExpanderIcon.loadExpanderStyles();
|
142
149
|
|
143
150
|
this._hosts.push(host);
|
151
|
+
host.listen("columnMoved", this._onColumnIndexChanged);
|
152
|
+
host.listen("columnRemoved", this._onColumnIndexChanged);
|
153
|
+
host.listen("columnAdded", this._onColumnIndexChanged);
|
144
154
|
host.listen("preSectionDataBinding", this._onPreSectionDataBinding);
|
145
155
|
|
146
156
|
this.config(options);
|
@@ -237,10 +247,14 @@ RowSegmentingPlugin.prototype.unload = function (host) {
|
|
237
247
|
if (at < 0) {
|
238
248
|
return;
|
239
249
|
}
|
250
|
+
host.unlisten("columnMoved", this._onColumnIndexChanged);
|
251
|
+
host.unlisten("columnRemoved", this._onColumnIndexChanged);
|
252
|
+
host.unlisten("columnAdded", this._onColumnIndexChanged);
|
240
253
|
host.unlisten("preSectionDataBinding", this._onPreSectionDataBinding);
|
241
254
|
this._hosts.splice(at, 1);
|
242
255
|
if(this._hosts.length <= 0) {
|
243
256
|
this._separatorRefreshConflator.reset();
|
257
|
+
this._columnIndexChangedConflator.reset();
|
244
258
|
}
|
245
259
|
this._dispose();
|
246
260
|
};
|
@@ -278,6 +292,9 @@ RowSegmentingPlugin.prototype.config = function (options) {
|
|
278
292
|
if (option.predefinedColors != null && typeof option.predefinedColors === "object") {
|
279
293
|
this._predefinedColors = option.predefinedColors;
|
280
294
|
}
|
295
|
+
if(option.displayColumn != null) {
|
296
|
+
this._displayColumn = option.displayColumn;
|
297
|
+
}
|
281
298
|
|
282
299
|
this._rowPainter = new RowPainter({
|
283
300
|
clickableCell: false,
|
@@ -349,6 +366,23 @@ RowSegmentingPlugin.prototype.getConfigObject = function (gridOptions) {
|
|
349
366
|
extOptions.segmentIdField = this._segmentIdField;
|
350
367
|
}
|
351
368
|
|
369
|
+
if(this._displayColumn != null) {
|
370
|
+
// WANRING: display column use colId and colIndex for internal, but give field and colIndex for user
|
371
|
+
let curDisIndex = this._resolveDisplayColumn();
|
372
|
+
if(curDisIndex < 0 ) {
|
373
|
+
extOptions.displayColumn = this._displayColumn; // column id
|
374
|
+
} else {
|
375
|
+
let curDisFied = this.getColumnField(curDisIndex);
|
376
|
+
let fields = this.getColumnFields();
|
377
|
+
let currentIndex = fields.indexOf(curDisFied);
|
378
|
+
if(currentIndex === fields.lastIndexOf(curDisFied)) {
|
379
|
+
extOptions.displayColumn = curDisFied; // column field
|
380
|
+
} else { // duplicate col use col index
|
381
|
+
extOptions.displayColumn = curDisIndex; // column index
|
382
|
+
}
|
383
|
+
}
|
384
|
+
}
|
385
|
+
|
352
386
|
// restore runningId for spanningIdField
|
353
387
|
this._runningId = 0;
|
354
388
|
|
@@ -434,16 +468,43 @@ RowSegmentingPlugin.prototype._onPreSectionDataBinding = function (e) {
|
|
434
468
|
);
|
435
469
|
};
|
436
470
|
|
471
|
+
/**
|
472
|
+
* @private
|
473
|
+
*/
|
474
|
+
RowSegmentingPlugin.prototype._onColumnIndexChanged = function () {
|
475
|
+
if(this._columnIndexChangedConflator.conflate()) {
|
476
|
+
return;
|
477
|
+
}
|
478
|
+
if(this._spanning) {
|
479
|
+
this._clearSectionHeaderStyle();
|
480
|
+
}
|
481
|
+
// columnAdded event will fire binding, so it will be double render header
|
482
|
+
this.updateHeaders(); // need to force update header when column index is changed
|
483
|
+
};
|
484
|
+
|
437
485
|
/** @private
|
438
|
-
* @param {Array.<Object>} segments
|
439
486
|
*/
|
440
|
-
RowSegmentingPlugin.prototype.
|
441
|
-
|
487
|
+
RowSegmentingPlugin.prototype._clearSectionHeaderStyle = function () {
|
488
|
+
let host = this._hosts[0];
|
489
|
+
if (!host) {
|
490
|
+
return;
|
491
|
+
}
|
492
|
+
let sections = host.getAllSections("content");
|
493
|
+
let rowPainter = this._rowPainter;
|
494
|
+
for (let i = sections.length; --i >= 0;) {
|
495
|
+
let section = sections[i];
|
496
|
+
|
497
|
+
let fi = section.getFirstIndexInView();
|
498
|
+
let li = section.getLastIndexInView();
|
499
|
+
for (let r = fi; r <= li; r++) {
|
500
|
+
// Currently, removeHeaderStyle removes all stretched cells, no need to use column index. In this case, we're specifying column index 0
|
501
|
+
rowPainter.removeHeaderStyle(section, 0, r);
|
502
|
+
}
|
503
|
+
}
|
442
504
|
};
|
443
505
|
|
444
506
|
/** @public */
|
445
507
|
RowSegmentingPlugin.prototype.updateHeaders = function () {
|
446
|
-
this._timerId = 0;
|
447
508
|
var host = this._hosts[0];
|
448
509
|
if (!host) {
|
449
510
|
return;
|
@@ -492,6 +553,32 @@ RowSegmentingPlugin.prototype.getSegmentParentRowId = function (rowRef) {
|
|
492
553
|
return "";
|
493
554
|
};
|
494
555
|
|
556
|
+
/** @public
|
557
|
+
* @description Resolve display column from column id to column index
|
558
|
+
* @return {number} return column index of display column
|
559
|
+
*/
|
560
|
+
RowSegmentingPlugin.prototype._resolveDisplayColumn = function () {
|
561
|
+
if(this._displayColumn == null) {
|
562
|
+
return 0;
|
563
|
+
}
|
564
|
+
if(this.getColumnCount() === 0) {
|
565
|
+
return -1;
|
566
|
+
}
|
567
|
+
if(typeof this._displayColumn === "number") {
|
568
|
+
let colId = this.getColumnId(this._displayColumn);
|
569
|
+
if(colId) {
|
570
|
+
this._displayColumn = colId;
|
571
|
+
}
|
572
|
+
}
|
573
|
+
let colIndex = this.getColumnIndex(this._displayColumn);
|
574
|
+
if(colIndex < 0) {
|
575
|
+
colIndex = 0;
|
576
|
+
this._displayColumn = this.getColumnId(colIndex);
|
577
|
+
}
|
578
|
+
|
579
|
+
return colIndex;
|
580
|
+
};
|
581
|
+
|
495
582
|
/** @private
|
496
583
|
* @param {Object} settings SectionSettings
|
497
584
|
* @param {number=} firstRowIndex
|
@@ -502,7 +589,10 @@ RowSegmentingPlugin.prototype._updateHeader = function (settings, firstRowIndex,
|
|
502
589
|
return;
|
503
590
|
}
|
504
591
|
|
505
|
-
var headerColumn =
|
592
|
+
var headerColumn = this._resolveDisplayColumn();
|
593
|
+
if(headerColumn < 0) {
|
594
|
+
return;
|
595
|
+
}
|
506
596
|
var dv = settings.getDataSource();
|
507
597
|
var dt = dv.getDataSource();
|
508
598
|
var section = settings.getSection();
|
@@ -537,6 +537,7 @@ CheckboxPlugin.prototype._genCheckboxColumn = function (userObj) {
|
|
537
537
|
let defaultObj = {
|
538
538
|
width: this._width,
|
539
539
|
sortable: false,
|
540
|
+
focusable: true,
|
540
541
|
className: "tr-checkbox-column", // For rt-grid
|
541
542
|
classes: { "tr-checkbox-column": 1 }, // For composite grid
|
542
543
|
noResizing: true, // ColumnResizing Extension
|
@@ -625,7 +626,8 @@ CheckboxPlugin.prototype._findCheckboxColumnIndexFromConfig = function (columns)
|
|
625
626
|
if(Array.isArray(columns)) {
|
626
627
|
let colCount = columns.length;
|
627
628
|
for(let i = 0; i < colCount; i++) {
|
628
|
-
|
629
|
+
let col = columns[i];
|
630
|
+
if(col && col.checkboxColumn) {
|
629
631
|
return i;
|
630
632
|
}
|
631
633
|
}
|
@@ -1760,6 +1760,8 @@ RowFilteringPlugin.prototype.openDialog = function(colIndex, runtimeDialogOption
|
|
1760
1760
|
this._filterDialog.addEventListener("sortChanged", this._onDialogSortChanged.bind(this));
|
1761
1761
|
}
|
1762
1762
|
this._filterDialog.addEventListener("filterChanged", this._onDialogFilterChanged.bind(this));
|
1763
|
+
this._filterDialog.addEventListener("confirm", this._onDialogClosed.bind(this));
|
1764
|
+
this._filterDialog.addEventListener("cancel", this._onDialogClosed.bind(this));
|
1763
1765
|
}
|
1764
1766
|
}
|
1765
1767
|
|
@@ -2153,7 +2155,14 @@ RowFilteringPlugin._getFilterBuilder = function() {
|
|
2153
2155
|
}
|
2154
2156
|
return RowFilteringPlugin._filterBuilder;
|
2155
2157
|
};
|
2156
|
-
|
2158
|
+
/** @private
|
2159
|
+
* @param {Object} e
|
2160
|
+
*/
|
2161
|
+
RowFilteringPlugin.prototype._onDialogClosed = function(e) {
|
2162
|
+
if(this._hosts.length) {
|
2163
|
+
this._hosts[0].focus();
|
2164
|
+
}
|
2165
|
+
};
|
2157
2166
|
/** @public
|
2158
2167
|
* @ignore
|
2159
2168
|
* @param {*} val
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import {Ext} from '../../tr-grid-util/es6/Ext.js';
|
2
2
|
import {GridPlugin} from '../../tr-grid-util/es6/GridPlugin.js';
|
3
|
-
import {extendObject, injectCss, prettifyCss} from '../../tr-grid-util/es6/Util.js';
|
3
|
+
import {extendObject, injectCss, prettifyCss, isEmptyObject} from '../../tr-grid-util/es6/Util.js';
|
4
4
|
import {CellPainter} from '../../tr-grid-util/es6/CellPainter.js';
|
5
5
|
import {FilterBuilder} from '../../tr-grid-util/es6/FilterBuilder.js';
|
6
6
|
import {ElfUtil} from '../../tr-grid-util/es6/ElfUtil.js';
|
package/lib/versions.json
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
"tr-grid-util": "1.3.159",
|
3
3
|
"tr-grid-printer": "1.0.18",
|
4
4
|
"@grid/column-dragging": "1.0.21",
|
5
|
-
"@grid/row-segmenting": "1.0.
|
5
|
+
"@grid/row-segmenting": "1.0.34",
|
6
6
|
"@grid/statistics-row": "1.0.17",
|
7
7
|
"@grid/zoom": "1.0.11",
|
8
8
|
"tr-grid-auto-tooltip": "1.1.8",
|
9
9
|
"tr-grid-cell-selection": "1.0.38",
|
10
|
-
"tr-grid-checkbox": "1.0.
|
10
|
+
"tr-grid-checkbox": "1.0.70",
|
11
11
|
"tr-grid-column-fitter": "1.0.41",
|
12
12
|
"tr-grid-column-formatting": "0.9.36",
|
13
13
|
"tr-grid-column-grouping": "1.0.63",
|
@@ -24,7 +24,7 @@
|
|
24
24
|
"tr-grid-percent-bar": "1.0.24",
|
25
25
|
"tr-grid-range-bar": "2.0.8",
|
26
26
|
"tr-grid-row-dragging": "1.0.36",
|
27
|
-
"tr-grid-row-filtering": "1.0.
|
27
|
+
"tr-grid-row-filtering": "1.0.82",
|
28
28
|
"tr-grid-row-grouping": "1.0.88",
|
29
29
|
"tr-grid-row-selection": "1.0.32",
|
30
30
|
"tr-grid-rowcoloring": "1.0.25",
|
@@ -32,6 +32,6 @@
|
|
32
32
|
"tr-grid-titlewrap": "1.0.22",
|
33
33
|
"@grid/formatters": "1.0.55",
|
34
34
|
"@grid/column-selection-dialog": "4.0.57",
|
35
|
-
"@grid/filter-dialog": "4.0.
|
35
|
+
"@grid/filter-dialog": "4.0.71",
|
36
36
|
"@grid/column-format-dialog": "4.0.45"
|
37
37
|
}
|
package/package.json
CHANGED