Skip to content

Commit

Permalink
Menu: Items in adaptive mode should have links if item.url is set (T1…
Browse files Browse the repository at this point in the history
…181342) (#25484)
  • Loading branch information
AlexanderMoiseev authored Aug 31, 2023
1 parent 9ca0740 commit a9bbb83
Show file tree
Hide file tree
Showing 28 changed files with 243 additions and 45 deletions.
24 changes: 8 additions & 16 deletions js/ui/context_menu/ui.menu_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const SINGLE_SELECTION_MODE = 'single';
const DEFAULT_DELAY = { 'show': 50, 'hide': 300 };
const DX_MENU_ITEM_CAPTION_URL_CLASS = `${DX_MENU_ITEM_CAPTION_CLASS}-with-url`;
const DX_ICON_WITH_URL_CLASS = 'dx-icon-with-url';
const DX_ITEM_URL_CLASS = 'dx-item-url';
const ITEM_URL_CLASS = 'dx-item-url';


class MenuBase extends HierarchicalCollectionWidget {
Expand Down Expand Up @@ -183,29 +183,21 @@ class MenuBase extends HierarchicalCollectionWidget {
_getLinkContainer(iconContainer, textContainer, { linkAttr, url }) {
iconContainer?.addClass(DX_ICON_WITH_URL_CLASS);
textContainer?.addClass(DX_MENU_ITEM_CAPTION_URL_CLASS);
const linkAttributes = isObject(linkAttr) ? linkAttr : {};
return $('<a>')
.addClass(DX_ITEM_URL_CLASS)
.attr({ ...linkAttributes, href: url })
.append(iconContainer)
.append(textContainer);

return super._getLinkContainer(iconContainer, textContainer, { linkAttr, url });
}

_addContent($container, itemData) {
const { html, url } = itemData;

const iconContainer = this._getIconContainer(itemData);
const textContainer = this._getTextContainer(itemData);

$container.html(html);
if(url) {
const link = this._getLinkContainer(iconContainer, textContainer, itemData);
$container.html(html);
const link = this._getLinkContainer(this._getIconContainer(itemData), this._getTextContainer(itemData), itemData);
$container.append(link);
} else {
$container
.append(iconContainer)
.append(textContainer);
super._addContent($container, itemData);
}

$container.append(this._getPopoutContainer(itemData));
this._addContentClasses(itemData, $container.parent());
}
Expand Down Expand Up @@ -561,7 +553,7 @@ class MenuBase extends HierarchicalCollectionWidget {

_itemClick(actionArgs) {
const args = actionArgs.args[0];
const link = args.event.target.getElementsByClassName(DX_ITEM_URL_CLASS)[0];
const link = args.event.target.getElementsByClassName(ITEM_URL_CLASS)[0];
if(args.itemData.url && link) {
link.click();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { getImageContainer } from '../../core/utils/icon';
import HierarchicalDataAdapter from './ui.data_adapter';
import CollectionWidget from '../collection/ui.collection_widget.edit';
import { BindableTemplate } from '../../core/templates/bindable_template';
import { isFunction } from '../../core/utils/type';
import { isFunction, isObject } from '../../core/utils/type';
import { noop } from '../../core/utils/common';

const DISABLED_STATE_CLASS = 'dx-state-disabled';
const ITEM_URL_CLASS = 'dx-item-url';

const HierarchicalCollectionWidget = CollectionWidget.inherit({

Expand Down Expand Up @@ -94,6 +95,15 @@ const HierarchicalCollectionWidget = CollectionWidget.inherit({
.append(this._getTextContainer(itemData));
},

_getLinkContainer: function(iconContainer, textContainer, { linkAttr, url }) {
const linkAttributes = isObject(linkAttr) ? linkAttr : {};
return $('<a>')
.addClass(ITEM_URL_CLASS)
.attr({ ...linkAttributes, href: url })
.append(iconContainer)
.append(textContainer);
},

_getIconContainer: function(itemData) {
return itemData.icon ? getImageContainer(itemData.icon) : undefined;
},
Expand Down
34 changes: 32 additions & 2 deletions js/ui/tree_view/ui.tree_view.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const DISABLED_STATE_CLASS = 'dx-state-disabled';
const SELECTED_ITEM_CLASS = 'dx-state-selected';
const EXPAND_EVENT_NAMESPACE = 'dxTreeView_expand';
const DATA_ITEM_ID = 'data-item-id';
const ITEM_URL_CLASS = 'dx-item-url';

const TreeViewBase = HierarchicalCollectionWidget.inherit({

Expand Down Expand Up @@ -769,6 +770,22 @@ const TreeViewBase = HierarchicalCollectionWidget.inherit({
return deferred.promise();
},

_getItemExtraPropNames() {
return ['url', 'linkAttr'];
},

_addContent: function($container, itemData) {
const { html, url } = itemData;

if(url) {
$container.html(html);
const link = this._getLinkContainer(this._getIconContainer(itemData), this._getTextContainer(itemData), itemData);
$container.append(link);
} else {
this.callBase($container, itemData);
}
},

_renderSublevel: function($node, node, childNodes) {
const $nestedNodeContainer = this._renderNodeContainer($node, node);

Expand Down Expand Up @@ -1415,11 +1432,24 @@ const TreeViewBase = HierarchicalCollectionWidget.inherit({
});
},

_itemClick: function(actionArgs) {
const args = actionArgs.args[0];
const target = args.event.target[0] || args.event.target;
const link = target.getElementsByClassName(ITEM_URL_CLASS)[0];

if(args.itemData.url && link) {
link.click();
}
},

_itemClickHandler: function(e, $item) {
const itemData = this._getItemData($item);
const node = this._getNodeByElement($item);

this._itemDXEventHandler(e, 'onItemClick', { node: this._dataAdapter.getPublicNode(node) });
this._itemDXEventHandler(e, 'onItemClick', {
node: this._dataAdapter.getPublicNode(node),
}, {
beforeExecute: this._itemClick,
});

if(this.option('selectByClick') && !e.isDefaultPrevented()) {
this._updateItemSelection(!node.internalFields.selected, itemData, e);
Expand Down
24 changes: 13 additions & 11 deletions scss/widgets/base/treeView/_common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,16 @@ $tree-view-icon-size: 24px;
display: block;
cursor: pointer;

.dx-treeview-item-content > .dx-icon {
display: inline-block;
width: $tree-view-icon-size;
height: $tree-view-icon-size;
vertical-align: middle;
margin-right: 5px;
background-size: $tree-view-icon-size $tree-view-icon-size;
}

.dx-treeview-item-content {
.dx-icon {
display: inline-block;
width: $tree-view-icon-size;
height: $tree-view-icon-size;
vertical-align: middle;
margin-right: 5px;
background-size: $tree-view-icon-size $tree-view-icon-size;
}

span {
vertical-align: middle;
}
Expand Down Expand Up @@ -127,8 +127,10 @@ $tree-view-icon-size: 24px;
}

.dx-treeview-item {
.dx-treeview-item-content > .dx-icon {
margin-right: 0;
.dx-treeview-item-content {
.dx-icon {
margin-right: 0;
}
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions scss/widgets/base/treeView/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@
}

.dx-treeview-item {
.dx-treeview-item-content > .dx-icon {
margin-left: 5px;
.dx-treeview-item-content {
.dx-icon {
margin-left: 5px;
}
}
}

Expand Down
12 changes: 10 additions & 2 deletions scss/widgets/generic/treeView/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ $generic-treeview-toggle-item-visibility-offset: -4px;
> .dx-treeview-item {
background-color: $treeview-focused-bg;
color: $treeview-focus-color;

.dx-item-content {
.dx-item-url {
color: unset;
}
}
}
}
}
Expand All @@ -150,8 +156,10 @@ $generic-treeview-toggle-item-visibility-offset: -4px;
padding: $generic-treeview-item-padding;
min-height: $generic-treeview-min-item-height;

.dx-treeview-item-content > .dx-icon {
@include dx-icon-sizing($generic-base-icon-size);
.dx-treeview-item-content {
.dx-icon {
@include dx-icon-sizing($generic-base-icon-size);
}
}

&.dx-state-hover {
Expand Down
6 changes: 4 additions & 2 deletions scss/widgets/material/treeView/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,10 @@ $material-treeview-item-with-checkbox-offset: $material-treeview-checkbox-offset
min-height: $material-treeview-min-item-height;
line-height: math.div($material-treeview-min-item-height, 2) - 2;

.dx-treeview-item-content > .dx-icon {
@include dx-icon-sizing($material-base-icon-size);
.dx-treeview-item-content {
.dx-icon {
@include dx-icon-sizing($material-base-icon-size);
}
}

&.dx-state-hover {
Expand Down
11 changes: 9 additions & 2 deletions testing/testcafe/model/menu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ import ContextMenu from '../contextMenu';
const CLASS = {
menu: 'dx-menu',
item: 'dx-menu-item',
adaptiveItem: 'dx-treeview-item',
contextMenu: 'dx-context-menu',
hamburgerButton: 'dx-menu-hamburger-button',
};

export default class Menu extends Widget {
items: Selector;

constructor() {
constructor(adaptivityEnabled = false) {
super(`.${CLASS.menu}`);

this.items = Selector(`.${CLASS.item}`).filterVisible();
const itemClass = adaptivityEnabled ? `.${CLASS.adaptiveItem}` : `.${CLASS.item}`;
this.items = Selector(itemClass).filterVisible();
}

// eslint-disable-next-line class-methods-use-this
Expand All @@ -25,6 +28,10 @@ export default class Menu extends Widget {
return this.items.nth(index);
}

getHamburgerButton(): Selector {
return this.element.find(`.${CLASS.hamburgerButton}`);
}

// eslint-disable-next-line class-methods-use-this
isElementFocused(element: Selector): Promise<boolean> {
return element.hasClass('dx-state-focused');
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 70 additions & 0 deletions testing/testcafe/tests/navigation/menu/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,73 @@ test('Items should have links if item.url is set', async (t) => {
}],
}, '#menu');
});

test('Adaptive mode: items should have links if item.url is set', async (t) => {
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
const menu = new Menu(true);

await t.click(menu.getHamburgerButton())
.click(menu.items(0));

await testScreenshot(t, takeScreenshot, 'Items with links.png', { element: '#container' });

await t.pressKey('down');

await testScreenshot(t, takeScreenshot, 'Items without links.png', { element: '#container' });

await t
.pressKey('down')
.pressKey('down');

await testScreenshot(t, takeScreenshot, 'Items with link and icon focus.png', { element: '#container' });

await t.pressKey('down');

await testScreenshot(t, takeScreenshot, 'Items mode with link focus.png', { element: '#container' });

await t
.expect(compareResults.isValid())
.ok(compareResults.errorMessages());
}).before(async () => {
await appendElementTo('#container', 'div', 'menu');

await setAttribute('#container', 'style', 'width: 200px; height: 400px;');

return createWidget('dxMenu', {
displayExpr: 'name',
adaptivityEnabled: true,
items: [{
id: '1',
name: 'Items',
items: [{
id: '1-1',
name: 'Item 1',
}, {
id: '1-2',
icon: 'more',
}, {
id: '1-3',
name: 'Item 2',
icon: 'unlock',
url: 'https://js.devexpress.com/',
}, {
id: '1-4',
name: 'Item 3',
url: 'https://js.devexpress.com/',
}],
},
{
id: '2',
name: 'Items',
},
{
id: '3',
name: 'Items',
},
{
id: '4',
name: 'Items',
},
],
}, '#menu');
});
Loading

0 comments on commit a9bbb83

Please sign in to comment.