Skip to content

Commit b3c594e

Browse files
CollectionAsync: use unique string keys to index _asyncTemplateItems (T1269855) (#28630)
1 parent 5ac4ef1 commit b3c594e

File tree

6 files changed

+193
-15
lines changed

6 files changed

+193
-15
lines changed

apps/demos/testing/common.test.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,6 @@ const SKIPPED_TESTS = {
133133
],
134134
},
135135
React: {
136-
Common: [
137-
{ demo: 'ActionAndListsOverview', themes: [THEME.generic, THEME.material] },
138-
],
139136
Charts: [
140137
{ demo: 'PiesWithEqualSize', themes: [THEME.material] },
141138
{ demo: 'CustomAnnotations', themes: [THEME.material] },
@@ -182,9 +179,6 @@ const SKIPPED_TESTS = {
182179
],
183180
},
184181
Vue: {
185-
Common: [
186-
{ demo: 'ActionAndListsOverview', themes: [THEME.generic, THEME.material] },
187-
],
188182
Charts: [
189183
{ demo: 'TilingAlgorithms', themes: [THEME.material] },
190184
{ demo: 'ExportAndPrintingAPI', themes: [THEME.material] },

packages/devextreme/js/__internal/ui/collection/base.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ declare class Base<
4747
container: dxElementWrapper;
4848
contentClass: string;
4949
defaultTemplateName: string;
50+
uniqueKey?: string;
5051
}): dxElementWrapper;
5152
_renderContent(): void;
5253
_postprocessRenderItem(args: unknown): void;

packages/devextreme/js/__internal/ui/collection/m_collection_widget.async.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import Guid from '@js/core/guid';
12
import { noop } from '@js/core/utils/common';
3+
import type { DeferredObj } from '@js/core/utils/deferred';
24
import { Deferred, when } from '@js/core/utils/deferred';
35

46
import CollectionWidgetEdit from './m_collection_widget.edit';
57

68
const AsyncCollectionWidget = CollectionWidgetEdit.inherit({
79
_initMarkup() {
8-
this._asyncTemplateItems = [];
10+
this._asyncTemplateItemsMap = {};
911
this.callBase();
1012
},
1113

@@ -17,9 +19,10 @@ const AsyncCollectionWidget = CollectionWidgetEdit.inherit({
1719
_renderItemContent(args) {
1820
const renderContentDeferred = Deferred();
1921
const itemDeferred = Deferred();
22+
const uniqueKey = `dx${new Guid()}`;
2023

21-
this._asyncTemplateItems[args.index] = itemDeferred;
22-
const $itemContent = this.callBase(args);
24+
this._asyncTemplateItemsMap[uniqueKey] = itemDeferred;
25+
const $itemContent = this.callBase({ ...args, uniqueKey });
2326

2427
itemDeferred.done(() => {
2528
renderContentDeferred.resolve($itemContent);
@@ -30,27 +33,37 @@ const AsyncCollectionWidget = CollectionWidgetEdit.inherit({
3033

3134
_onItemTemplateRendered(itemTemplate, renderArgs) {
3235
return () => {
33-
this._asyncTemplateItems[renderArgs.index]?.resolve();
36+
this._asyncTemplateItemsMap[renderArgs.uniqueKey]?.resolve();
3437
};
3538
},
3639

3740
_postProcessRenderItems: noop,
3841

3942
_planPostRenderActions(...args: unknown[]) {
4043
const d = Deferred();
41-
when.apply(this, this._asyncTemplateItems).done(() => {
44+
const asyncTemplateItems = Object.values<DeferredObj<unknown>>(this._asyncTemplateItemsMap);
45+
46+
when.apply(this, asyncTemplateItems).done(() => {
4247
this._postProcessRenderItems(...args);
43-
d.resolve();
48+
49+
d.resolve().done(() => {
50+
this._asyncTemplateItemsMap = {};
51+
});
4452
});
53+
4554
return d.promise();
4655
},
4756

4857
_clean() {
4958
this.callBase();
50-
this._asyncTemplateItems.forEach((item) => {
59+
60+
const asyncTemplateItems = Object.values<DeferredObj<unknown>>(this._asyncTemplateItemsMap);
61+
62+
asyncTemplateItems.forEach((item) => {
5163
item.reject();
5264
});
53-
this._asyncTemplateItems = [];
65+
66+
this._asyncTemplateItemsMap = {};
5467
},
5568
});
5669

packages/devextreme/js/__internal/ui/tree_view/m_tree_view.base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1441,7 +1441,7 @@ const TreeViewBase = (HierarchicalCollectionWidget as any).inherit({
14411441

14421442
if (this._showCheckboxes()) {
14431443
const parentValue = parentNode.internalFields.selected;
1444-
this._getCheckBoxInstance($parentNode).option('value', parentValue);
1444+
this._getCheckBoxInstance($parentNode)?.option('value', parentValue);
14451445
this._toggleSelectedClass($parentNode, parentValue);
14461446
}
14471447

packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3683,6 +3683,54 @@ QUnit.module('regressions', moduleSetup, () => {
36833683

36843684
assert.equal(count, 1);
36853685
});
3686+
3687+
QUnit.test('Selection: item selected correctly on async render with tree structure (T1269855)', function(assert) {
3688+
this.clock.restore();
3689+
const done = assert.async();
3690+
3691+
const data = [
3692+
{ id: 1, name: 'Item 1_1', group: 'group_1' },
3693+
{ id: 2, name: 'Item 1_2', group: 'group_1' },
3694+
{ id: 3, name: 'Item 1_3', group: 'group_1' },
3695+
{ id: 4, name: 'Item 2_1', group: 'group_2' },
3696+
];
3697+
3698+
const dataSource = new DataSource({
3699+
store: new ArrayStore({ data, key: 'id' }),
3700+
group: 'group',
3701+
});
3702+
3703+
const instance = new List($('#list'), {
3704+
dataSource,
3705+
grouped: true,
3706+
templatesRenderAsynchronously: true,
3707+
integrationOptions: {
3708+
templates: {
3709+
'item': {
3710+
render: function({ model, container, onRendered }) {
3711+
setTimeout(function() {
3712+
const $item = $(`<div>${model.name}</div>`);
3713+
$item.appendTo(container);
3714+
3715+
onRendered();
3716+
}, 100);
3717+
}
3718+
},
3719+
}
3720+
},
3721+
selectionMode: 'single',
3722+
selectedItemKeys: [data[0].id],
3723+
});
3724+
3725+
instance.option('_onItemsRendered', () => {
3726+
const listElement = instance.element();
3727+
const $firstGroup = $(listElement).find(`.${LIST_GROUP_CLASS}`).eq(0);
3728+
const $firstItemInFirstGroup = $firstGroup.find(`.${LIST_ITEM_CLASS}`).eq(0);
3729+
3730+
assert.ok($firstItemInFirstGroup.hasClass(LIST_ITEM_SELECTED_CLASS), 'First item in first group should be selected');
3731+
done();
3732+
});
3733+
});
36863734
});
36873735

36883736
QUnit.module('widget sizing render', {}, () => {
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import $ from 'jquery';
2+
import TreeView from 'ui/tree_view';
3+
4+
import 'generic_light.css!';
5+
6+
const { testStart } = QUnit;
7+
8+
testStart(function() {
9+
const markup = '<div id="treeView"></div>';
10+
11+
$('#qunit-fixture').html(markup);
12+
});
13+
14+
const asyncTemplateRenderTimeout = 50;
15+
16+
const CHECKBOX_CLASS = 'dx-checkbox';
17+
const CHECKBOX_CHECKED_CLASS = 'dx-checkbox-checked';
18+
const CHECKBOX_INDETERMINATE_CLASS = 'dx-checkbox-indeterminate';
19+
const TREEVIEW_ROOT_NODE_CLASS = 'dx-treeview-root-node';
20+
21+
QUnit.module('Async render', () => {
22+
['normal', 'selectAll'].forEach((showCheckBoxesMode) => {
23+
QUnit.test(`TreeView checkboxed should be correctly rendered in async mode. checkboxMode: ${showCheckBoxesMode} (T1269855)`, function(assert) {
24+
const done = assert.async();
25+
26+
const data = [
27+
{
28+
id: 1,
29+
text: 'Item 1',
30+
expanded: true,
31+
selected: true,
32+
items: [
33+
{
34+
id: 12, text: 'Nested Item 2', expanded: true, items: [
35+
{ id: 121, text: 'Third level item 1' },
36+
{ id: 122, text: 'Third level item 2' }
37+
]
38+
}
39+
]
40+
},
41+
{
42+
id: 2,
43+
text: 'Item 2',
44+
expanded: true,
45+
items: [
46+
{
47+
id: 22, text: 'Nested Item 2', expanded: true, items: [
48+
{ id: 221, text: 'Third level item 1' },
49+
{ id: 222, text: 'Third level item 2', selected: true }
50+
]
51+
}
52+
]
53+
},
54+
{
55+
id: 3,
56+
text: 'Item 3',
57+
expanded: true,
58+
items: [
59+
{
60+
id: 33, text: 'Nested Item 3', expanded: true, items: [
61+
{ id: 331, text: 'Third level item 1' },
62+
{ id: 332, text: 'Third level item 2' }
63+
]
64+
}
65+
]
66+
}
67+
];
68+
69+
const instance = new TreeView($('#treeView'), {
70+
items: data,
71+
showCheckBoxesMode,
72+
templatesRenderAsynchronously: true,
73+
itemTemplate: 'myTemplate',
74+
integrationOptions: {
75+
templates: {
76+
myTemplate: {
77+
render({ model, container, onRendered }) {
78+
setTimeout(() => {
79+
const $item = $(`<div>${model.text}</div>`);
80+
$item.appendTo(container);
81+
82+
onRendered();
83+
});
84+
}
85+
}
86+
}
87+
},
88+
});
89+
90+
setTimeout(() => {
91+
const element = instance.itemsContainer();
92+
const $treeRootNodes = $(element).find(`.${TREEVIEW_ROOT_NODE_CLASS}`);
93+
94+
const $firstRootNode = $treeRootNodes.eq(0);
95+
const $firstGroupCheckboxes = $firstRootNode.find(`.${CHECKBOX_CLASS}`);
96+
97+
assert.ok($firstGroupCheckboxes.eq(0).hasClass(CHECKBOX_CHECKED_CLASS), 'First group root checkbox has selected class');
98+
assert.ok($firstGroupCheckboxes.eq(1).hasClass(CHECKBOX_CHECKED_CLASS), 'First group nested node checkbox has selected class');
99+
assert.ok($firstGroupCheckboxes.eq(2).hasClass(CHECKBOX_CHECKED_CLASS), 'First group leaf node 1 checkbox has selected class');
100+
assert.ok($firstGroupCheckboxes.eq(3).hasClass(CHECKBOX_CHECKED_CLASS), 'First group leaf node 2 checkbox has selected class');
101+
102+
const $secondRootNode = $treeRootNodes.eq(1);
103+
const $secondGroupCheckboxes = $secondRootNode.find(`.${CHECKBOX_CLASS}`);
104+
105+
assert.ok($secondGroupCheckboxes.eq(0).hasClass(CHECKBOX_INDETERMINATE_CLASS), 'Second group root checkbox has indeterminate class');
106+
assert.ok($secondGroupCheckboxes.eq(1).hasClass(CHECKBOX_INDETERMINATE_CLASS), 'Second group nested node checkbox has indeterminate class');
107+
assert.notOk($secondGroupCheckboxes.eq(2).hasClass(CHECKBOX_CHECKED_CLASS), 'Second group leaf node 1 checkbox has not selected class');
108+
assert.ok($secondGroupCheckboxes.eq(3).hasClass(CHECKBOX_CHECKED_CLASS), 'Second group leaf node 2 checkbox has selected class');
109+
110+
const $thirdRootNode = $treeRootNodes.eq(2);
111+
const $thirdGroupCheckboxes = $thirdRootNode.find(`.${CHECKBOX_CLASS}`);
112+
113+
assert.notOk($thirdGroupCheckboxes.eq(0).hasClass(CHECKBOX_CHECKED_CLASS), 'Third group root checkbox has not selected class');
114+
assert.notOk($thirdGroupCheckboxes.eq(1).hasClass(CHECKBOX_CHECKED_CLASS), 'Third group nested node checkbox has not selected class');
115+
assert.notOk($thirdGroupCheckboxes.eq(2).hasClass(CHECKBOX_CHECKED_CLASS), 'Third group leaf node 1 checkbox has not selected class');
116+
assert.notOk($thirdGroupCheckboxes.eq(3).hasClass(CHECKBOX_CHECKED_CLASS), 'Third group leaf node 2 checkbox has not selected class');
117+
118+
done();
119+
}, asyncTemplateRenderTimeout);
120+
});
121+
});
122+
});

0 commit comments

Comments
 (0)