Skip to content

Commit

Permalink
CollectionAsync: use unique string keys to index _asyncTemplateItems …
Browse files Browse the repository at this point in the history
…(T1269855) (#28630)
  • Loading branch information
anna-shakhova committed Dec 27, 2024
1 parent ef2202f commit 4586736
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 15 deletions.
6 changes: 0 additions & 6 deletions apps/demos/testing/common.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,6 @@ const SKIPPED_TESTS = {
],
},
React: {
Common: [
{ demo: 'ActionAndListsOverview', themes: [THEME.generic, THEME.material] },
],
Charts: [
{ demo: 'PiesWithEqualSize', themes: [THEME.material] },
{ demo: 'CustomAnnotations', themes: [THEME.material] },
Expand Down Expand Up @@ -182,9 +179,6 @@ const SKIPPED_TESTS = {
],
},
Vue: {
Common: [
{ demo: 'ActionAndListsOverview', themes: [THEME.generic, THEME.material] },
],
Charts: [
{ demo: 'TilingAlgorithms', themes: [THEME.material] },
{ demo: 'ExportAndPrintingAPI', themes: [THEME.material] },
Expand Down
1 change: 1 addition & 0 deletions packages/devextreme/js/__internal/ui/collection/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ declare class Base<
container: dxElementWrapper;
contentClass: string;
defaultTemplateName: string;
uniqueKey?: string;
}): dxElementWrapper;
_renderContent(): void;
_postprocessRenderItem(args: unknown): void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Guid from '@js/core/guid';
import { noop } from '@js/core/utils/common';
import type { DeferredObj } from '@js/core/utils/deferred';
import { Deferred, when } from '@js/core/utils/deferred';

import CollectionWidgetEdit from './m_collection_widget.edit';

const AsyncCollectionWidget = CollectionWidgetEdit.inherit({
_initMarkup() {
this._asyncTemplateItems = [];
this._asyncTemplateItemsMap = {};
this.callBase();
},

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

this._asyncTemplateItems[args.index] = itemDeferred;
const $itemContent = this.callBase(args);
this._asyncTemplateItemsMap[uniqueKey] = itemDeferred;
const $itemContent = this.callBase({ ...args, uniqueKey });

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

_onItemTemplateRendered(itemTemplate, renderArgs) {
return () => {
this._asyncTemplateItems[renderArgs.index]?.resolve();
this._asyncTemplateItemsMap[renderArgs.uniqueKey]?.resolve();
};
},

_postProcessRenderItems: noop,

_planPostRenderActions(...args: unknown[]) {
const d = Deferred();
when.apply(this, this._asyncTemplateItems).done(() => {
const asyncTemplateItems = Object.values<DeferredObj<unknown>>(this._asyncTemplateItemsMap);

when.apply(this, asyncTemplateItems).done(() => {
this._postProcessRenderItems(...args);
d.resolve();

d.resolve().done(() => {
this._asyncTemplateItemsMap = {};
});
});

return d.promise();
},

_clean() {
this.callBase();
this._asyncTemplateItems.forEach((item) => {

const asyncTemplateItems = Object.values<DeferredObj<unknown>>(this._asyncTemplateItemsMap);

asyncTemplateItems.forEach((item) => {
item.reject();
});
this._asyncTemplateItems = [];

this._asyncTemplateItemsMap = {};
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1441,7 +1441,7 @@ const TreeViewBase = (HierarchicalCollectionWidget as any).inherit({

if (this._showCheckboxes()) {
const parentValue = parentNode.internalFields.selected;
this._getCheckBoxInstance($parentNode).option('value', parentValue);
this._getCheckBoxInstance($parentNode)?.option('value', parentValue);
this._toggleSelectedClass($parentNode, parentValue);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3683,6 +3683,54 @@ QUnit.module('regressions', moduleSetup, () => {

assert.equal(count, 1);
});

QUnit.test('Selection: item selected correctly on async render with tree structure (T1269855)', function(assert) {
this.clock.restore();
const done = assert.async();

const data = [
{ id: 1, name: 'Item 1_1', group: 'group_1' },
{ id: 2, name: 'Item 1_2', group: 'group_1' },
{ id: 3, name: 'Item 1_3', group: 'group_1' },
{ id: 4, name: 'Item 2_1', group: 'group_2' },
];

const dataSource = new DataSource({
store: new ArrayStore({ data, key: 'id' }),
group: 'group',
});

const instance = new List($('#list'), {
dataSource,
grouped: true,
templatesRenderAsynchronously: true,
integrationOptions: {
templates: {
'item': {
render: function({ model, container, onRendered }) {
setTimeout(function() {
const $item = $(`<div>${model.name}</div>`);
$item.appendTo(container);

onRendered();
}, 100);
}
},
}
},
selectionMode: 'single',
selectedItemKeys: [data[0].id],
});

instance.option('_onItemsRendered', () => {
const listElement = instance.element();
const $firstGroup = $(listElement).find(`.${LIST_GROUP_CLASS}`).eq(0);
const $firstItemInFirstGroup = $firstGroup.find(`.${LIST_ITEM_CLASS}`).eq(0);

assert.ok($firstItemInFirstGroup.hasClass(LIST_ITEM_SELECTED_CLASS), 'First item in first group should be selected');
done();
});
});
});

QUnit.module('widget sizing render', {}, () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import $ from 'jquery';
import TreeView from 'ui/tree_view';

import 'generic_light.css!';

const { testStart } = QUnit;

testStart(function() {
const markup = '<div id="treeView"></div>';

$('#qunit-fixture').html(markup);
});

const asyncTemplateRenderTimeout = 50;

const CHECKBOX_CLASS = 'dx-checkbox';
const CHECKBOX_CHECKED_CLASS = 'dx-checkbox-checked';
const CHECKBOX_INDETERMINATE_CLASS = 'dx-checkbox-indeterminate';
const TREEVIEW_ROOT_NODE_CLASS = 'dx-treeview-root-node';

QUnit.module('Async render', () => {
['normal', 'selectAll'].forEach((showCheckBoxesMode) => {
QUnit.test(`TreeView checkboxed should be correctly rendered in async mode. checkboxMode: ${showCheckBoxesMode} (T1269855)`, function(assert) {
const done = assert.async();

const data = [
{
id: 1,
text: 'Item 1',
expanded: true,
selected: true,
items: [
{
id: 12, text: 'Nested Item 2', expanded: true, items: [
{ id: 121, text: 'Third level item 1' },
{ id: 122, text: 'Third level item 2' }
]
}
]
},
{
id: 2,
text: 'Item 2',
expanded: true,
items: [
{
id: 22, text: 'Nested Item 2', expanded: true, items: [
{ id: 221, text: 'Third level item 1' },
{ id: 222, text: 'Third level item 2', selected: true }
]
}
]
},
{
id: 3,
text: 'Item 3',
expanded: true,
items: [
{
id: 33, text: 'Nested Item 3', expanded: true, items: [
{ id: 331, text: 'Third level item 1' },
{ id: 332, text: 'Third level item 2' }
]
}
]
}
];

const instance = new TreeView($('#treeView'), {
items: data,
showCheckBoxesMode,
templatesRenderAsynchronously: true,
itemTemplate: 'myTemplate',
integrationOptions: {
templates: {
myTemplate: {
render({ model, container, onRendered }) {
setTimeout(() => {
const $item = $(`<div>${model.text}</div>`);
$item.appendTo(container);

onRendered();
});
}
}
}
},
});

setTimeout(() => {
const element = instance.itemsContainer();
const $treeRootNodes = $(element).find(`.${TREEVIEW_ROOT_NODE_CLASS}`);

const $firstRootNode = $treeRootNodes.eq(0);
const $firstGroupCheckboxes = $firstRootNode.find(`.${CHECKBOX_CLASS}`);

assert.ok($firstGroupCheckboxes.eq(0).hasClass(CHECKBOX_CHECKED_CLASS), 'First group root checkbox has selected class');
assert.ok($firstGroupCheckboxes.eq(1).hasClass(CHECKBOX_CHECKED_CLASS), 'First group nested node checkbox has selected class');
assert.ok($firstGroupCheckboxes.eq(2).hasClass(CHECKBOX_CHECKED_CLASS), 'First group leaf node 1 checkbox has selected class');
assert.ok($firstGroupCheckboxes.eq(3).hasClass(CHECKBOX_CHECKED_CLASS), 'First group leaf node 2 checkbox has selected class');

const $secondRootNode = $treeRootNodes.eq(1);
const $secondGroupCheckboxes = $secondRootNode.find(`.${CHECKBOX_CLASS}`);

assert.ok($secondGroupCheckboxes.eq(0).hasClass(CHECKBOX_INDETERMINATE_CLASS), 'Second group root checkbox has indeterminate class');
assert.ok($secondGroupCheckboxes.eq(1).hasClass(CHECKBOX_INDETERMINATE_CLASS), 'Second group nested node checkbox has indeterminate class');
assert.notOk($secondGroupCheckboxes.eq(2).hasClass(CHECKBOX_CHECKED_CLASS), 'Second group leaf node 1 checkbox has not selected class');
assert.ok($secondGroupCheckboxes.eq(3).hasClass(CHECKBOX_CHECKED_CLASS), 'Second group leaf node 2 checkbox has selected class');

const $thirdRootNode = $treeRootNodes.eq(2);
const $thirdGroupCheckboxes = $thirdRootNode.find(`.${CHECKBOX_CLASS}`);

assert.notOk($thirdGroupCheckboxes.eq(0).hasClass(CHECKBOX_CHECKED_CLASS), 'Third group root checkbox has not selected class');
assert.notOk($thirdGroupCheckboxes.eq(1).hasClass(CHECKBOX_CHECKED_CLASS), 'Third group nested node checkbox has not selected class');
assert.notOk($thirdGroupCheckboxes.eq(2).hasClass(CHECKBOX_CHECKED_CLASS), 'Third group leaf node 1 checkbox has not selected class');
assert.notOk($thirdGroupCheckboxes.eq(3).hasClass(CHECKBOX_CHECKED_CLASS), 'Third group leaf node 2 checkbox has not selected class');

done();
}, asyncTemplateRenderTimeout);
});
});
});

0 comments on commit 4586736

Please sign in to comment.