Skip to content

Commit

Permalink
feat(tree): add left menu and an option to control availability of menus
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Zhivka Dimova committed Mar 15, 2017
1 parent 4172c93 commit 1afb6fc
Show file tree
Hide file tree
Showing 10 changed files with 1,388 additions and 341 deletions.
16 changes: 11 additions & 5 deletions src/menu/node-menu.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Output, Renderer, Inject, OnDestroy, OnInit } from '@angular/core';
import { Component, EventEmitter, Output, Renderer, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { NodeMenuService } from './node-menu.service';
import { NodeMenuItemSelectedEvent, NodeMenuItemAction, NodeMenuAction } from './menu.events';
import { isLeftButtonClicked, isEscapePressed } from '../utils/event.utils';
Expand All @@ -7,9 +7,9 @@ import { isLeftButtonClicked, isEscapePressed } from '../utils/event.utils';
selector: 'node-menu',
template: `
<div class="node-menu">
<ul class="node-menu-content">
<ul class="node-menu-content" #menuContainer>
<li class="node-menu-item" *ngFor="let menuItem of availableMenuItems"
(click)="onMenuItemSelected($event, menuItem)">
(click)="onMenuItemSelected($event, menuItem)">
<div class="node-menu-item-icon {{menuItem.cssClass}}"></div>
<span class="node-menu-item-value">{{menuItem.name}}</span>
</li>
Expand All @@ -21,6 +21,8 @@ export class NodeMenuComponent implements OnInit, OnDestroy {
@Output()
public menuItemSelected: EventEmitter<NodeMenuItemSelectedEvent> = new EventEmitter<NodeMenuItemSelectedEvent>();

@ViewChild('menuContainer') public menuContainer: any;

public availableMenuItems: NodeMenuItem[] = [
{
name: 'New tag',
Expand Down Expand Up @@ -52,7 +54,7 @@ export class NodeMenuComponent implements OnInit, OnDestroy {

public ngOnInit(): void {
this.disposersForGlobalListeners.push(this.renderer.listenGlobal('document', 'keyup', this.closeMenu.bind(this)));
this.disposersForGlobalListeners.push(this.renderer.listenGlobal('document', 'click', this.closeMenu.bind(this)));
this.disposersForGlobalListeners.push(this.renderer.listenGlobal('document', 'mousedown', this.closeMenu.bind(this)));
}

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

private closeMenu(e: MouseEvent | KeyboardEvent): void {
const mouseClicked = e instanceof MouseEvent;
if (mouseClicked || isEscapePressed(e as KeyboardEvent)) {
// Check if the click is fired on an element inside a menu
const containingTarget = (this.menuContainer.nativeElement !== e.target && this.menuContainer.nativeElement.contains(e.target));

if (mouseClicked && !containingTarget || isEscapePressed(e as KeyboardEvent)) {
this.nodeMenuService.fireMenuEvent(e.target as HTMLElement, NodeMenuAction.Close);
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ tree-internal .tree .node-value:hover:after {
width: 100%;
}

tree-internal .tree .node-left-menu {
display: inline-block;
height: 100%;
width: auto;
}

tree-internal .tree .node-left-menu:before {
content: '\2026';
color: #757575;
}

tree-internal .tree .node-selected:after {
width: 100%;
}
Expand Down
45 changes: 36 additions & 9 deletions src/tree-internal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Observable } from 'rxjs';
<li>
<div class="value-container"
[ngClass]="{rootless: isRootHidden()}"
(contextmenu)="showMenu($event)"
(contextmenu)="showRightMenu($event)"
[nodeDraggable]="element"
[tree]="tree">
Expand All @@ -32,9 +32,16 @@ import { Observable } from 'rxjs';
*ngIf="shouldShowInputForTreeValue()"
[nodeEditable]="tree.value"
(valueChanged)="applyNewValue($event)"/>
<div class="node-left-menu" #leftMenuButton
*ngIf="tree.hasLeftMenu()" (click)="showLeftMenu($event)">
</div>
<node-menu *ngIf="tree.hasLeftMenu() && isLeftMenuVisible"
(menuItemSelected)="onMenuItemSelected($event)">
</node-menu>
</div>
<node-menu *ngIf="isMenuVisible" (menuItemSelected)="onMenuItemSelected($event)"></node-menu>
<node-menu *ngIf="isRightMenuVisible" (menuItemSelected)="onMenuItemSelected($event)"></node-menu>
<template [ngIf]="tree.isNodeExpanded()">
<tree-internal *ngFor="let child of tree.childrenAsync | async" [tree]="child"></tree-internal>
Expand All @@ -51,7 +58,8 @@ export class TreeInternalComponent implements OnInit {
public settings: Ng2TreeSettings;

public isSelected: boolean = false;
public isMenuVisible: boolean = false;
public isRightMenuVisible: boolean = false;
public isLeftMenuVisible: boolean = false;

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

this.nodeMenuService.hideMenuStream(this.element)
.subscribe(() => this.isMenuVisible = false);
.subscribe(() => {
this.isRightMenuVisible = false;
this.isLeftMenuVisible = false;
});

this.treeService.unselectStream(this.tree)
.subscribe(() => this.isSelected = false);
Expand Down Expand Up @@ -103,18 +114,32 @@ export class TreeInternalComponent implements OnInit {
}
}

public showMenu(e: MouseEvent): void {
if (this.tree.isStatic()) {
public showRightMenu(e: MouseEvent): void {
if (!this.tree.hasRightMenu()) {
return;
}

if (EventUtils.isRightButtonClicked(e)) {
this.isMenuVisible = !this.isMenuVisible;
this.isRightMenuVisible = !this.isRightMenuVisible;
this.nodeMenuService.hideMenuForAllNodesExcept(this.element);
}
e.preventDefault();
}

public showLeftMenu(e: MouseEvent): void {
if (!this.tree.hasLeftMenu()) {
return;
}

if (EventUtils.isLeftButtonClicked(e)) {
this.isLeftMenuVisible = !this.isLeftMenuVisible;
this.nodeMenuService.hideMenuForAllNodesExcept(this.element);
if (this.isLeftMenuVisible) {
e.preventDefault();
}
}
}

public onMenuItemSelected(e: NodeMenuItemSelectedEvent): void {
switch (e.nodeMenuItemAction) {
case NodeMenuItemAction.NewTag:
Expand All @@ -136,12 +161,14 @@ export class TreeInternalComponent implements OnInit {

private onNewSelected(e: NodeMenuItemSelectedEvent): void {
this.tree.createNode(e.nodeMenuItemAction === NodeMenuItemAction.NewFolder);
this.isMenuVisible = false;
this.isRightMenuVisible = false;
this.isLeftMenuVisible = false;
}

private onRenameSelected(): void {
this.tree.markAsBeingRenamed();
this.isMenuVisible = false;
this.isRightMenuVisible = false;
this.isLeftMenuVisible = false;
}

private onRemoveSelected(): void {
Expand Down
16 changes: 16 additions & 0 deletions src/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,22 @@ export class Tree {
return _.get(this.node.settings, 'static', false);
}

/**
* Check whether or not this tree has a left menu.
* @returns {boolean} A flag indicating whether or not this has a left menu.
*/
public hasLeftMenu(): boolean {
return !_.get(this.node.settings, 'static', false) && _.get(this.node.settings, 'leftMenu', false);
}

/**
* Check whether or not this tree has a right menu.
* @returns {boolean} A flag indicating whether or not this has a right menu.
*/
public hasRightMenu(): boolean {
return !_.get(this.node.settings, 'static', false) && _.get(this.node.settings, 'rightMenu', false);
}

/**
* Check whether this tree is "Leaf" or not.
* @returns {boolean} A flag indicating whether or not this tree is a "Leaf".
Expand Down
18 changes: 17 additions & 1 deletion src/tree.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ export interface TreeModel {
}

export class TreeModelSettings {
/**
* "leftMenu" property when set to true makes left menu available.
* @name TreeModelSettings#leftMenu
* @type boolean
* @default false
*/
public leftMenu?: boolean;

/**
* "rightMenu" property when set to true makes right menu available.
* @name TreeModelSettings#rightMenu
* @type boolean
* @default true
*/
public rightMenu?: boolean;

/**
* "static" property when set to true makes it impossible to drag'n'drop tree or call a menu on it.
* @name TreeModelSettings#static
Expand All @@ -34,7 +50,7 @@ export class TreeModelSettings {
public static?: boolean;

public static merge(sourceA: TreeModel, sourceB: TreeModel): TreeModelSettings {
return _.defaults({}, _.get(sourceA, 'settings'), _.get(sourceB, 'settings'), {static: false});
return _.defaults({}, _.get(sourceA, 'settings'), _.get(sourceB, 'settings'), {static: false, leftMenu: false, rightMenu: true});
}
}

Expand Down
Loading

0 comments on commit 1afb6fc

Please sign in to comment.