Skip to content

Commit deb6685

Browse files
authored
feat(ui5-menu): provide busy indicator display (#6866)
* feat(ui5-menu): provide busy indicator display New property "busyIndicator" has been introduced. Through this property the application developers will be able to configure a busy indicator display for a particular ui5-menu DOM representation. That way there will be a busy indicator while the corresponding menu item texts are getting fetched from a database. Fixes: #6131
1 parent de73d48 commit deb6685

File tree

7 files changed

+247
-14
lines changed

7 files changed

+247
-14
lines changed

packages/main/src/Menu.hbs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,12 @@
5050
<ui5-list
5151
id="{{_id}}-menu-list"
5252
mode="None"
53+
?busy="{{busy}}"
54+
busy-delay="{{busyDelay}}"
5355
separators="None"
5456
accessible-role="menu"
5557
@ui5-item-click={{_itemClick}}
58+
@mouseover="{{_busyMouseOver}}"
5659
>
5760
{{#each _currentItems}}
5861
<ui5-li
@@ -79,7 +82,7 @@
7982
</div>
8083
{{/if}}
8184
{{this.item.text}}
82-
{{#if this.item.hasChildren}}
85+
{{#if this.item.hasSubmenu}}
8386
<ui5-icon
8487
part="icon"
8588
name="slim-arrow-right"
@@ -95,6 +98,14 @@
9598
</ui5-li>
9699
{{/each}}
97100
</ui5-list>
101+
{{else if busy}}
102+
<ui5-busy-indicator
103+
id="{{_id}}-menu-busy-indicator"
104+
delay="{{busyDelay}}"
105+
class="ui5-menu-busy-indicator"
106+
@mouseover="{{_busyMouseOver}}"
107+
active>
108+
</ui5-busy-indicator>
98109
{{/if}}
99110
</div>
100111
</ui5-responsive-popover>

packages/main/src/Menu.ts

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
1818
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
1919
import "@ui5/webcomponents-icons/dist/slim-arrow-right.js";
2020
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
21+
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
2122
import ResponsivePopover from "./ResponsivePopover.js";
2223
import type { ResponsivePopoverBeforeCloseEventDetail } from "./ResponsivePopover.js";
2324
import Button from "./Button.js";
2425
import List from "./List.js";
2526
import StandardListItem from "./StandardListItem.js";
2627
import Icon from "./Icon.js";
28+
import BusyIndicator from "./BusyIndicator.js";
2729
import type MenuItem from "./MenuItem.js";
2830
import type { ClickEventDetail } from "./List.js";
2931
import staticAreaMenuTemplate from "./generated/templates/MenuTemplate.lit.js";
@@ -92,6 +94,7 @@ type OpenerStandardListItem = StandardListItem & { associatedItem: MenuItem };
9294
List,
9395
StandardListItem,
9496
Icon,
97+
BusyIndicator,
9598
],
9699
})
97100

@@ -180,6 +183,30 @@ class Menu extends UI5Element {
180183
@property({ type: Boolean })
181184
open!:boolean;
182185

186+
/**
187+
* Defines if a loading indicator would be displayed inside the corresponding ui5-menu popover.
188+
*
189+
* @name sap.ui.webc.main.Menu.prototype.busy
190+
* @type {boolean}
191+
* @defaultvalue false
192+
* @public
193+
* @since 1.13.0
194+
*/
195+
@property({ type: Boolean })
196+
busy!: boolean;
197+
198+
/**
199+
* Defines the delay in milliseconds, after which the busy indicator will be displayed inside the corresponding ui5-menu popover..
200+
*
201+
* @name sap.ui.webc.main.Menu.prototype.busyDelay
202+
* @type {sap.ui.webc.base.types.Integer}
203+
* @defaultValue 1000
204+
* @public
205+
* @since 1.13.0
206+
*/
207+
@property({ validator: Integer, defaultValue: 1000 })
208+
busyDelay!: number;
209+
183210
/**
184211
* Defines the ID or DOM Reference of the element that the menu is shown at
185212
*
@@ -315,6 +342,10 @@ class Menu extends UI5Element {
315342
this._currentItems.forEach(item => {
316343
item.item._siblingsWithChildren = itemsWithChildren;
317344
item.item._siblingsWithIcon = itemsWithIcon;
345+
if (item.item._subMenu) {
346+
item.item._subMenu.busy = item.item.busy;
347+
item.item._subMenu.busyDelay = item.item.busyDelay;
348+
}
318349
});
319350
}
320351

@@ -398,7 +429,7 @@ class Menu extends UI5Element {
398429
return {
399430
item,
400431
position: index + 1,
401-
ariaHasPopup: item.hasChildren ? "menu" : undefined,
432+
ariaHasPopup: item.hasSubmenu ? "menu" : undefined,
402433
};
403434
});
404435
}
@@ -411,6 +442,8 @@ class Menu extends UI5Element {
411442
subMenu._isSubMenu = true;
412443
subMenu.setAttribute("id", `submenu-${openerId}`);
413444
subMenu._parentMenuItem = item;
445+
subMenu.busy = item.busy;
446+
subMenu.busyDelay = item.busyDelay;
414447
const subItems = item.children;
415448
let clonedItem,
416449
idx;
@@ -456,11 +489,11 @@ class Menu extends UI5Element {
456489
}
457490

458491
_prepareSubMenuDesktopTablet(item: MenuItem, opener: HTMLElement, actionId: string) {
459-
if (actionId !== this._subMenuOpenerId || (item && item.hasChildren)) {
492+
if (actionId !== this._subMenuOpenerId || (item && item.hasSubmenu)) {
460493
// close opened sub-menu if there is any opened
461494
this._closeItemSubMenu(this._openedSubMenuItem!, true);
462495
}
463-
if (item && item.hasChildren) {
496+
if (item && item.hasSubmenu) {
464497
// create new sub-menu
465498
this._createSubMenu(item, actionId);
466499
this._openItemSubMenu(item, opener, actionId);
@@ -488,13 +521,19 @@ class Menu extends UI5Element {
488521
}
489522
}
490523

524+
_busyMouseOver() {
525+
if (this._parentMenuItem) {
526+
this._parentMenuItem._preventSubMenuClose = true;
527+
}
528+
}
529+
491530
_itemMouseOut(e: MouseEvent) {
492531
if (isDesktop()) {
493532
// respect mouseover only on desktop
494533
const opener = e.target as OpenerStandardListItem;
495534
const item = opener.associatedItem;
496535

497-
if (item && item.hasChildren && item._subMenu) {
536+
if (item && item.hasSubmenu && item._subMenu) {
498537
// try to close the sub-menu
499538
item._preventSubMenuClose = false;
500539
this._closeItemSubMenu(item);
@@ -514,7 +553,7 @@ class Menu extends UI5Element {
514553
const item = opener.associatedItem;
515554
const hoverId = opener.getAttribute("id")!;
516555

517-
item.hasChildren && this._prepareSubMenuDesktopTablet(item, opener, hoverId);
556+
item.hasSubmenu && this._prepareSubMenuDesktopTablet(item, opener, hoverId);
518557
} else if (isMenuClose && this._isSubMenu && this._parentMenuItem) {
519558
const parentMenuItemParent = this._parentMenuItem.parentElement as Menu;
520559
parentMenuItemParent._closeItemSubMenu(this._parentMenuItem, true);
@@ -526,7 +565,7 @@ class Menu extends UI5Element {
526565
const item = opener.associatedItem;
527566
const actionId = opener.getAttribute("id")!;
528567

529-
if (!item.hasChildren) {
568+
if (!item.hasSubmenu) {
530569
// click on an item that doesn't have sub-items fires an "item-click" event
531570
if (!this._isSubMenu) {
532571
if (isPhone()) {

packages/main/src/MenuItem.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
22
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
33
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
44
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
5+
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
56
import type Menu from "./Menu.js";
67

78
/**
@@ -96,6 +97,32 @@ class MenuItem extends UI5Element {
9697
@property({ type: Boolean })
9798
disabled!: boolean;
9899

100+
/**
101+
* Defines the delay in milliseconds, after which the busy indicator will be displayed inside the corresponding ui5-menu popover.
102+
*
103+
* Note: If set to <code>true</code> a <code>ui5-busy-indicator</code> component will be displayed into the related one to the current <code>ui5-menu-item</code> sub-menu popover.
104+
*
105+
* @name sap.ui.webc.main.MenuItem.prototype.busy
106+
* @type {boolean}
107+
* @defaultvalue false
108+
* @public
109+
* @since 1.13.0
110+
*/
111+
@property({ type: Boolean })
112+
busy!: boolean;
113+
114+
/**
115+
* Defines the delay in milliseconds, after which the busy indicator will be displayed inside the corresponding ui5-menu popover.
116+
*
117+
* @name sap.ui.webc.main.MenuItem.prototype.busyDelay
118+
* @type {sap.ui.webc.base.types.Integer}
119+
* @defaultValue 1000
120+
* @public
121+
* @since 1.13.0
122+
*/
123+
@property({ validator: Integer, defaultValue: 1000 })
124+
busyDelay!: number;
125+
99126
/**
100127
* Defines the accessible ARIA name of the component.
101128
*
@@ -143,8 +170,8 @@ class MenuItem extends UI5Element {
143170
@slot({ "default": true, type: HTMLElement, invalidateOnChildChange: true })
144171
items!: Array<MenuItem>;
145172

146-
get hasChildren() {
147-
return !!this.items.length;
173+
get hasSubmenu() {
174+
return !!(this.items.length || this.busy);
148175
}
149176

150177
get hasDummyIcon() {
@@ -156,7 +183,7 @@ class MenuItem extends UI5Element {
156183
}
157184

158185
get _additionalText() {
159-
return this.hasChildren ? "" : this.additionalText;
186+
return this.hasSubmenu ? "" : this.additionalText;
160187
}
161188

162189
get ariaLabelledByText() {

packages/main/src/themes/Menu.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
pointer-events: none;
4444
}
4545

46+
.ui5-menu-busy-indicator {
47+
width: 100%;
48+
}
49+
4650
.ui5-menu-dialog-header {
4751
display: flex;
4852
height: var(--_ui5-responsive_popover_header_height);

packages/main/test/pages/Menu.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
<ui5-menu-item text="New File" accessible-name="Opens a file explorer" additional-text="Ctrl+Alt+Shift+N" icon="add-document"></ui5-menu-item>
2020
<ui5-menu-item text="New Folder with very long title for a menu item" additional-text="Ctrl+F" icon="add-folder" disabled></ui5-menu-item>
21-
<ui5-menu-item text="Open" icon="open-folder" starts-section>
21+
<ui5-menu-item text="Open" icon="open-folder" starts-section busy-delay="100" busy>
2222
<ui5-menu-item text="Open Locally" icon="open-folder" additional-text="Ctrl+K">
2323
<ui5-menu-item text="Open from C"></ui5-menu-item>
2424
<ui5-menu-item text="Open from D"></ui5-menu-item>
@@ -34,7 +34,7 @@
3434
</ui5-menu-item>
3535
<ui5-menu-item text="Save on Cloud" icon="upload-to-cloud"></ui5-menu-item>
3636
</ui5-menu-item>
37-
<ui5-menu-item text="Close" additional-text="Ctrl+W"></ui5-menu-item>
37+
<ui5-menu-item text="Close" additional-text="Ctrl+W" busy-delay="100" busy></ui5-menu-item>
3838
<ui5-menu-item text="Preferences" icon="action-settings" starts-section disabled></ui5-menu-item>
3939
<ui5-menu-item text="Exit" icon="journey-arrive"></ui5-menu-item>
4040
</ui5-menu>

packages/main/test/samples/Menu.sample.html

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,130 @@ <h3>Menu with additional text on menu items</h3>
147147
</xmp></pre>
148148
</section>
149149

150+
<section>
151+
<h3>Menu with simulated menu item texts fetching from a database</h3>
152+
<div class="snippet">
153+
<ui5-button id="btnOpenMenuBusy" class="samples-margin">Open Menu</ui5-button> <br/>
154+
<ui5-menu id="menuBusy" busy-delay="100" busy>
155+
</ui5-menu>
156+
<script>
157+
btnOpenMenuBusy.addEventListener("click", function(event) {
158+
menuBusy.showAt(btnOpenMenuBusy);
159+
});
160+
161+
menuBusy.addEventListener("ui5-before-open", function(event) {
162+
setTimeout(function() {
163+
menuBusy.removeAttribute("busy");
164+
let newFileNode = document.createElement("ui5-menu-item");
165+
newFileNode.setAttribute("text", "New File");
166+
newFileNode.setAttribute("icon", "add-document");
167+
newFileNode.setAttribute("additional-text", "Ctrl+N");
168+
menuBusy.appendChild(newFileNode);
169+
let newFolderNode = document.createElement("ui5-menu-item");
170+
newFolderNode.setAttribute("text", "New Folder");
171+
newFolderNode.setAttribute("icon", "add-folder");
172+
newFolderNode.setAttribute("additional-text", "Ctrl+F");
173+
menuBusy.appendChild(newFolderNode);
174+
let openNode = document.createElement("ui5-menu-item");
175+
openNode.setAttribute("text", "Open");
176+
openNode.setAttribute("icon", "open-folder");
177+
openNode.setAttribute("starts-section", "");
178+
menuBusy.appendChild(openNode);
179+
let closeNode = document.createElement("ui5-menu-item");
180+
closeNode.setAttribute("text", "Close");
181+
menuBusy.appendChild(closeNode);
182+
}, 3000);
183+
}, { once: true });
184+
</script>
185+
</div>
186+
<pre class="prettyprint lang-html"><xmp>
187+
<ui5-button id="btnOpenMenuBusy">Open Menu</ui5-button> <br/>
188+
<ui5-menu id="menuBusy" busy-delay="100" busy>
189+
</ui5-menu>
190+
<script>
191+
btnOpenMenuBusy.addEventListener("click", function(event) {
192+
menuBusy.showAt(btnOpenMenuBusy);
193+
});
194+
195+
menuBusy.addEventListener("ui5-before-open", function(event) {
196+
setTimeout(function() {
197+
menuBusy.removeAttribute("busy");
198+
let newFileNode = document.createElement("ui5-menu-item");
199+
newFileNode.setAttribute("text", "New File");
200+
newFileNode.setAttribute("icon", "add-document");
201+
newFileNode.setAttribute("additional-text", "Ctrl+N");
202+
menuBusy.appendChild(newFileNode);
203+
let newFolderNode = document.createElement("ui5-menu-item");
204+
newFolderNode.setAttribute("text", "New Folder");
205+
newFolderNode.setAttribute("icon", "add-folder");
206+
newFolderNode.setAttribute("additional-text", "Ctrl+F");
207+
menuBusy.appendChild(newFolderNode);
208+
let openNode = document.createElement("ui5-menu-item");
209+
openNode.setAttribute("text", "Open");
210+
openNode.setAttribute("icon", "open-folder");
211+
openNode.setAttribute("starts-section", "");
212+
menuBusy.appendChild(openNode);
213+
let closeNode = document.createElement("ui5-menu-item");
214+
closeNode.setAttribute("text", "Close");
215+
menuBusy.appendChild(closeNode);
216+
}, 3000);
217+
}, { once: true });
218+
</script>
219+
</xmp></pre>
220+
</section>
221+
222+
<section>
223+
<h3>Menu with initial menu items and additional menu items fetching from a database</h3>
224+
<div class="snippet">
225+
<ui5-button id="btnMenuListBusy" class="samples-margin">Open Menu</ui5-button> <br/>
226+
<ui5-menu id="menuListBusy" busy-delay="100" busy>
227+
<ui5-menu-item text="New File" icon="add-document" additional-text="Ctrl+N"></ui5-menu-item>
228+
<ui5-menu-item text="New Folder" icon="add-folder" additional-text="Ctrl+F" disabled></ui5-menu-item>
229+
</ui5-menu>
230+
<script>
231+
btnMenuListBusy.addEventListener("click", function(event) {
232+
menuListBusy.showAt(btnMenuListBusy);
233+
});
234+
235+
menuListBusy.addEventListener("ui5-before-open", function(event) {
236+
setTimeout(function() {
237+
menuListBusy.removeAttribute("busy");
238+
let openNode = document.createElement("ui5-menu-item");
239+
openNode.setAttribute("text", "Open");
240+
openNode.setAttribute("icon", "open-folder");
241+
openNode.setAttribute("starts-section", "");
242+
menuListBusy.appendChild(openNode);
243+
let closeNode = document.createElement("ui5-menu-item");
244+
closeNode.setAttribute("text", "Close");
245+
menuListBusy.appendChild(closeNode);
246+
}, 3000);
247+
}, { once: true });
248+
</script>
249+
</div>
250+
<pre class="prettyprint lang-html"><xmp>
251+
<ui5-button id="btnMenuListBusy">Open Menu</ui5-button> <br/>
252+
<ui5-menu id="menuListBusy" busy-delay="100" busy>
253+
</ui5-menu>
254+
<script>
255+
btnMenuListBusy.addEventListener("click", function(event) {
256+
menuListBusy.showAt(btnMenuListBusy);
257+
});
258+
259+
menuListBusy.addEventListener("ui5-before-open", function(event) {
260+
setTimeout(function() {
261+
menuListBusy.removeAttribute("busy");
262+
let openNode = document.createElement("ui5-menu-item");
263+
openNode.setAttribute("text", "Open");
264+
openNode.setAttribute("icon", "open-folder");
265+
openNode.setAttribute("starts-section", "");
266+
menuListBusy.appendChild(openNode);
267+
let closeNode = document.createElement("ui5-menu-item");
268+
closeNode.setAttribute("text", "Close");
269+
menuListBusy.appendChild(closeNode);
270+
}, 3000);
271+
}, { once: true });
272+
</script>
273+
</xmp></pre>
274+
</section>
150275

151276
<!-- JSDoc marker -->

0 commit comments

Comments
 (0)