Skip to content

Commit

Permalink
TagBox: tags should be actualized after data source change (T1253312)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zedwag authored Jan 30, 2025
1 parent de84349 commit 384f0d9
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 14 deletions.
58 changes: 47 additions & 11 deletions packages/devextreme/js/__internal/ui/m_tag_box.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,15 @@ const TagBox = (SelectBox as any).inherit({
const d = Deferred();
const dataController = this._dataController;

if ((!this._isDataSourceChanged || isListItemsLoaded) && selectedItemsAlreadyLoaded) {
if (!this._dataSource) {
return d.resolve([]).promise();
}

if (
(!this._isDataSourceChanged || isListItemsLoaded)
&& selectedItemsAlreadyLoaded
&& !this._isDataSourceOptionChanged
) {
return d.resolve(filteredItems).promise();
}
const { customQueryParams, expand, select } = dataController.loadOptions();
Expand All @@ -798,6 +806,7 @@ const TagBox = (SelectBox as any).inherit({
})
.done((data, extra) => {
this._isDataSourceChanged = false;
this._isDataSourceOptionChanged = false;
if (this._disposed) {
d.reject();
return;
Expand All @@ -817,6 +826,7 @@ const TagBox = (SelectBox as any).inherit({
const items = [];
const cache = {};
const isValueExprSpecified = this._valueGetterExpr() === 'this';
const { acceptCustomValue } = this.option();
const filteredValues = {};

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

if (isValueExprSpecified && !isDefined(currentItem)) {
loadItemPromises.push(this._loadItem(value, cache).always((item) => {
const newItem = this._createTagData(item, value);
items.splice(index, 0, newItem as never);
}) as never);
if (!this._dataSource) {
return;
}

loadItemPromises.push(
// @ts-expect-error
this._loadItem(value, cache)
.done((item) => {
const newItem = this._createTagData(item, value);
// @ts-expect-error
items.splice(index, 0, newItem);
})
.fail(() => {
if (acceptCustomValue) {
const newItem = this._createTagData(undefined, value);
// @ts-expect-error
items.splice(index, 0, newItem);
}
}),
);
} else {
const newItem = this._createTagData(currentItem, value);
items.splice(index, 0, newItem as never);
// @ts-expect-error
items.splice(index, 0, newItem);
}
});

Expand Down Expand Up @@ -869,7 +896,8 @@ const TagBox = (SelectBox as any).inherit({
values.forEach((value) => {
const item = this._getItemFromPlain(value);
if (isDefined(item)) {
resultItems.push(item as never);
// @ts-expect-error
resultItems.push(item);
}
});
return resultItems;
Expand Down Expand Up @@ -1006,6 +1034,12 @@ const TagBox = (SelectBox as any).inherit({
return selectedItems;
},

_processDataSourceChanging() {
this._isDataSourceOptionChanged = true;

this.callBase();
},

_integrateInput() {
this._isInputReady.resolve();
this.callBase();
Expand Down Expand Up @@ -1059,13 +1093,15 @@ const TagBox = (SelectBox as any).inherit({
this._tagElements().remove();
} else {
const $tags = this._tagElements();
const values = this._getValue();

const selectedItems = this.option('selectedItems') ?? [];
const values = selectedItems.map((item) => this._valueGetter(item));

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

if (!values?.includes(tagData)) {
if (!values.includes(tagData)) {
$tag.remove();
}
});
Expand Down Expand Up @@ -1411,8 +1447,8 @@ const TagBox = (SelectBox as any).inherit({

_dataSourceFilterExpr() {
const filter = [];

this._getValue().forEach((value) => filter.push(['!', [this._valueGetterExpr(), value]] as never));
// @ts-expect-error
this._getValue().forEach((value) => filter.push(['!', [this._valueGetterExpr(), value]]));

return filter;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ QUnit.module('base markup', moduleSetup, () => {
QUnit.test('tagbox should render custom values in tags', function(assert) {
const $element = $('#tagBox')
.dxTagBox({
value: [1, 2]
value: [1, 2],
acceptCustomValue: true,
});

const tags = $element.find('.' + TAGBOX_TAG_CONTENT_CLASS);
Expand All @@ -77,6 +78,7 @@ QUnit.module('base markup', moduleSetup, () => {
QUnit.test('tagElement arguments of tagTemplate for custom tags is correct', function(assert) {
$('#tagBox').dxTagBox({
value: [1, 2],
acceptCustomValue: true,
tagTemplate: function(tagData, tagElement) {
assert.equal(isRenderer(tagElement), !!config().useJQuery, 'tagElement is correct');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,146 @@ QUnit.module('tags', moduleSetup, () => {
done();
}, TIME_TO_WAIT);
});

['items', 'dataSource'].forEach((optionName) => {
QUnit.test('TagBox should not have unexpected selected tags when value includes item that doesn\'t exist in items', function(assert) {
const options = { value: [1, 11] };
options[optionName] = [1, 2, 3];
const $tagBox = $('#tagBox').dxTagBox(options);
const tagBox = $tagBox.dxTagBox('instance');

const { selectedItems } = tagBox.option();
const $tags = $tagBox.find(`.${TAGBOX_TAG_CLASS}`);

assert.deepEqual(selectedItems, [1], 'selectedItems have no unexpected items');
assert.strictEqual($tags.length, 1, 'there is no unexpected tags');
});
});

[false, true].forEach((deferRendering) => {
[
{
initialOptions: {
deferRendering,
items: [1, 2, 3],
value: [1, 3],
},
optionsToUpdate: {
items: [1, 2],
},
expectedSelectedItems: [1],
optionName: 'items',
},
{
initialOptions: {
deferRendering,
dataSource: [1, 2, 3],
value: [1, 3],
},
optionsToUpdate: {
dataSource: [1, 2],
},
expectedSelectedItems: [1],
optionName: 'dataSource',
},
{
initialOptions: {
deferRendering,
items: [1],
value: [1],
},
optionsToUpdate: {
items: null,
},
expectedSelectedItems: [],
optionName: 'items',
},
{
initialOptions: {
deferRendering,
dataSource: [1],
value: [1],
},
optionsToUpdate: {
dataSource: null,
},
expectedSelectedItems: [],
optionName: 'dataSource',
},
{
initialOptions: {
deferRendering,
dataSource: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }, { id: 3, text: 'three' }],
value: [1, 3],
valueExpr: 'id',
},
optionsToUpdate: {
dataSource: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }],
},
expectedSelectedItems: [{ id: 1, text: 'one' }],
optionName: 'dataSource',
},
{
initialOptions: {
deferRendering,
dataSource: new DataSource({ store: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }, { id: 3, text: 'three' }] }),
value: [1, 3],
valueExpr: 'id',
},
optionsToUpdate: {
dataSource: new DataSource({ store: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }] }),
},
expectedSelectedItems: [{ id: 1, text: 'one' }],
optionName: 'dataSource',
},
{
initialOptions: {
deferRendering,
items: null,
value: [1],
},
optionsToUpdate: {
items: [1, 2],
},
expectedSelectedItems: [1],
optionName: 'items',
},
{
initialOptions: {
deferRendering,
dataSource: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }],
value: [1, 3],
valueExpr: 'id',
},
optionsToUpdate: {
dataSource: [{ id: 1, text: 'one' }, { id: 2, text: 'two' }, { id: 3, text: 'three' }],
},
expectedSelectedItems: [{ id: 1, text: 'one' }, { id: 3, text: 'three' }],
optionName: 'dataSource',
},
].forEach(({ initialOptions, optionsToUpdate, expectedSelectedItems, optionName }) => {
const source = initialOptions.dataSource instanceof DataSource ? 'DataSource' : JSON.stringify(initialOptions[optionName]);

QUnit.test(`SelectedItems should be updated correctly on runtime ${optionName} change (deferRendering=${deferRendering}, source=${source}) (T1253312)`, function(assert) {
const tagBox = $('#tagBox').dxTagBox(initialOptions).dxTagBox('instance');

tagBox.option(optionsToUpdate);

assert.deepEqual(tagBox.option('selectedItems'), expectedSelectedItems, 'selectedItems are updated');
});

QUnit.test(`Tags should be updated correctly on runtime ${optionName} change (deferRendering=${deferRendering}, source=${source}) (T1253312)`, function(assert) {
const $tagBox = $('#tagBox').dxTagBox(initialOptions);
const tagBox = $tagBox.dxTagBox('instance');

tagBox.option(optionsToUpdate);

const $tags = $tagBox.find(`.${TAGBOX_TAG_CLASS}`);

assert.strictEqual($tags.length, expectedSelectedItems.length, 'tags are updated');
});
});
});
});

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

assert.strictEqual(this.getValueGetterCallCount(), 6154, 'key getter call count');
assert.strictEqual(this.getValueGetterCallCount(), 6254, 'key getter call count');
assert.strictEqual(isValueEqualsSpy.callCount, 5050, '_isValueEquals call count');
});

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

assert.strictEqual(this.getValueGetterCallCount(), 6054, 'key getter call count');
assert.strictEqual(this.getValueGetterCallCount(), 6153, 'key getter call count');
});
});

Expand Down

0 comments on commit 384f0d9

Please sign in to comment.