Skip to content

Commit 384f0d9

Browse files
authored
TagBox: tags should be actualized after data source change (T1253312)
1 parent de84349 commit 384f0d9

File tree

3 files changed

+192
-14
lines changed

3 files changed

+192
-14
lines changed

packages/devextreme/js/__internal/ui/m_tag_box.ts

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,15 @@ const TagBox = (SelectBox as any).inherit({
786786
const d = Deferred();
787787
const dataController = this._dataController;
788788

789-
if ((!this._isDataSourceChanged || isListItemsLoaded) && selectedItemsAlreadyLoaded) {
789+
if (!this._dataSource) {
790+
return d.resolve([]).promise();
791+
}
792+
793+
if (
794+
(!this._isDataSourceChanged || isListItemsLoaded)
795+
&& selectedItemsAlreadyLoaded
796+
&& !this._isDataSourceOptionChanged
797+
) {
790798
return d.resolve(filteredItems).promise();
791799
}
792800
const { customQueryParams, expand, select } = dataController.loadOptions();
@@ -798,6 +806,7 @@ const TagBox = (SelectBox as any).inherit({
798806
})
799807
.done((data, extra) => {
800808
this._isDataSourceChanged = false;
809+
this._isDataSourceOptionChanged = false;
801810
if (this._disposed) {
802811
d.reject();
803812
return;
@@ -817,6 +826,7 @@ const TagBox = (SelectBox as any).inherit({
817826
const items = [];
818827
const cache = {};
819828
const isValueExprSpecified = this._valueGetterExpr() === 'this';
829+
const { acceptCustomValue } = this.option();
820830
const filteredValues = {};
821831

822832
filteredItems.forEach((filteredItem) => {
@@ -831,13 +841,30 @@ const TagBox = (SelectBox as any).inherit({
831841
const currentItem = filteredValues[isValueExprSpecified ? JSON.stringify(value) : value];
832842

833843
if (isValueExprSpecified && !isDefined(currentItem)) {
834-
loadItemPromises.push(this._loadItem(value, cache).always((item) => {
835-
const newItem = this._createTagData(item, value);
836-
items.splice(index, 0, newItem as never);
837-
}) as never);
844+
if (!this._dataSource) {
845+
return;
846+
}
847+
848+
loadItemPromises.push(
849+
// @ts-expect-error
850+
this._loadItem(value, cache)
851+
.done((item) => {
852+
const newItem = this._createTagData(item, value);
853+
// @ts-expect-error
854+
items.splice(index, 0, newItem);
855+
})
856+
.fail(() => {
857+
if (acceptCustomValue) {
858+
const newItem = this._createTagData(undefined, value);
859+
// @ts-expect-error
860+
items.splice(index, 0, newItem);
861+
}
862+
}),
863+
);
838864
} else {
839865
const newItem = this._createTagData(currentItem, value);
840-
items.splice(index, 0, newItem as never);
866+
// @ts-expect-error
867+
items.splice(index, 0, newItem);
841868
}
842869
});
843870

@@ -869,7 +896,8 @@ const TagBox = (SelectBox as any).inherit({
869896
values.forEach((value) => {
870897
const item = this._getItemFromPlain(value);
871898
if (isDefined(item)) {
872-
resultItems.push(item as never);
899+
// @ts-expect-error
900+
resultItems.push(item);
873901
}
874902
});
875903
return resultItems;
@@ -1006,6 +1034,12 @@ const TagBox = (SelectBox as any).inherit({
10061034
return selectedItems;
10071035
},
10081036

1037+
_processDataSourceChanging() {
1038+
this._isDataSourceOptionChanged = true;
1039+
1040+
this.callBase();
1041+
},
1042+
10091043
_integrateInput() {
10101044
this._isInputReady.resolve();
10111045
this.callBase();
@@ -1059,13 +1093,15 @@ const TagBox = (SelectBox as any).inherit({
10591093
this._tagElements().remove();
10601094
} else {
10611095
const $tags = this._tagElements();
1062-
const values = this._getValue();
1096+
1097+
const selectedItems = this.option('selectedItems') ?? [];
1098+
const values = selectedItems.map((item) => this._valueGetter(item));
10631099

10641100
each($tags, (_, tag) => {
10651101
const $tag = $(tag);
10661102
const tagData = $tag.data(TAGBOX_TAG_DATA_KEY);
10671103

1068-
if (!values?.includes(tagData)) {
1104+
if (!values.includes(tagData)) {
10691105
$tag.remove();
10701106
}
10711107
});
@@ -1411,8 +1447,8 @@ const TagBox = (SelectBox as any).inherit({
14111447

14121448
_dataSourceFilterExpr() {
14131449
const filter = [];
1414-
1415-
this._getValue().forEach((value) => filter.push(['!', [this._valueGetterExpr(), value]] as never));
1450+
// @ts-expect-error
1451+
this._getValue().forEach((value) => filter.push(['!', [this._valueGetterExpr(), value]]));
14161452

14171453
return filter;
14181454
},

packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/tagBox.markup.tests.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ QUnit.module('base markup', moduleSetup, () => {
6767
QUnit.test('tagbox should render custom values in tags', function(assert) {
6868
const $element = $('#tagBox')
6969
.dxTagBox({
70-
value: [1, 2]
70+
value: [1, 2],
71+
acceptCustomValue: true,
7172
});
7273

7374
const tags = $element.find('.' + TAGBOX_TAG_CONTENT_CLASS);
@@ -77,6 +78,7 @@ QUnit.module('base markup', moduleSetup, () => {
7778
QUnit.test('tagElement arguments of tagTemplate for custom tags is correct', function(assert) {
7879
$('#tagBox').dxTagBox({
7980
value: [1, 2],
81+
acceptCustomValue: true,
8082
tagTemplate: function(tagData, tagElement) {
8183
assert.equal(isRenderer(tagElement), !!config().useJQuery, 'tagElement is correct');
8284
}

packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/tagBox.tests.js

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,146 @@ QUnit.module('tags', moduleSetup, () => {
738738
done();
739739
}, TIME_TO_WAIT);
740740
});
741+
742+
['items', 'dataSource'].forEach((optionName) => {
743+
QUnit.test('TagBox should not have unexpected selected tags when value includes item that doesn\'t exist in items', function(assert) {
744+
const options = { value: [1, 11] };
745+
options[optionName] = [1, 2, 3];
746+
const $tagBox = $('#tagBox').dxTagBox(options);
747+
const tagBox = $tagBox.dxTagBox('instance');
748+
749+
const { selectedItems } = tagBox.option();
750+
const $tags = $tagBox.find(`.${TAGBOX_TAG_CLASS}`);
751+
752+
assert.deepEqual(selectedItems, [1], 'selectedItems have no unexpected items');
753+
assert.strictEqual($tags.length, 1, 'there is no unexpected tags');
754+
});
755+
});
756+
757+
[false, true].forEach((deferRendering) => {
758+
[
759+
{
760+
initialOptions: {
761+
deferRendering,
762+
items: [1, 2, 3],
763+
value: [1, 3],
764+
},
765+
optionsToUpdate: {
766+
items: [1, 2],
767+
},
768+
expectedSelectedItems: [1],
769+
optionName: 'items',
770+
},
771+
{
772+
initialOptions: {
773+
deferRendering,
774+
dataSource: [1, 2, 3],
775+
value: [1, 3],
776+
},
777+
optionsToUpdate: {
778+
dataSource: [1, 2],
779+
},
780+
expectedSelectedItems: [1],
781+
optionName: 'dataSource',
782+
},
783+
{
784+
initialOptions: {
785+
deferRendering,
786+
items: [1],
787+
value: [1],
788+
},
789+
optionsToUpdate: {
790+
items: null,
791+
},
792+
expectedSelectedItems: [],
793+
optionName: 'items',
794+
},
795+
{
796+
initialOptions: {
797+
deferRendering,
798+
dataSource: [1],
799+
value: [1],
800+
},
801+
optionsToUpdate: {
802+
dataSource: null,
803+
},
804+
expectedSelectedItems: [],
805+
optionName: 'dataSource',
806+
},
807+
{
808+
initialOptions: {
809+
deferRendering,
810+
dataSource: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }, { id: 3, text: 'three' }],
811+
value: [1, 3],
812+
valueExpr: 'id',
813+
},
814+
optionsToUpdate: {
815+
dataSource: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }],
816+
},
817+
expectedSelectedItems: [{ id: 1, text: 'one' }],
818+
optionName: 'dataSource',
819+
},
820+
{
821+
initialOptions: {
822+
deferRendering,
823+
dataSource: new DataSource({ store: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }, { id: 3, text: 'three' }] }),
824+
value: [1, 3],
825+
valueExpr: 'id',
826+
},
827+
optionsToUpdate: {
828+
dataSource: new DataSource({ store: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }] }),
829+
},
830+
expectedSelectedItems: [{ id: 1, text: 'one' }],
831+
optionName: 'dataSource',
832+
},
833+
{
834+
initialOptions: {
835+
deferRendering,
836+
items: null,
837+
value: [1],
838+
},
839+
optionsToUpdate: {
840+
items: [1, 2],
841+
},
842+
expectedSelectedItems: [1],
843+
optionName: 'items',
844+
},
845+
{
846+
initialOptions: {
847+
deferRendering,
848+
dataSource: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }],
849+
value: [1, 3],
850+
valueExpr: 'id',
851+
},
852+
optionsToUpdate: {
853+
dataSource: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }, { id: 3, text: 'three' }],
854+
},
855+
expectedSelectedItems: [{ id: 1, text: 'one' }, { id: 3, text: 'three' }],
856+
optionName: 'dataSource',
857+
},
858+
].forEach(({ initialOptions, optionsToUpdate, expectedSelectedItems, optionName }) => {
859+
const source = initialOptions.dataSource instanceof DataSource ? 'DataSource' : JSON.stringify(initialOptions[optionName]);
860+
861+
QUnit.test(`SelectedItems should be updated correctly on runtime ${optionName} change (deferRendering=${deferRendering}, source=${source}) (T1253312)`, function(assert) {
862+
const tagBox = $('#tagBox').dxTagBox(initialOptions).dxTagBox('instance');
863+
864+
tagBox.option(optionsToUpdate);
865+
866+
assert.deepEqual(tagBox.option('selectedItems'), expectedSelectedItems, 'selectedItems are updated');
867+
});
868+
869+
QUnit.test(`Tags should be updated correctly on runtime ${optionName} change (deferRendering=${deferRendering}, source=${source}) (T1253312)`, function(assert) {
870+
const $tagBox = $('#tagBox').dxTagBox(initialOptions);
871+
const tagBox = $tagBox.dxTagBox('instance');
872+
873+
tagBox.option(optionsToUpdate);
874+
875+
const $tags = $tagBox.find(`.${TAGBOX_TAG_CLASS}`);
876+
877+
assert.strictEqual($tags.length, expectedSelectedItems.length, 'tags are updated');
878+
});
879+
});
880+
});
741881
});
742882

743883
QUnit.module('multi tag support', {
@@ -7038,7 +7178,7 @@ QUnit.module('performance', () => {
70387178
this.resetGetterCallCount();
70397179
$(`.${SELECT_ALL_CHECKBOX_CLASS}`).trigger('dxclick');
70407180

7041-
assert.strictEqual(this.getValueGetterCallCount(), 6154, 'key getter call count');
7181+
assert.strictEqual(this.getValueGetterCallCount(), 6254, 'key getter call count');
70427182
assert.strictEqual(isValueEqualsSpy.callCount, 5050, '_isValueEquals call count');
70437183
});
70447184

@@ -7049,7 +7189,7 @@ QUnit.module('performance', () => {
70497189
const checkboxes = $(`.${LIST_CHECKBOX_CLASS}`);
70507190
checkboxes.eq(checkboxes.length - 1).trigger('dxclick');
70517191

7052-
assert.strictEqual(this.getValueGetterCallCount(), 6054, 'key getter call count');
7192+
assert.strictEqual(this.getValueGetterCallCount(), 6153, 'key getter call count');
70537193
});
70547194
});
70557195

0 commit comments

Comments
 (0)