Skip to content

Commit 1afb6fc

Browse files
author
Zhivka Dimova
committed
feat(tree): add left menu and an option to control availability of menus
Add setting's option - rightMenu which allows to control the availability of the right menu. By default it is true, which mean that right menu is available. When it is set to false there is no right menu. Add left menu and an option to control it. This menu is usefull when the tree should be displayed under mobile and tablet devices which don't have right click. By default left menu isn't available. Change event listeners for closing menus from click to mouse down, because opening of a left menu is an left mouse event (click event) and if you add event listener for closing menu on a click (when a menu is initialized), it fires right after opening, which cause immediate closing. Add a verification which on a mousedown event a close action will proceed only if the event target is out of the menu's items, because other wise the menu is destroyed before an action attached to the item is called. Add a call to a closing function at the end of the fired event on selecting a menu's item, because when a click event it trigger on a menu's item it doesn't call the close function.
1 parent 4172c93 commit 1afb6fc

10 files changed

Lines changed: 1388 additions & 341 deletions

src/menu/node-menu.component.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, EventEmitter, Output, Renderer, Inject, OnDestroy, OnInit } from '@angular/core';
1+
import { Component, EventEmitter, Output, Renderer, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
22
import { NodeMenuService } from './node-menu.service';
33
import { NodeMenuItemSelectedEvent, NodeMenuItemAction, NodeMenuAction } from './menu.events';
44
import { isLeftButtonClicked, isEscapePressed } from '../utils/event.utils';
@@ -7,9 +7,9 @@ import { isLeftButtonClicked, isEscapePressed } from '../utils/event.utils';
77
selector: 'node-menu',
88
template: `
99
<div class="node-menu">
10-
<ul class="node-menu-content">
10+
<ul class="node-menu-content" #menuContainer>
1111
<li class="node-menu-item" *ngFor="let menuItem of availableMenuItems"
12-
(click)="onMenuItemSelected($event, menuItem)">
12+
(click)="onMenuItemSelected($event, menuItem)">
1313
<div class="node-menu-item-icon {{menuItem.cssClass}}"></div>
1414
<span class="node-menu-item-value">{{menuItem.name}}</span>
1515
</li>
@@ -21,6 +21,8 @@ export class NodeMenuComponent implements OnInit, OnDestroy {
2121
@Output()
2222
public menuItemSelected: EventEmitter<NodeMenuItemSelectedEvent> = new EventEmitter<NodeMenuItemSelectedEvent>();
2323

24+
@ViewChild('menuContainer') public menuContainer: any;
25+
2426
public availableMenuItems: NodeMenuItem[] = [
2527
{
2628
name: 'New tag',
@@ -52,7 +54,7 @@ export class NodeMenuComponent implements OnInit, OnDestroy {
5254

5355
public ngOnInit(): void {
5456
this.disposersForGlobalListeners.push(this.renderer.listenGlobal('document', 'keyup', this.closeMenu.bind(this)));
55-
this.disposersForGlobalListeners.push(this.renderer.listenGlobal('document', 'click', this.closeMenu.bind(this)));
57+
this.disposersForGlobalListeners.push(this.renderer.listenGlobal('document', 'mousedown', this.closeMenu.bind(this)));
5658
}
5759

5860
public ngOnDestroy(): void {
@@ -62,12 +64,16 @@ export class NodeMenuComponent implements OnInit, OnDestroy {
6264
public onMenuItemSelected(e: MouseEvent, selectedMenuItem: NodeMenuItem): void {
6365
if (isLeftButtonClicked(e)) {
6466
this.menuItemSelected.emit({nodeMenuItemAction: selectedMenuItem.action});
67+
this.nodeMenuService.fireMenuEvent(e.target as HTMLElement, NodeMenuAction.Close);
6568
}
6669
}
6770

6871
private closeMenu(e: MouseEvent | KeyboardEvent): void {
6972
const mouseClicked = e instanceof MouseEvent;
70-
if (mouseClicked || isEscapePressed(e as KeyboardEvent)) {
73+
// Check if the click is fired on an element inside a menu
74+
const containingTarget = (this.menuContainer.nativeElement !== e.target && this.menuContainer.nativeElement.contains(e.target));
75+
76+
if (mouseClicked && !containingTarget || isEscapePressed(e as KeyboardEvent)) {
7177
this.nodeMenuService.fireMenuEvent(e.target as HTMLElement, NodeMenuAction.Close);
7278
}
7379
}

src/styles.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ tree-internal .tree .node-value:hover:after {
103103
width: 100%;
104104
}
105105

106+
tree-internal .tree .node-left-menu {
107+
display: inline-block;
108+
height: 100%;
109+
width: auto;
110+
}
111+
112+
tree-internal .tree .node-left-menu:before {
113+
content: '\2026';
114+
color: #757575;
115+
}
116+
106117
tree-internal .tree .node-selected:after {
107118
width: 100%;
108119
}

src/tree-internal.component.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { Observable } from 'rxjs';
1616
<li>
1717
<div class="value-container"
1818
[ngClass]="{rootless: isRootHidden()}"
19-
(contextmenu)="showMenu($event)"
19+
(contextmenu)="showRightMenu($event)"
2020
[nodeDraggable]="element"
2121
[tree]="tree">
2222
@@ -32,9 +32,16 @@ import { Observable } from 'rxjs';
3232
*ngIf="shouldShowInputForTreeValue()"
3333
[nodeEditable]="tree.value"
3434
(valueChanged)="applyNewValue($event)"/>
35+
36+
<div class="node-left-menu" #leftMenuButton
37+
*ngIf="tree.hasLeftMenu()" (click)="showLeftMenu($event)">
38+
</div>
39+
<node-menu *ngIf="tree.hasLeftMenu() && isLeftMenuVisible"
40+
(menuItemSelected)="onMenuItemSelected($event)">
41+
</node-menu>
3542
</div>
3643
37-
<node-menu *ngIf="isMenuVisible" (menuItemSelected)="onMenuItemSelected($event)"></node-menu>
44+
<node-menu *ngIf="isRightMenuVisible" (menuItemSelected)="onMenuItemSelected($event)"></node-menu>
3845
3946
<template [ngIf]="tree.isNodeExpanded()">
4047
<tree-internal *ngFor="let child of tree.childrenAsync | async" [tree]="child"></tree-internal>
@@ -51,7 +58,8 @@ export class TreeInternalComponent implements OnInit {
5158
public settings: Ng2TreeSettings;
5259

5360
public isSelected: boolean = false;
54-
public isMenuVisible: boolean = false;
61+
public isRightMenuVisible: boolean = false;
62+
public isLeftMenuVisible: boolean = false;
5563

5664
public constructor(@Inject(NodeMenuService) private nodeMenuService: NodeMenuService,
5765
@Inject(TreeService) private treeService: TreeService,
@@ -62,7 +70,10 @@ export class TreeInternalComponent implements OnInit {
6270
this.settings = this.settings || { rootIsVisible: true };
6371

6472
this.nodeMenuService.hideMenuStream(this.element)
65-
.subscribe(() => this.isMenuVisible = false);
73+
.subscribe(() => {
74+
this.isRightMenuVisible = false;
75+
this.isLeftMenuVisible = false;
76+
});
6677

6778
this.treeService.unselectStream(this.tree)
6879
.subscribe(() => this.isSelected = false);
@@ -103,18 +114,32 @@ export class TreeInternalComponent implements OnInit {
103114
}
104115
}
105116

106-
public showMenu(e: MouseEvent): void {
107-
if (this.tree.isStatic()) {
117+
public showRightMenu(e: MouseEvent): void {
118+
if (!this.tree.hasRightMenu()) {
108119
return;
109120
}
110121

111122
if (EventUtils.isRightButtonClicked(e)) {
112-
this.isMenuVisible = !this.isMenuVisible;
123+
this.isRightMenuVisible = !this.isRightMenuVisible;
113124
this.nodeMenuService.hideMenuForAllNodesExcept(this.element);
114125
}
115126
e.preventDefault();
116127
}
117128

129+
public showLeftMenu(e: MouseEvent): void {
130+
if (!this.tree.hasLeftMenu()) {
131+
return;
132+
}
133+
134+
if (EventUtils.isLeftButtonClicked(e)) {
135+
this.isLeftMenuVisible = !this.isLeftMenuVisible;
136+
this.nodeMenuService.hideMenuForAllNodesExcept(this.element);
137+
if (this.isLeftMenuVisible) {
138+
e.preventDefault();
139+
}
140+
}
141+
}
142+
118143
public onMenuItemSelected(e: NodeMenuItemSelectedEvent): void {
119144
switch (e.nodeMenuItemAction) {
120145
case NodeMenuItemAction.NewTag:
@@ -136,12 +161,14 @@ export class TreeInternalComponent implements OnInit {
136161

137162
private onNewSelected(e: NodeMenuItemSelectedEvent): void {
138163
this.tree.createNode(e.nodeMenuItemAction === NodeMenuItemAction.NewFolder);
139-
this.isMenuVisible = false;
164+
this.isRightMenuVisible = false;
165+
this.isLeftMenuVisible = false;
140166
}
141167

142168
private onRenameSelected(): void {
143169
this.tree.markAsBeingRenamed();
144-
this.isMenuVisible = false;
170+
this.isRightMenuVisible = false;
171+
this.isLeftMenuVisible = false;
145172
}
146173

147174
private onRemoveSelected(): void {

src/tree.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,22 @@ export class Tree {
204204
return _.get(this.node.settings, 'static', false);
205205
}
206206

207+
/**
208+
* Check whether or not this tree has a left menu.
209+
* @returns {boolean} A flag indicating whether or not this has a left menu.
210+
*/
211+
public hasLeftMenu(): boolean {
212+
return !_.get(this.node.settings, 'static', false) && _.get(this.node.settings, 'leftMenu', false);
213+
}
214+
215+
/**
216+
* Check whether or not this tree has a right menu.
217+
* @returns {boolean} A flag indicating whether or not this has a right menu.
218+
*/
219+
public hasRightMenu(): boolean {
220+
return !_.get(this.node.settings, 'static', false) && _.get(this.node.settings, 'rightMenu', false);
221+
}
222+
207223
/**
208224
* Check whether this tree is "Leaf" or not.
209225
* @returns {boolean} A flag indicating whether or not this tree is a "Leaf".

src/tree.types.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ export interface TreeModel {
2525
}
2626

2727
export class TreeModelSettings {
28+
/**
29+
* "leftMenu" property when set to true makes left menu available.
30+
* @name TreeModelSettings#leftMenu
31+
* @type boolean
32+
* @default false
33+
*/
34+
public leftMenu?: boolean;
35+
36+
/**
37+
* "rightMenu" property when set to true makes right menu available.
38+
* @name TreeModelSettings#rightMenu
39+
* @type boolean
40+
* @default true
41+
*/
42+
public rightMenu?: boolean;
43+
2844
/**
2945
* "static" property when set to true makes it impossible to drag'n'drop tree or call a menu on it.
3046
* @name TreeModelSettings#static
@@ -34,7 +50,7 @@ export class TreeModelSettings {
3450
public static?: boolean;
3551

3652
public static merge(sourceA: TreeModel, sourceB: TreeModel): TreeModelSettings {
37-
return _.defaults({}, _.get(sourceA, 'settings'), _.get(sourceB, 'settings'), {static: false});
53+
return _.defaults({}, _.get(sourceA, 'settings'), _.get(sourceB, 'settings'), {static: false, leftMenu: false, rightMenu: true});
3854
}
3955
}
4056

0 commit comments

Comments
 (0)