From 2810bb67e38f51fc1c7c4efd63e5be00da68d8cc Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Mon, 19 Feb 2018 11:07:56 +0200 Subject: [PATCH 01/30] basic components and directives for the new api --- lib/src/html.directive.ts | 17 -- lib/src/index.ts | 2 - lib/src/injector.service.ts | 129 ------------ lib/src/sh-context-menu.component.ts | 197 ------------------ lib/src/sh-context-menu.directive.ts | 107 ---------- lib/src/sh-context-menu.module.ts | 30 --- lib/src/sh-context-overlay.component.ts | 22 -- lib/src/sh-context-service.ts | 22 -- lib/src/sh-context-sub-menu.directive.ts | 78 ------- package.json | 5 +- src/app/app.component.html | 21 +- src/app/app.component.ts | 33 ++- src/app/app.module.ts | 5 +- {lib => src/lib}/package.json | 3 +- src/lib/src/index.ts | 3 + .../lib}/src/sh-context-menu.models.ts | 12 +- src/lib/src/sh-context-menu.module.ts | 31 +++ src/lib/src/shContextMenu.service.ts | 13 ++ src/lib/src/shContextMenu.ts | 62 ++++++ src/lib/src/shContextMenuTrigger.directive.ts | 27 +++ src/tsconfig.app.json | 2 +- tsconfig.json | 2 +- tslint.json | 8 +- yarn.lock | 6 + 24 files changed, 207 insertions(+), 630 deletions(-) delete mode 100644 lib/src/html.directive.ts delete mode 100644 lib/src/index.ts delete mode 100644 lib/src/injector.service.ts delete mode 100755 lib/src/sh-context-menu.component.ts delete mode 100755 lib/src/sh-context-menu.directive.ts delete mode 100755 lib/src/sh-context-menu.module.ts delete mode 100644 lib/src/sh-context-overlay.component.ts delete mode 100644 lib/src/sh-context-service.ts delete mode 100755 lib/src/sh-context-sub-menu.directive.ts rename {lib => src/lib}/package.json (92%) create mode 100644 src/lib/src/index.ts rename {lib => src/lib}/src/sh-context-menu.models.ts (94%) create mode 100755 src/lib/src/sh-context-menu.module.ts create mode 100644 src/lib/src/shContextMenu.service.ts create mode 100644 src/lib/src/shContextMenu.ts create mode 100644 src/lib/src/shContextMenuTrigger.directive.ts diff --git a/lib/src/html.directive.ts b/lib/src/html.directive.ts deleted file mode 100644 index 179f314..0000000 --- a/lib/src/html.directive.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ElementRef } from '@angular/core'; -import { AfterContentInit } from '@angular/core'; -import { Input } from '@angular/core'; -import { Directive } from '@angular/core'; - -@Directive({ - selector: '[sh-html]' -}) -export class HtmlDirective implements AfterContentInit { - @Input('sh-html') content: String; - - constructor(private elmRef: ElementRef){} - - ngAfterContentInit(): void { - this.elmRef.nativeElement.insertAdjacentHTML('afterbegin', this.content); - } -} diff --git a/lib/src/index.ts b/lib/src/index.ts deleted file mode 100644 index b1f0ff3..0000000 --- a/lib/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { ShContextMenuModule } from './sh-context-menu.module'; -export { IShContextMenuItem, IShContextOptions, BeforeMenuEvent } from './sh-context-menu.models'; diff --git a/lib/src/injector.service.ts b/lib/src/injector.service.ts deleted file mode 100644 index c44cfe0..0000000 --- a/lib/src/injector.service.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { - ApplicationRef, ComponentFactoryResolver, ComponentRef, Injectable, - Injector, EmbeddedViewRef, Type -} from '@angular/core'; - -/** - * Injection service is a helper to append components - * dynamically to a known location in the DOM, most - * noteably for dialogs/tooltips appending to body. - * - * @export - * @class InjectionService - */ -@Injectable() -export class InjectionService { - private _container: ComponentRef; - - constructor( - private applicationRef: ApplicationRef, - private componentFactoryResolver: ComponentFactoryResolver, - private injector: Injector) { - } - - /** - * Gets the root view container to inject the component to. - * - * @returns {ComponentRef} - * - * @memberOf InjectionService - */ - getRootViewContainer(): ComponentRef { - if(this._container) return this._container; - - const rootComponents = this.applicationRef['components']; - if (rootComponents.length) return rootComponents[0]; - - throw new Error('View Container not found! ngUpgrade needs to manually set this via setRootViewContainer.'); - } - - /** - * Overrides the default root view container. This is useful for - * things like ngUpgrade that doesn't have a ApplicationRef root. - * - * @param {any} container - * - * @memberOf InjectionService - */ - setRootViewContainer(container:any): void { - this._container = container; - } - - /** - * Gets the html element for a component ref. - * - * @param {ComponentRef} componentRef - * @returns {HTMLElement} - * - * @memberOf InjectionService - */ - getComponentRootNode(componentRef: ComponentRef): HTMLElement { - return (componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement; - } - - /** - * Gets the root component container html element. - * - * @returns {HTMLElement} - * - * @memberOf InjectionService - */ - getRootViewContainerNode(): HTMLElement { - return this.getComponentRootNode(this.getRootViewContainer()); - } - - /** - * Projects the inputs onto the component - * - * @param {ComponentRef} component - * @param {*} options - * @returns {ComponentRef} - * - * @memberOf InjectionService - */ - projectComponentInputs(component: ComponentRef, options: any): ComponentRef { - if(options) { - const props = Object.getOwnPropertyNames(options); - for(const prop of props) { - component.instance[prop] = options[prop]; - } - } - - return component; - } - - /** - * Appends a component to a adjacent location - * - * @template T - * @param {Type} componentClass - * @param {*} [options={}] - * @param {Element} [location=this.getRootViewContainerNode()] - * @returns {ComponentRef} - * - * @memberOf InjectionService - */ - appendComponent( - componentClass: Type, - options: any = {}, - location: Element = this.getRootViewContainerNode()): ComponentRef { - - let componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentClass); - let componentRef = componentFactory.create(this.injector); - let appRef: any = this.applicationRef; - let componentRootNode = this.getComponentRootNode(componentRef); - - // project the options passed to the component instance - this.projectComponentInputs(componentRef, options); - - appRef.attachView(componentRef.hostView); - - componentRef.onDestroy(() => { - appRef.detachView(componentRef.hostView); - }); - - location.appendChild(componentRootNode); - - return componentRef; - } -} diff --git a/lib/src/sh-context-menu.component.ts b/lib/src/sh-context-menu.component.ts deleted file mode 100755 index 77ec010..0000000 --- a/lib/src/sh-context-menu.component.ts +++ /dev/null @@ -1,197 +0,0 @@ -import {Component, Input, Output, EventEmitter, OnInit, ElementRef, ViewChild, AfterContentInit} from "@angular/core"; -import {IShContextMenuItem, IShContextOptions} from "./sh-context-menu.models"; -import {ShContextService} from './sh-context-service'; - -export interface ShContextPosition { - top: number; - left: number; -} - -@Component({ - selector: 'sh-context-menu', - template: ` -
-
    -
  • -
    -
    -
    -
    -
    -
  • -
-
- `, - styles: [` - .sh-context--container { - font-family: sans-serif; - position: fixed; - background: #ececec; - min-width: 150px; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 3px; - box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1); - z-index: 9999; - color: black; - } - - .dark { - background: #383737 !important; - color: white !important; - } - - .sh-context--container ul { - list-style: none; - padding: 5px 0; - margin: 0; - } - - .sh-context--container ul li { - padding: 5px 10px 5px 15px; - transition: all 0.15s; - } - - .sh-context--container ul li.sh-context-divider { - height: 1px; - margin: 1px 1px 8px 1px; - overflow: hidden; - border-bottom: 1px solid #d0d0d0; - line-height: 10px; - } - - .sh-context--container ul li.sh-menu-item:hover { - cursor: pointer; - background: #4b8bec; - color: white; - } - - .sh-context--container.dark ul li.sh-menu-item:hover { - cursor: pointer; - background: #4b8bec; - color: white; - } - - .sh-context--container ul li.sh-menu-disabled { - color: #d0d0d0; - } - - .sh-context--container ul li.sh-menu-item.sh-menu-hidden { - display: none; - } - - .sh-context--container ul li.sh-menu-disabled:hover { - cursor: not-allowed; - color: #d0d0d0; - background: #ececec; - } - - .right-arrow { - float: right; - margin-left: 10px; - margin-top: 3px; - border-top: 6px solid transparent; - border-bottom: 6px solid transparent; - border-left: 8px solid black; - } - - .left-arrow { - float: left; - margin-right: 10px; - margin-top: 3px; - border-top: 6px solid transparent; - border-bottom: 6px solid transparent; - border-right: 8px solid black; - } - `] -}) -export class ShContextMenuComponent implements OnInit, AfterContentInit { - @Input() position: ShContextPosition; - @Input() items: IShContextMenuItem[]; - @Input() dataContext: any; - @Output() onClose = new EventEmitter(); - - options: IShContextOptions; - - @ViewChild('childRef') childRef: ElementRef; - - constructor(private ctxService: ShContextService) { - } - - ngOnInit(): void { - this.options = this.ctxService.getOptions(); - } - - ngAfterContentInit(): void { - if (this.options.rtl) - this.setRtlLocation(); - } - - getLabel(item: IShContextMenuItem): string { - if (typeof item.label === 'string') { - return item.label; - } else if (typeof item.label === 'function') { - return item.label(this.dataContext); - } - return ''; - } - - close() { - this.onClose.emit(); - } - - onClick(item: IShContextMenuItem) { - if (this.isItemDisabled(item)) - return; - - if (item.onClick) { - this.close(); - - // invoke the onClick handler with a timeout of 0, - // so that the menu gets a chance to be closed before (screen refresh) - this.invokeOnClickWithTimeOut(item); - } - } - - private invokeOnClickWithTimeOut(item: IShContextMenuItem) { - setTimeout(() => { - if (item.onClick) { - item.onClick({ - menuItem: item, - dataContext: this.dataContext - }); - } - }, 0); - } - - isItemDisabled(item: IShContextMenuItem) { - if (!item.disabled) - return false; - - return item.disabled(this.dataContext); - } - - isItemVisible(item: IShContextMenuItem) { - if (!item.visible) - return true; - - return item.visible(this.dataContext); - } - - setRtlLocation() { - const elmRect: ClientRect = - this.childRef.nativeElement.getClientRects()[0]; - - this.position.left = this.position.left - elmRect.width; - } - -} diff --git a/lib/src/sh-context-menu.directive.ts b/lib/src/sh-context-menu.directive.ts deleted file mode 100755 index 9b2161c..0000000 --- a/lib/src/sh-context-menu.directive.ts +++ /dev/null @@ -1,107 +0,0 @@ -import {ShContextService} from './sh-context-service'; -import { - Directive, - Input, - HostListener, - ComponentRef, - Output, EventEmitter -} from "@angular/core"; - -import {ShContextOverlayComponent} from './sh-context-overlay.component'; -import {ShContextMenuComponent} from "./sh-context-menu.component"; -import {InjectionService} from "./injector.service"; -import {BeforeMenuEvent, IShContextMenuItem, IShContextOptions} from "./sh-context-menu.models"; - -@Directive({ - selector: '[sh-context]' -}) -export class ShContextMenuDirective { - @Input('sh-context') menuItems: IShContextMenuItem[]; - @Input('sh-data-context') dataContext: any; - @Input('sh-options') options: IShContextOptions; - - @Output('onBeforeMenuOpen') onBeforeMenuOpen = new EventEmitter(); - - ctxComponent: ComponentRef; - overlayComponent: ComponentRef; - - constructor(private ctxService: ShContextService, - private injectionService: InjectionService) { - } - - @HostListener('contextmenu', ['$event']) - onClick(event: MouseEvent): boolean | void { - this.options = this.ctxService.setOptions(this.options); - - this.closeMenu(); - - if ( this.contextMenuIsEmpty() ) { - return; - } - - if (this.onBeforeMenuOpen.observers.length > 0) { - this.onBeforeMenuOpen.emit({ - event: event, - items: this.menuItems, - open: (modifiedItems: IShContextMenuItem[] = this.menuItems) => this.createMenu(event, modifiedItems) - }); - } else { - this.createMenu(event); - } - - return false; - } - - private createMenu(event: MouseEvent, items: IShContextMenuItem[] = this.menuItems) { - this.overlayComponent = this.createOverlayComponent(); - this.ctxComponent = this.createContextComponent(); - - this.registerBindings(items); - this.registerEvents(); - this.setLocation(event); - } - - registerEvents() { - this.ctxComponent.instance.onClose.subscribe(() => { - this.closeMenu() - }); - - this.overlayComponent.instance.onClick.subscribe(() => { - this.closeMenu() - }); - } - - registerBindings(menuItems: IShContextMenuItem[]) { - this.ctxComponent.instance.items = menuItems; - this.ctxComponent.instance.dataContext = this.dataContext; - } - - createContextComponent(): ComponentRef { - return this.injectionService.appendComponent(ShContextMenuComponent); - } - - createOverlayComponent(): ComponentRef { - return this.injectionService.appendComponent(ShContextOverlayComponent); - } - - setLocation(event: MouseEvent) { - let {clientX, clientY} = event; - - this.ctxComponent.instance.position = { - top: clientY, - left: clientX - }; - } - - closeMenu() { - if (this.ctxComponent) - this.ctxComponent.destroy(); - - if (this.overlayComponent) - this.overlayComponent.destroy(); - } - - private contextMenuIsEmpty(): boolean { - return !this.menuItems || this.menuItems.length === 0; - } -} diff --git a/lib/src/sh-context-menu.module.ts b/lib/src/sh-context-menu.module.ts deleted file mode 100755 index 02ccb56..0000000 --- a/lib/src/sh-context-menu.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; - -import { HtmlDirective } from './html.directive'; -import { ShContextOverlayComponent } from './sh-context-overlay.component'; -import { ShContextMenuDirective } from "./sh-context-menu.directive"; -import { ShContextMenuComponent } from './sh-context-menu.component'; -import { ShContextSubMenuDirective } from './sh-context-sub-menu.directive'; -import { ShContextServiceModule } from './sh-context-service'; - - -@NgModule({ - declarations: [ - ShContextMenuDirective, - ShContextMenuComponent, - ShContextSubMenuDirective, - ShContextOverlayComponent, - HtmlDirective - ], - exports: [ShContextMenuDirective], - imports: [ - CommonModule, - ShContextServiceModule - ], - entryComponents: [ - ShContextMenuComponent, - ShContextOverlayComponent - ] -}) -export class ShContextMenuModule { } diff --git a/lib/src/sh-context-overlay.component.ts b/lib/src/sh-context-overlay.component.ts deleted file mode 100644 index e11f8c0..0000000 --- a/lib/src/sh-context-overlay.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { EventEmitter } from '@angular/core'; -import { Output } from '@angular/core'; -import { Component } from '@angular/core'; - -@Component({ - selector: 'sh-context-overlay', - template: `
`, - styles: [` - .sh-context-overlay{ - position: fixed; - top:0; - bottom: 0; - left: 0; - right: 0; - z-index: 9998; - background-color: transparent; - } - `] -}) -export class ShContextOverlayComponent{ - @Output() onClick = new EventEmitter() -} diff --git a/lib/src/sh-context-service.ts b/lib/src/sh-context-service.ts deleted file mode 100644 index 7064745..0000000 --- a/lib/src/sh-context-service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Injectable, NgModule } from '@angular/core'; -import {InjectionService} from "./injector.service"; -import {IShContextOptions, ShContextDefaultOptions} from "./sh-context-menu.models"; - -@Injectable() -export class ShContextService { - options: IShContextOptions; - - setOptions(opts: IShContextOptions): IShContextOptions { - this.options = Object.assign({}, ShContextDefaultOptions, opts); - return this.options; - } - - getOptions(): IShContextOptions{ - return this.options; - } -} - -@NgModule({ - providers: [ShContextService, InjectionService] -}) -export class ShContextServiceModule { } diff --git a/lib/src/sh-context-sub-menu.directive.ts b/lib/src/sh-context-sub-menu.directive.ts deleted file mode 100755 index 8b4c303..0000000 --- a/lib/src/sh-context-sub-menu.directive.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Directive, Output, ElementRef, EventEmitter, Input, HostListener, ViewContainerRef, ComponentFactoryResolver, ComponentRef, OnInit } from "@angular/core"; - -import { ShContextMenuComponent, ShContextPosition } from "./sh-context-menu.component"; -import { ShContextService } from "./sh-context-service"; -import {IShContextMenuItem, IShContextOptions} from "./sh-context-menu.models"; - -@Directive({ - selector: '[sh-context-sub-menu]' -}) -export class ShContextSubMenuDirective implements OnInit { - @Input('sh-context-sub-menu') menuItems: IShContextMenuItem[]; - @Input('sh-data-context') dataContext: any; - @Output() closeSubMenu = new EventEmitter(); - - options: IShContextOptions; - ctxComponent: ComponentRef; - - constructor( - private viewRef: ViewContainerRef, - private elmRef: ElementRef, - private resolver: ComponentFactoryResolver, - private ctxService: ShContextService - ) { } - - ngOnInit(): void { - this.options = this.ctxService.getOptions(); - } - - @HostListener('mouseover', ['$event']) - onMouseOver(event: MouseEvent) { - this.closeCurrent(); - this.ctxComponent = this.createContextComponent(); - - this.registerBindings(); - this.registerEvents(); - this.setLocation(); - - return false; - } - - registerEvents() { - this.ctxComponent.instance.onClose.subscribe(() => { - this.closeSubMenu.emit(); - }); - } - - registerBindings() { - this.ctxComponent.instance.items = this.menuItems; - this.ctxComponent.instance.dataContext = this.dataContext; - } - - createContextComponent(): ComponentRef { - let shContextMenuFactory = this.resolver.resolveComponentFactory(ShContextMenuComponent); - let shContextComponentRef = this.viewRef.createComponent(shContextMenuFactory); - - return shContextComponentRef; - } - - setLocation() { - const { top, left, width } = - this.elmRef.nativeElement.getClientRects()[0]; - - let position: ShContextPosition = { - top: top, - left: this.options.rtl ? left : left + width - }; - - this.ctxComponent.instance.position = position; - } - - closeMenu() { - this.closeSubMenu.emit(); - } - - closeCurrent() { - this.viewRef.clear(); - } -} diff --git a/package.json b/package.json index 68e5a21..aec947c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "private": true, "dependencies": { "@angular/animations": "^5.2.0", + "@angular/cdk": "^5.2.1", "@angular/common": "^5.2.0", "@angular/compiler": "^5.2.0", "@angular/core": "^5.2.0", @@ -58,10 +59,10 @@ "karma-coverage-istanbul-reporter": "^1.2.1", "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", + "ng-packagr": "^1.6.0", "protractor": "~5.1.2", "ts-node": "~4.1.0", "tslint": "~5.9.1", - "typescript": "~2.5.3", - "ng-packagr": "^1.6.0" + "typescript": "~2.5.3" } } diff --git a/src/app/app.component.html b/src/app/app.component.html index 43cfee5..5045348 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,6 +1,21 @@ -
+

{{title}}

-
-

{{titleRtl}}

+ +
+

{{title}}

+ + +
+ one - {{item.label}} +
+
+ two - {{item.label}} +
+
+ three - {{item.label}} +
+
+ + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5600a9d..ac81bfc 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,5 @@ -import {Component, ViewEncapsulation} from '@angular/core'; -import {IShContextMenuItem, IShContextOptions, BeforeMenuEvent} from 'ng2-right-click-menu'; +import {Component, Input, ViewEncapsulation} from '@angular/core'; +import {ShContextMenuComponent} from '../lib/src/shContextMenu'; @Component({ selector: 'app-root', @@ -7,14 +7,16 @@ import {IShContextMenuItem, IShContextOptions, BeforeMenuEvent} from 'ng2-right- templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) -export class AppComponent { +export class AppComponent { title = 'Right Click Me'; titleRtl = 'Right Click Me (RTL)'; - items: IShContextMenuItem[]; - itemsRtl: IShContextMenuItem[]; + items: any[]; + itemsRtl: any[]; dataCtxOne: any; dataCtxTwo: any; - options: IShContextOptions; + options: any; + + menuKey = 'firstMenu'; constructor() { this.dataCtxOne = { @@ -124,12 +126,27 @@ export class AppComponent { }; } - onBefore = (event: BeforeMenuEvent) => { + onBefore = (event: any) => { console.log(event); event.open([event.items[0]]); }; clickEvent = ($event: any) => { console.log('clicked ', $event); - } + }; +} + +@Component({ + selector: 'my-menu', + template: ` +
+ from comp !! - {{item.label}} +
+
+ from comp !! - {{item.label}} +
+ ` +}) +export class MyMenuComponent extends ShContextMenuComponent { + @Input() item: any; } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d7fb4cc..5ed590c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,12 +1,13 @@ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; -import {AppComponent} from './app.component'; +import {AppComponent, MyMenuComponent} from './app.component'; import {ShContextMenuModule} from 'ng2-right-click-menu'; @NgModule({ declarations: [ - AppComponent + AppComponent, + MyMenuComponent ], imports: [ BrowserModule, diff --git a/lib/package.json b/src/lib/package.json similarity index 92% rename from lib/package.json rename to src/lib/package.json index 6807fa5..cbbf306 100644 --- a/lib/package.json +++ b/src/lib/package.json @@ -21,7 +21,8 @@ }, "peerDependencies": { "@angular/core": "^5.0.0", - "@angular/common": "^5.0.0" + "@angular/common": "^5.0.0", + "@angular/cdk": "^5.0.0" }, "ngPackage": { "$schema": "../node_modules/ng-packagr/ng-package.schema.json", diff --git a/src/lib/src/index.ts b/src/lib/src/index.ts new file mode 100644 index 0000000..8497608 --- /dev/null +++ b/src/lib/src/index.ts @@ -0,0 +1,3 @@ +export {ShContextMenuTriggerDirective} from './shContextMenuTrigger.directive'; +export {ShContextMenuModule} from './sh-context-menu.module'; +export {ShContextMenuComponent, ShContextMenuItemDirective, ShContextMenuDirective} from './shContextMenu'; diff --git a/lib/src/sh-context-menu.models.ts b/src/lib/src/sh-context-menu.models.ts similarity index 94% rename from lib/src/sh-context-menu.models.ts rename to src/lib/src/sh-context-menu.models.ts index a890fca..1f333a9 100644 --- a/lib/src/sh-context-menu.models.ts +++ b/src/lib/src/sh-context-menu.models.ts @@ -7,21 +7,25 @@ export interface IShContextMenuItem { label?: ((context: any) => string) | string; id?: string; divider?: boolean; - onClick?($event: any): void; - visible?(context: any): boolean; - disabled?(context: any): boolean; subMenu?: boolean; subMenuItems?: IShContextMenuItem[]; data?: any; + + onClick?($event: any): void; + + visible?(context: any): boolean; + + disabled?(context: any): boolean; } export interface IShContextOptions { rtl?: boolean; - theme?: 'light' | 'dark' + theme?: 'light' | 'dark'; } export interface BeforeMenuEvent { event: MouseEvent; items: IShContextMenuItem[]; + open(items?: IShContextMenuItem[]): void; } diff --git a/src/lib/src/sh-context-menu.module.ts b/src/lib/src/sh-context-menu.module.ts new file mode 100755 index 0000000..a445497 --- /dev/null +++ b/src/lib/src/sh-context-menu.module.ts @@ -0,0 +1,31 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; + +import {ShContextMenuComponent, ShContextMenuDirective, ShContextMenuItemDirective} from './shContextMenu'; +import {ShContextMenuTriggerDirective} from './shContextMenuTrigger.directive'; +import {ShContextMenuService} from './shContextMenu.service'; + +@NgModule({ + declarations: [ + ShContextMenuTriggerDirective, + ShContextMenuDirective, + ShContextMenuComponent, + ShContextMenuItemDirective + ], + exports: [ + ShContextMenuTriggerDirective, + ShContextMenuDirective, + ShContextMenuComponent, + ShContextMenuItemDirective + ], + providers: [ + ShContextMenuService + ], + imports: [ + CommonModule, + ], + entryComponents: [ + ] +}) +export class ShContextMenuModule { +} diff --git a/src/lib/src/shContextMenu.service.ts b/src/lib/src/shContextMenu.service.ts new file mode 100644 index 0000000..7b6afa3 --- /dev/null +++ b/src/lib/src/shContextMenu.service.ts @@ -0,0 +1,13 @@ +import {Injectable} from '@angular/core'; +import {ShContextMenuComponent} from './shContextMenu'; + +@Injectable() +export class ShContextMenuService { + + constructor() { + } + + openMenu(menu: ShContextMenuComponent, event: MouseEvent) { + menu.show(); + } +} diff --git a/src/lib/src/shContextMenu.ts b/src/lib/src/shContextMenu.ts new file mode 100644 index 0000000..6d73ae0 --- /dev/null +++ b/src/lib/src/shContextMenu.ts @@ -0,0 +1,62 @@ +import { + AfterContentInit, Component, ContentChildren, Directive, Input, OnInit, TemplateRef, ViewChildren, + ViewContainerRef +} from '@angular/core'; + +@Directive({ + selector: '[shContextMenu]' +}) +export class ShContextMenuDirective { + @Input('shContextMenu') shContextMenu; + + constructor() { + // console.log(this.data); + } +} + +@Directive({ + selector: '[shContextMenuItem]' +}) +export class ShContextMenuItemDirective { + constructor(public template: TemplateRef) { + } +} + +@Component({ + selector: 'sh-context-menu', + template: ` + ` +}) +export class ShContextMenuComponent implements OnInit, AfterContentInit { + + @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) items; + @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; + + constructor(private vcr: ViewContainerRef) { + } + + ngAfterContentInit(): void { + console.log(this.items); + } + + ngOnInit() { + } + + show() { + if (this.items.length) { + this.showMenu(this.items); + } + if (this.viewChildrenItems.length) { + this.showMenu(this.viewChildrenItems); + } + } + + private showMenu(items: ShContextMenuItemDirective[]) { + items + .forEach( + item => this.vcr.createEmbeddedView(item.template, { + $implicit: {label: 'testtt'} + }) + ); + } +} diff --git a/src/lib/src/shContextMenuTrigger.directive.ts b/src/lib/src/shContextMenuTrigger.directive.ts new file mode 100644 index 0000000..81ea031 --- /dev/null +++ b/src/lib/src/shContextMenuTrigger.directive.ts @@ -0,0 +1,27 @@ +import {Directive, HostListener, Input, OnInit} from '@angular/core'; +import {ShContextMenuService} from './shContextMenu.service'; +import {ShContextMenuComponent} from './shContextMenu'; + +@Directive({ + selector: '[shContextMenuTriggerFor]' +}) +export class ShContextMenuTriggerDirective implements OnInit { + @Input('shContextMenuTriggerFor') + triggerFor: ShContextMenuComponent; + + @Input('shMenuData') data: any; + + constructor(private ctxMenu: ShContextMenuService) { + } + + ngOnInit(): void { + console.log(this.data); + } + + @HostListener('contextmenu', ['$event']) + openMenu(event: MouseEvent) { + event.preventDefault(); + console.log(this.triggerFor); + this.ctxMenu.openMenu(this.triggerFor, event); + } +} diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json index 51a1d68..3ac1166 100644 --- a/src/tsconfig.app.json +++ b/src/tsconfig.app.json @@ -3,7 +3,7 @@ "angularCompilerOptions": { "paths": { "ng2-right-click-menu": [ - "../lib/src/index.ts" + "./lib/src/index.ts" ] } }, diff --git a/tsconfig.json b/tsconfig.json index 475df5f..1ad142b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,7 @@ ], "paths": { "ng2-right-click-menu": [ - "../lib/src/index.ts" + "./lib/src/index.ts" ] } } diff --git a/tslint.json b/tslint.json index 9963d6c..aefac69 100644 --- a/tslint.json +++ b/tslint.json @@ -29,7 +29,7 @@ "interface-over-type-literal": true, "label-position": true, "max-line-length": [ - true, + false, 140 ], "member-access": false, @@ -118,13 +118,13 @@ "check-type" ], "directive-selector": [ - true, + false, "attribute", "app", "camelCase" ], "component-selector": [ - true, + false, "element", "app", "kebab-case" @@ -133,7 +133,7 @@ "use-input-property-decorator": true, "use-output-property-decorator": true, "use-host-property-decorator": true, - "no-input-rename": true, + "no-input-rename": false, "no-output-rename": true, "use-life-cycle-interface": true, "use-pipe-transform-interface": true, diff --git a/yarn.lock b/yarn.lock index 4f98f89..c67ff13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,6 +33,12 @@ dependencies: tslib "^1.7.1" +"@angular/cdk@^5.2.1": + version "5.2.1" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-5.2.1.tgz#11500502deeaf42f88f92ca7d78d9a7642b07761" + dependencies: + tslib "^1.7.1" + "@angular/cli@1.6.5": version "1.6.5" resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.6.5.tgz#9217c5d5c366292aca61fb0328c406bb5b0f2d76" From 7bdd5820281cc13f2e7aed0aaf22ffb4a83b16e5 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Mon, 19 Feb 2018 11:21:16 +0200 Subject: [PATCH 02/30] break down to files --- src/app/app.component.html | 4 +-- src/app/app.component.ts | 2 +- src/lib/src/index.ts | 7 +++-- src/lib/src/sh-anchor-for.directive.ts | 27 +++++++++++++++++++ src/lib/src/sh-context-menu-item.directive.ts | 9 +++++++ ...xtMenu.ts => sh-context-menu.component.ts} | 22 ++------------- src/lib/src/sh-context-menu.module.ts | 13 +++++---- ....service.ts => sh-context-menu.service.ts} | 2 +- src/lib/src/shContextMenuTrigger.directive.ts | 27 ------------------- 9 files changed, 53 insertions(+), 60 deletions(-) create mode 100644 src/lib/src/sh-anchor-for.directive.ts create mode 100644 src/lib/src/sh-context-menu-item.directive.ts rename src/lib/src/{shContextMenu.ts => sh-context-menu.component.ts} (68%) rename src/lib/src/{shContextMenu.service.ts => sh-context-menu.service.ts} (74%) delete mode 100644 src/lib/src/shContextMenuTrigger.directive.ts diff --git a/src/app/app.component.html b/src/app/app.component.html index 5045348..7309385 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,8 +1,8 @@ -
+

{{title}}

-
+

{{title}}

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ac81bfc..fb9b0e6 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,5 @@ import {Component, Input, ViewEncapsulation} from '@angular/core'; -import {ShContextMenuComponent} from '../lib/src/shContextMenu'; +import {ShContextMenuComponent} from '../lib/src/sh-context-menu.component'; @Component({ selector: 'app-root', diff --git a/src/lib/src/index.ts b/src/lib/src/index.ts index 8497608..6cc9961 100644 --- a/src/lib/src/index.ts +++ b/src/lib/src/index.ts @@ -1,3 +1,6 @@ -export {ShContextMenuTriggerDirective} from './shContextMenuTrigger.directive'; export {ShContextMenuModule} from './sh-context-menu.module'; -export {ShContextMenuComponent, ShContextMenuItemDirective, ShContextMenuDirective} from './shContextMenu'; +export {ShContextMenuComponent} from './sh-context-menu.component'; +export {ShAnchorForDirective} from './sh-anchor-for.directive'; +export {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; +export {ShContextMenuService} from './sh-context-menu.service'; + diff --git a/src/lib/src/sh-anchor-for.directive.ts b/src/lib/src/sh-anchor-for.directive.ts new file mode 100644 index 0000000..b33cffe --- /dev/null +++ b/src/lib/src/sh-anchor-for.directive.ts @@ -0,0 +1,27 @@ +import {Directive, HostListener, Input, OnInit} from '@angular/core'; +import {ShContextMenuService} from './sh-context-menu.service'; +import {ShContextMenuComponent} from './sh-context-menu.component'; + +@Directive({ + selector: '[shAnchorFor]' +}) +export class ShAnchorForDirective implements OnInit { + @Input('shAnchorFor') + menu: ShContextMenuComponent; + + @Input('shMenuData') data: any; + + constructor(private ctxMenu: ShContextMenuService) { + } + + ngOnInit(): void { + console.log(this.data); + } + + @HostListener('contextmenu', ['$event']) + openMenu(event: MouseEvent) { + event.preventDefault(); + console.log(this.menu); + this.ctxMenu.openMenu(this.menu, event); + } +} diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts new file mode 100644 index 0000000..2249728 --- /dev/null +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -0,0 +1,9 @@ +import {Directive, TemplateRef} from '@angular/core'; + +@Directive({ + selector: '[shContextMenuItem]' +}) +export class ShContextMenuItemDirective { + constructor(public template: TemplateRef) { + } +} diff --git a/src/lib/src/shContextMenu.ts b/src/lib/src/sh-context-menu.component.ts similarity index 68% rename from src/lib/src/shContextMenu.ts rename to src/lib/src/sh-context-menu.component.ts index 6d73ae0..45a09e0 100644 --- a/src/lib/src/shContextMenu.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -1,26 +1,8 @@ import { - AfterContentInit, Component, ContentChildren, Directive, Input, OnInit, TemplateRef, ViewChildren, + AfterContentInit, Component, ContentChildren, OnInit, ViewChildren, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[shContextMenu]' -}) -export class ShContextMenuDirective { - @Input('shContextMenu') shContextMenu; - - constructor() { - // console.log(this.data); - } -} - -@Directive({ - selector: '[shContextMenuItem]' -}) -export class ShContextMenuItemDirective { - constructor(public template: TemplateRef) { - } -} +import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; @Component({ selector: 'sh-context-menu', diff --git a/src/lib/src/sh-context-menu.module.ts b/src/lib/src/sh-context-menu.module.ts index a445497..63cb59e 100755 --- a/src/lib/src/sh-context-menu.module.ts +++ b/src/lib/src/sh-context-menu.module.ts @@ -1,20 +1,19 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; -import {ShContextMenuComponent, ShContextMenuDirective, ShContextMenuItemDirective} from './shContextMenu'; -import {ShContextMenuTriggerDirective} from './shContextMenuTrigger.directive'; -import {ShContextMenuService} from './shContextMenu.service'; +import {ShContextMenuComponent} from './sh-context-menu.component'; +import {ShAnchorForDirective} from './sh-anchor-for.directive'; +import {ShContextMenuService} from './sh-context-menu.service'; +import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; @NgModule({ declarations: [ - ShContextMenuTriggerDirective, - ShContextMenuDirective, + ShAnchorForDirective, ShContextMenuComponent, ShContextMenuItemDirective ], exports: [ - ShContextMenuTriggerDirective, - ShContextMenuDirective, + ShAnchorForDirective, ShContextMenuComponent, ShContextMenuItemDirective ], diff --git a/src/lib/src/shContextMenu.service.ts b/src/lib/src/sh-context-menu.service.ts similarity index 74% rename from src/lib/src/shContextMenu.service.ts rename to src/lib/src/sh-context-menu.service.ts index 7b6afa3..d572a8c 100644 --- a/src/lib/src/shContextMenu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {ShContextMenuComponent} from './shContextMenu'; +import {ShContextMenuComponent} from './sh-context-menu.component'; @Injectable() export class ShContextMenuService { diff --git a/src/lib/src/shContextMenuTrigger.directive.ts b/src/lib/src/shContextMenuTrigger.directive.ts deleted file mode 100644 index 81ea031..0000000 --- a/src/lib/src/shContextMenuTrigger.directive.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {Directive, HostListener, Input, OnInit} from '@angular/core'; -import {ShContextMenuService} from './shContextMenu.service'; -import {ShContextMenuComponent} from './shContextMenu'; - -@Directive({ - selector: '[shContextMenuTriggerFor]' -}) -export class ShContextMenuTriggerDirective implements OnInit { - @Input('shContextMenuTriggerFor') - triggerFor: ShContextMenuComponent; - - @Input('shMenuData') data: any; - - constructor(private ctxMenu: ShContextMenuService) { - } - - ngOnInit(): void { - console.log(this.data); - } - - @HostListener('contextmenu', ['$event']) - openMenu(event: MouseEvent) { - event.preventDefault(); - console.log(this.triggerFor); - this.ctxMenu.openMenu(this.triggerFor, event); - } -} From 07e40c6ee7350b336fd047a1ad563df1eab44ac1 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Mon, 19 Feb 2018 23:34:30 +0200 Subject: [PATCH 03/30] using cdk to posit ion the menu --- src/app/app.component.css | 14 +++-- src/app/app.component.html | 13 ++-- src/app/app.component.ts | 1 - src/lib/src/overlay.css | 1 + src/lib/src/sh-anchor-for.directive.ts | 18 ++---- src/lib/src/sh-context-menu.component.ts | 23 +++++--- src/lib/src/sh-context-menu.module.ts | 3 + src/lib/src/sh-context-menu.service.ts | 75 ++++++++++++++++++++++-- 8 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 src/lib/src/overlay.css diff --git a/src/app/app.component.css b/src/app/app.component.css index 82541ef..721c8ad 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -1,21 +1,27 @@ -.box{ + +.box { height: 180px; width: 180px; background-color: #e3e4c2; line-height: 140px; text-align: center; - box-shadow: 6px 6px 15px 2px rgba(0,0,0,.3); + box-shadow: 6px 6px 15px 2px rgba(0, 0, 0, .3); display: inline-block; margin: 20px; } -.box.box-right{ +.box.box-right { float: right; width: 240px; } -i.menu-icon{ +i.menu-icon { margin-right: 3px; display: inline-block; color: green; } + +.divider { + height: 5px; + background-color: lightblue; +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 7309385..132df5c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -6,13 +6,18 @@

{{title}}

{{title}}

- -
- one - {{item.label}} -
+ + +
+ one - {{item.label}} +
+
two - {{item.label}}
+
+
+
three - {{item.label}}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index fb9b0e6..a3e10c0 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -127,7 +127,6 @@ export class AppComponent { } onBefore = (event: any) => { - console.log(event); event.open([event.items[0]]); }; diff --git a/src/lib/src/overlay.css b/src/lib/src/overlay.css new file mode 100644 index 0000000..419a746 --- /dev/null +++ b/src/lib/src/overlay.css @@ -0,0 +1 @@ +@import "~@angular/cdk/overlay-prebuilt.css"; diff --git a/src/lib/src/sh-anchor-for.directive.ts b/src/lib/src/sh-anchor-for.directive.ts index b33cffe..ebc4549 100644 --- a/src/lib/src/sh-anchor-for.directive.ts +++ b/src/lib/src/sh-anchor-for.directive.ts @@ -1,27 +1,19 @@ -import {Directive, HostListener, Input, OnInit} from '@angular/core'; +import {Directive, ElementRef, HostListener, Input, OnInit} from '@angular/core'; import {ShContextMenuService} from './sh-context-menu.service'; import {ShContextMenuComponent} from './sh-context-menu.component'; @Directive({ selector: '[shAnchorFor]' }) -export class ShAnchorForDirective implements OnInit { - @Input('shAnchorFor') - menu: ShContextMenuComponent; - +export class ShAnchorForDirective { + @Input('shAnchorFor') menu: ShContextMenuComponent; @Input('shMenuData') data: any; - constructor(private ctxMenu: ShContextMenuService) { - } - - ngOnInit(): void { - console.log(this.data); - } + constructor(private ctxMenu: ShContextMenuService, private elm: ElementRef) { } @HostListener('contextmenu', ['$event']) openMenu(event: MouseEvent) { event.preventDefault(); - console.log(this.menu); - this.ctxMenu.openMenu(this.menu, event); + this.ctxMenu.openMenu(this.menu, event, this.elm); } } diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 45a09e0..72d2bc1 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -1,27 +1,32 @@ import { - AfterContentInit, Component, ContentChildren, OnInit, ViewChildren, - ViewContainerRef + AfterContentInit, AfterViewInit, Component, ContentChildren, ElementRef, OnInit, QueryList, ViewChildren, + ViewContainerRef, ViewEncapsulation } from '@angular/core'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; @Component({ selector: 'sh-context-menu', + encapsulation: ViewEncapsulation.None, + styleUrls: ['overlay.css'], template: ` ` }) -export class ShContextMenuComponent implements OnInit, AfterContentInit { +export class ShContextMenuComponent implements OnInit, AfterContentInit, AfterViewInit { - @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) items; - @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; + @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) items = new QueryList(); + @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems = new QueryList(); - constructor(private vcr: ViewContainerRef) { + constructor(public vcr: ViewContainerRef, public elm: ElementRef) { } ngAfterContentInit(): void { - console.log(this.items); + } + + ngAfterViewInit(): void { } ngOnInit() { + console.log('init'); } show() { @@ -33,7 +38,8 @@ export class ShContextMenuComponent implements OnInit, AfterContentInit { } } - private showMenu(items: ShContextMenuItemDirective[]) { + private showMenu(items: QueryList) { + console.log('changes'); items .forEach( item => this.vcr.createEmbeddedView(item.template, { @@ -42,3 +48,4 @@ export class ShContextMenuComponent implements OnInit, AfterContentInit { ); } } + diff --git a/src/lib/src/sh-context-menu.module.ts b/src/lib/src/sh-context-menu.module.ts index 63cb59e..e28079c 100755 --- a/src/lib/src/sh-context-menu.module.ts +++ b/src/lib/src/sh-context-menu.module.ts @@ -5,6 +5,7 @@ import {ShContextMenuComponent} from './sh-context-menu.component'; import {ShAnchorForDirective} from './sh-anchor-for.directive'; import {ShContextMenuService} from './sh-context-menu.service'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; +import {OverlayModule} from '@angular/cdk/overlay'; @NgModule({ declarations: [ @@ -22,8 +23,10 @@ import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; ], imports: [ CommonModule, + OverlayModule ], entryComponents: [ + ShContextMenuComponent ] }) export class ShContextMenuModule { diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index d572a8c..13e8983 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -1,13 +1,80 @@ -import {Injectable} from '@angular/core'; +import {ComponentRef, ElementRef, Injectable} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; +import {Overlay} from '@angular/cdk/overlay'; +import {ComponentPortal} from '@angular/cdk/portal'; +import {ConnectedPositionStrategy} from '@angular/cdk/overlay/typings/position/connected-position-strategy'; @Injectable() export class ShContextMenuService { - constructor() { + constructor(private overlay: Overlay) { } - openMenu(menu: ShContextMenuComponent, event: MouseEvent) { - menu.show(); + openMenu(menu: ShContextMenuComponent, event: MouseEvent, elm: ElementRef) { + + this.overrideGetBoundingClientRect(elm, event); + + const scrollStrategy = this.buildCloseScrollStrategy(); + const positionStrategy = this.buildConnectedPositionStrategy(elm); + + const overlayRef = this.overlay.create({ + positionStrategy: positionStrategy, + scrollStrategy: scrollStrategy + }); + + const menuPortal = new ComponentPortal(ShContextMenuComponent); + const componentRef: ComponentRef = overlayRef.attach(menuPortal); + + componentRef.instance.viewChildrenItems = menu.viewChildrenItems; + componentRef.instance.items = menu.items; + + componentRef.instance.show(); + } + + + private buildCloseScrollStrategy() { + return this.overlay.scrollStrategies.close(); + } + + private buildConnectedPositionStrategy(elm: ElementRef): ConnectedPositionStrategy { + return this + .overlay + .position() + .connectedTo(elm, + {originX: 'start', originY: 'bottom'}, + {overlayX: 'start', overlayY: 'top'}) + .withFallbackPosition( + {originX: 'start', originY: 'top'}, + {overlayX: 'start', overlayY: 'bottom'}) + .withFallbackPosition( + {originX: 'end', originY: 'top'}, + {overlayX: 'start', overlayY: 'top'}) + .withFallbackPosition( + {originX: 'start', originY: 'top'}, + {overlayX: 'end', overlayY: 'top'}) + .withFallbackPosition( + {originX: 'end', originY: 'center'}, + {overlayX: 'start', overlayY: 'center'}) + .withFallbackPosition( + {originX: 'start', originY: 'center'}, + {overlayX: 'end', overlayY: 'center'}); + } + + /* + we need to override getBoundingClientRect() to return the position of the menu. + this is done because @angular/cdk use this function internally to determine where the overlay should be positioned + https://github.com/angular/material2/blob/master/src/cdk/overlay/position/connected-position-strategy.ts#L288 + */ + private overrideGetBoundingClientRect(elm: ElementRef, event: MouseEvent) { + elm.nativeElement.getBoundingClientRect = (): ClientRect => { + return { + bottom: event.clientY, + height: 0, + left: event.clientX, + right: event.clientX, + top: event.clientY, + width: 0 + }; + }; } } From e587bc7b4619eb9c1252131ce576a17e6be06609 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Thu, 22 Feb 2018 12:55:18 +0200 Subject: [PATCH 04/30] context model --- src/lib/src/sh-anchor-for.directive.ts | 11 ++++++++--- src/lib/src/sh-context-menu.models.ts | 10 ++++++++++ src/lib/src/sh-context-menu.service.ts | 12 ++++++++---- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/lib/src/sh-anchor-for.directive.ts b/src/lib/src/sh-anchor-for.directive.ts index ebc4549..1bb21e1 100644 --- a/src/lib/src/sh-anchor-for.directive.ts +++ b/src/lib/src/sh-anchor-for.directive.ts @@ -9,11 +9,16 @@ export class ShAnchorForDirective { @Input('shAnchorFor') menu: ShContextMenuComponent; @Input('shMenuData') data: any; - constructor(private ctxMenu: ShContextMenuService, private elm: ElementRef) { } + constructor(private ctxMenu: ShContextMenuService, private elm: ElementRef) { + } @HostListener('contextmenu', ['$event']) openMenu(event: MouseEvent) { - event.preventDefault(); - this.ctxMenu.openMenu(this.menu, event, this.elm); + this.ctxMenu.openMenu({ + menu: this.menu, + mouseEvent: event, + targetElement: this.elm, + data: this.data + }); } } diff --git a/src/lib/src/sh-context-menu.models.ts b/src/lib/src/sh-context-menu.models.ts index 1f333a9..bcb2bb4 100644 --- a/src/lib/src/sh-context-menu.models.ts +++ b/src/lib/src/sh-context-menu.models.ts @@ -1,3 +1,13 @@ +import {ShContextMenuComponent} from './sh-context-menu.component'; +import {ElementRef} from '@angular/core'; + +export class ContextMenuEvent { + menu: ShContextMenuComponent; + mouseEvent: MouseEvent; + targetElement: ElementRef; + data: any; +} + export const ShContextDefaultOptions: IShContextOptions = { rtl: false, theme: 'light' diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index 13e8983..f18db1b 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -3,6 +3,7 @@ import {ShContextMenuComponent} from './sh-context-menu.component'; import {Overlay} from '@angular/cdk/overlay'; import {ComponentPortal} from '@angular/cdk/portal'; import {ConnectedPositionStrategy} from '@angular/cdk/overlay/typings/position/connected-position-strategy'; +import {ContextMenuEvent} from './sh-context-menu.models'; @Injectable() export class ShContextMenuService { @@ -10,12 +11,16 @@ export class ShContextMenuService { constructor(private overlay: Overlay) { } - openMenu(menu: ShContextMenuComponent, event: MouseEvent, elm: ElementRef) { + openMenu(ctxEvent: ContextMenuEvent) { + const {menu, mouseEvent, targetElement, data} = ctxEvent; - this.overrideGetBoundingClientRect(elm, event); + mouseEvent.preventDefault(); + mouseEvent.stopPropagation(); + + this.overrideGetBoundingClientRect(targetElement, mouseEvent); const scrollStrategy = this.buildCloseScrollStrategy(); - const positionStrategy = this.buildConnectedPositionStrategy(elm); + const positionStrategy = this.buildConnectedPositionStrategy(targetElement); const overlayRef = this.overlay.create({ positionStrategy: positionStrategy, @@ -31,7 +36,6 @@ export class ShContextMenuService { componentRef.instance.show(); } - private buildCloseScrollStrategy() { return this.overlay.scrollStrategies.close(); } From 35002b5807ef3801604320ffe1afdc903f1fe8de Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Thu, 22 Feb 2018 15:42:36 +0200 Subject: [PATCH 05/30] passing data obj to menu template --- src/lib/src/sh-context-menu.component.ts | 11 ++++---- src/lib/src/sh-context-menu.models.ts | 32 ------------------------ src/lib/src/sh-context-menu.service.ts | 2 +- 3 files changed, 7 insertions(+), 38 deletions(-) diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 72d2bc1..7b00b13 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -9,6 +9,7 @@ import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; encapsulation: ViewEncapsulation.None, styleUrls: ['overlay.css'], template: ` +
` }) export class ShContextMenuComponent implements OnInit, AfterContentInit, AfterViewInit { @@ -29,21 +30,21 @@ export class ShContextMenuComponent implements OnInit, AfterContentInit, AfterVi console.log('init'); } - show() { + show(data: any) { if (this.items.length) { - this.showMenu(this.items); + this.showMenu(this.items, data); } if (this.viewChildrenItems.length) { - this.showMenu(this.viewChildrenItems); + this.showMenu(this.viewChildrenItems, data); } } - private showMenu(items: QueryList) { + private showMenu(items: QueryList, data: any) { console.log('changes'); items .forEach( item => this.vcr.createEmbeddedView(item.template, { - $implicit: {label: 'testtt'} + $implicit: data }) ); } diff --git a/src/lib/src/sh-context-menu.models.ts b/src/lib/src/sh-context-menu.models.ts index bcb2bb4..9e976cd 100644 --- a/src/lib/src/sh-context-menu.models.ts +++ b/src/lib/src/sh-context-menu.models.ts @@ -7,35 +7,3 @@ export class ContextMenuEvent { targetElement: ElementRef; data: any; } - -export const ShContextDefaultOptions: IShContextOptions = { - rtl: false, - theme: 'light' -}; - -export interface IShContextMenuItem { - label?: ((context: any) => string) | string; - id?: string; - divider?: boolean; - subMenu?: boolean; - subMenuItems?: IShContextMenuItem[]; - data?: any; - - onClick?($event: any): void; - - visible?(context: any): boolean; - - disabled?(context: any): boolean; -} - -export interface IShContextOptions { - rtl?: boolean; - theme?: 'light' | 'dark'; -} - -export interface BeforeMenuEvent { - event: MouseEvent; - items: IShContextMenuItem[]; - - open(items?: IShContextMenuItem[]): void; -} diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index f18db1b..67e7c9d 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -33,7 +33,7 @@ export class ShContextMenuService { componentRef.instance.viewChildrenItems = menu.viewChildrenItems; componentRef.instance.items = menu.items; - componentRef.instance.show(); + componentRef.instance.show(data); } private buildCloseScrollStrategy() { From 733a9fa211eb9300a0eae20191036bdcb7e6512e Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Thu, 22 Feb 2018 16:37:59 +0200 Subject: [PATCH 06/30] ngTemplateOutlet instead ViewContainerRef --- src/app/app.component.html | 4 +- src/lib/src/sh-context-menu.component.ts | 48 +++++++++++------------- src/lib/src/sh-context-menu.service.ts | 2 +- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 132df5c..13f61ce 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -19,7 +19,9 @@

{{title}}

- three - {{item.label}} +
+ +
diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 7b00b13..6352abc 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -4,35 +4,35 @@ import { } from '@angular/core'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; +class ContextMenuItemWithData extends ShContextMenuItemDirective { + context: { + $implicit: any + }; +} + @Component({ selector: 'sh-context-menu', encapsulation: ViewEncapsulation.None, styleUrls: ['overlay.css'], template: ` -
- ` +
+ + + + +
+ ` }) -export class ShContextMenuComponent implements OnInit, AfterContentInit, AfterViewInit { - - @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) items = new QueryList(); +export class ShContextMenuComponent { + @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems = new QueryList(); @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems = new QueryList(); - constructor(public vcr: ViewContainerRef, public elm: ElementRef) { - } - - ngAfterContentInit(): void { - } - - ngAfterViewInit(): void { - } - - ngOnInit() { - console.log('init'); - } + items: ContextMenuItemWithData[] = []; show(data: any) { - if (this.items.length) { - this.showMenu(this.items, data); + if (this.contentChildrenItems.length) { + this.showMenu(this.contentChildrenItems, data); } if (this.viewChildrenItems.length) { this.showMenu(this.viewChildrenItems, data); @@ -40,13 +40,9 @@ export class ShContextMenuComponent implements OnInit, AfterContentInit, AfterVi } private showMenu(items: QueryList, data: any) { - console.log('changes'); - items - .forEach( - item => this.vcr.createEmbeddedView(item.template, { - $implicit: data - }) - ); + this.items = items.map((item) => { + return {...item, context: {$implicit: data}}; + }); } } diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index 67e7c9d..8ff4c83 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -31,7 +31,7 @@ export class ShContextMenuService { const componentRef: ComponentRef = overlayRef.attach(menuPortal); componentRef.instance.viewChildrenItems = menu.viewChildrenItems; - componentRef.instance.items = menu.items; + componentRef.instance.contentChildrenItems = menu.contentChildrenItems; componentRef.instance.show(data); } From f10c0eeabb886afe1fe9db532219d9a71f442e3b Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Sun, 25 Feb 2018 09:03:15 +0200 Subject: [PATCH 07/30] observable instead of HostListener --- src/lib/src/sh-anchor-for.directive.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib/src/sh-anchor-for.directive.ts b/src/lib/src/sh-anchor-for.directive.ts index 1bb21e1..f7d1a26 100644 --- a/src/lib/src/sh-anchor-for.directive.ts +++ b/src/lib/src/sh-anchor-for.directive.ts @@ -1,18 +1,24 @@ -import {Directive, ElementRef, HostListener, Input, OnInit} from '@angular/core'; +import {Directive, ElementRef, HostListener, Input, OnDestroy, OnInit} from '@angular/core'; import {ShContextMenuService} from './sh-context-menu.service'; import {ShContextMenuComponent} from './sh-context-menu.component'; +import {Subscription} from 'rxjs/Subscription'; +import {fromEvent} from 'rxjs/observable/fromEvent'; @Directive({ selector: '[shAnchorFor]' }) -export class ShAnchorForDirective { +export class ShAnchorForDirective implements OnDestroy { @Input('shAnchorFor') menu: ShContextMenuComponent; @Input('shMenuData') data: any; + sub: Subscription; + constructor(private ctxMenu: ShContextMenuService, private elm: ElementRef) { + this.sub = fromEvent(this.elm.nativeElement, 'contextmenu') + .subscribe(this.openMenu.bind(this)); } - @HostListener('contextmenu', ['$event']) + // @HostListener('contextmenu', ['$event']) openMenu(event: MouseEvent) { this.ctxMenu.openMenu({ menu: this.menu, @@ -21,4 +27,8 @@ export class ShAnchorForDirective { data: this.data }); } + + ngOnDestroy(): void { + this.sub.unsubscribe(); + } } From 5c59caabe0ff17acf194a5deb4ef42bfc7f6b215 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Sun, 25 Feb 2018 09:05:10 +0200 Subject: [PATCH 08/30] keep track of overlays and dispose them --- src/lib/src/sh-context-menu.service.ts | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index 8ff4c83..5e13d66 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -4,14 +4,18 @@ import {Overlay} from '@angular/cdk/overlay'; import {ComponentPortal} from '@angular/cdk/portal'; import {ConnectedPositionStrategy} from '@angular/cdk/overlay/typings/position/connected-position-strategy'; import {ContextMenuEvent} from './sh-context-menu.models'; +import {OverlayRef} from '@angular/cdk/overlay/typings/overlay-ref'; @Injectable() export class ShContextMenuService { + openOverlays: OverlayRef[] = []; + constructor(private overlay: Overlay) { } openMenu(ctxEvent: ContextMenuEvent) { + this.closeCurrentOverlays(); const {menu, mouseEvent, targetElement, data} = ctxEvent; mouseEvent.preventDefault(); @@ -34,6 +38,8 @@ export class ShContextMenuService { componentRef.instance.contentChildrenItems = menu.contentChildrenItems; componentRef.instance.show(data); + + this.openOverlays.push(overlayRef); } private buildCloseScrollStrategy() { @@ -70,15 +76,26 @@ export class ShContextMenuService { https://github.com/angular/material2/blob/master/src/cdk/overlay/position/connected-position-strategy.ts#L288 */ private overrideGetBoundingClientRect(elm: ElementRef, event: MouseEvent) { + const {clientX, clientY} = event; + elm.nativeElement.getBoundingClientRect = (): ClientRect => { return { - bottom: event.clientY, + bottom: clientY, height: 0, - left: event.clientX, - right: event.clientX, - top: event.clientY, + left: clientX, + right: clientX, + top: clientY, width: 0 }; }; } + + private closeCurrentOverlays() { + this.openOverlays.forEach((o) => { + o.detach(); + o.dispose(); + }); + + this.openOverlays = []; + } } From 84302bd6aed3ad3c229f64021ea81aeb9d44c4d7 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Sun, 25 Feb 2018 09:08:21 +0200 Subject: [PATCH 09/30] only show if items available --- src/app/app.component.css | 5 + src/app/app.component.html | 4 +- src/app/app.component.ts | 111 +---------------------- src/lib/src/sh-context-menu.component.ts | 5 +- 4 files changed, 13 insertions(+), 112 deletions(-) diff --git a/src/app/app.component.css b/src/app/app.component.css index 721c8ad..3697c14 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -25,3 +25,8 @@ i.menu-icon { height: 5px; background-color: lightblue; } + +.sh-context-menu{ + background-color: lightgray; + padding: 20px; +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 13f61ce..28225b9 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -19,8 +19,8 @@

{{title}}

-
- +
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a3e10c0..765e009 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -9,130 +9,24 @@ import {ShContextMenuComponent} from '../lib/src/sh-context-menu.component'; }) export class AppComponent { title = 'Right Click Me'; - titleRtl = 'Right Click Me (RTL)'; items: any[]; - itemsRtl: any[]; - dataCtxOne: any; - dataCtxTwo: any; - options: any; - - menuKey = 'firstMenu'; constructor() { - this.dataCtxOne = { - One: 'One' - }; - - this.dataCtxTwo = { - Two: 'Two' - }; - this.items = [ { - label: ' Save Me On Your HD', + label: 'text', onClick: this.clickEvent, }, { label: 'Edit', onClick: this.clickEvent - }, - { - label: ' Sub Menu', - subMenu: true, - subMenuItems: [ - { - label: 'Save', - onClick: this.clickEvent - }, - { - label: 'Edit', - onClick: this.clickEvent - }, - { - label: 'Another Sub Menu', - subMenu: true, - subMenuItems: [ - { - label: 'Save', - onClick: this.clickEvent - }, - { - label: 'Edit', - onClick: this.clickEvent - }] - } - ] - }, - { - divider: true - }, - { - label: 'Remove', - disabled: ctx => { - return ctx.Two === 'Two'; - }, - onClick: this.clickEvent - }, - { - label: 'Hidden', - onClick: this.clickEvent, - visible: ctx => { - return ctx.One === 'One'; - } - } - ]; - - this.itemsRtl = [ - { - label: 'שמור', - onClick: this.clickEvent - }, - { - label: 'ערוך', - onClick: this.clickEvent - }, - { - label: 'תפריט נוסף', - subMenu: true, - subMenuItems: [ - { - label: 'שמור', - onClick: this.clickEvent - }, - { - label: 'ערוך', - onClick: this.clickEvent - }, - { - label: 'עוד תפריט נוסף', - subMenu: true, - subMenuItems: [ - { - label: 'שמור', - onClick: this.clickEvent - }, - { - label: 'ערוך', - onClick: this.clickEvent - }] - } - ] } ]; - - this.options = { - rtl: true, - theme: 'dark' - }; } - onBefore = (event: any) => { - event.open([event.items[0]]); - }; - clickEvent = ($event: any) => { console.log('clicked ', $event); - }; + } } @Component({ @@ -147,5 +41,4 @@ export class AppComponent { ` }) export class MyMenuComponent extends ShContextMenuComponent { - @Input() item: any; } diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 6352abc..65ef725 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -15,7 +15,7 @@ class ContextMenuItemWithData extends ShContextMenuItemDirective { encapsulation: ViewEncapsulation.None, styleUrls: ['overlay.css'], template: ` -
+
@@ -31,9 +31,12 @@ export class ShContextMenuComponent { items: ContextMenuItemWithData[] = []; show(data: any) { + // when using the ShContextMenuComponent as menu, the ContentChildren is the source if (this.contentChildrenItems.length) { this.showMenu(this.contentChildrenItems, data); } + + // when using a custom component as menu the ViewChildren is the source if (this.viewChildrenItems.length) { this.showMenu(this.viewChildrenItems, data); } From 682e555cf8973fcba5aea763632e3f9decd2b41e Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Sun, 25 Feb 2018 14:12:29 +0200 Subject: [PATCH 10/30] backdrop --- src/lib/src/overlay.css | 4 +++ src/lib/src/sh-context-menu.service.ts | 50 +++++++++++++++++++------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/lib/src/overlay.css b/src/lib/src/overlay.css index 419a746..378b92d 100644 --- a/src/lib/src/overlay.css +++ b/src/lib/src/overlay.css @@ -1 +1,5 @@ @import "~@angular/cdk/overlay-prebuilt.css"; + +.sh-backdrop{ + background-color: transparent; +} diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index 5e13d66..4d02d51 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -1,15 +1,17 @@ -import {ComponentRef, ElementRef, Injectable} from '@angular/core'; +import {ComponentRef, ElementRef, Injectable, OnDestroy} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; -import {Overlay} from '@angular/cdk/overlay'; +import {CloseScrollStrategy, Overlay} from '@angular/cdk/overlay'; import {ComponentPortal} from '@angular/cdk/portal'; import {ConnectedPositionStrategy} from '@angular/cdk/overlay/typings/position/connected-position-strategy'; import {ContextMenuEvent} from './sh-context-menu.models'; import {OverlayRef} from '@angular/cdk/overlay/typings/overlay-ref'; +import {fromEvent} from 'rxjs/observable/fromEvent'; +import {Subscription} from 'rxjs/Subscription'; @Injectable() -export class ShContextMenuService { - +export class ShContextMenuService implements OnDestroy { openOverlays: OverlayRef[] = []; + backDropSub: Subscription; constructor(private overlay: Overlay) { } @@ -26,20 +28,39 @@ export class ShContextMenuService { const scrollStrategy = this.buildCloseScrollStrategy(); const positionStrategy = this.buildConnectedPositionStrategy(targetElement); - const overlayRef = this.overlay.create({ - positionStrategy: positionStrategy, - scrollStrategy: scrollStrategy - }); + const {overlayRef, componentRef} = this.createAndAttachOverlay(positionStrategy, scrollStrategy); - const menuPortal = new ComponentPortal(ShContextMenuComponent); - const componentRef: ComponentRef = overlayRef.attach(menuPortal); + this.setupComponentBindings(componentRef, menu, data); + componentRef.instance.show(data); + + this.openOverlays.push(overlayRef); + + this.registerToBackdropClick(overlayRef); + } + + private registerToBackdropClick(overlayRef: OverlayRef) { + this.backDropSub = fromEvent(overlayRef.backdropElement, 'mousedown') + .subscribe(this.closeCurrentOverlays.bind(this)); + } + private setupComponentBindings(componentRef: ComponentRef, menu: ShContextMenuComponent, data: any) { componentRef.instance.viewChildrenItems = menu.viewChildrenItems; componentRef.instance.contentChildrenItems = menu.contentChildrenItems; + } - componentRef.instance.show(data); + private createAndAttachOverlay(positionStrategy: ConnectedPositionStrategy, scrollStrategy: CloseScrollStrategy) { + const overlayRef = this.overlay.create({ + positionStrategy, + scrollStrategy, + hasBackdrop: true, + backdropClass: 'sh-backdrop' + }); - this.openOverlays.push(overlayRef); + const menuPortal = new ComponentPortal(ShContextMenuComponent); + + const componentRef: ComponentRef = overlayRef.attach(menuPortal); + + return {overlayRef, componentRef}; } private buildCloseScrollStrategy() { @@ -98,4 +119,9 @@ export class ShContextMenuService { this.openOverlays = []; } + + ngOnDestroy(): void { + this.closeCurrentOverlays(); + this.backDropSub.unsubscribe(); + } } From d2ff970fe3aa40f72cc02927bd47727bafa05f43 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Mon, 2 Apr 2018 18:16:21 +0300 Subject: [PATCH 11/30] sub menu open --- .angular-cli.json | 8 ++- src/app/app.component.css | 11 --- src/app/app.component.html | 25 +++++-- src/app/app.component.ts | 4 ++ src/lib/src/overlay.css | 5 -- src/lib/src/sh-anchor-for-sub.directive.ts | 50 +++++++++++++ src/lib/src/sh-anchor-for.directive.ts | 7 +- src/lib/src/sh-context-menu-item.directive.ts | 11 ++- src/lib/src/sh-context-menu.component.ts | 72 ++++++++++++++----- src/lib/src/sh-context-menu.css | 49 +++++++++++++ src/lib/src/sh-context-menu.models.ts | 4 ++ src/lib/src/sh-context-menu.module.ts | 4 +- src/lib/src/sh-context-menu.service.ts | 52 +++++++++++--- 13 files changed, 245 insertions(+), 57 deletions(-) delete mode 100644 src/lib/src/overlay.css create mode 100644 src/lib/src/sh-anchor-for-sub.directive.ts create mode 100644 src/lib/src/sh-context-menu.css diff --git a/.angular-cli.json b/.angular-cli.json index 4a640df..7138974 100644 --- a/.angular-cli.json +++ b/.angular-cli.json @@ -26,7 +26,8 @@ "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts" - } + }, + "showCircularDependencies": false } ], "e2e": { @@ -55,6 +56,9 @@ }, "defaults": { "styleExt": "css", - "component": {} + "component": {}, + "build": { + "showCircularDependencies": false + } } } diff --git a/src/app/app.component.css b/src/app/app.component.css index 3697c14..bfc3947 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -1,4 +1,3 @@ - .box { height: 180px; width: 180px; @@ -20,13 +19,3 @@ i.menu-icon { display: inline-block; color: green; } - -.divider { - height: 5px; - background-color: lightblue; -} - -.sh-context-menu{ - background-color: lightgray; - padding: 20px; -} diff --git a/src/app/app.component.html b/src/app/app.component.html index 28225b9..47c6a61 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -7,22 +7,33 @@

{{title}}

- -
- one - {{item.label}} -
-
+ one - {{item.label}} +
+
two - {{item.label}}
-
-
+
+
+
+ one - {{item.label}}
+ + +
+ From Sub Menu - {{item.label}} +
+ +
+ From Sub Menu Two - {{item.label}} +
+
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 765e009..08ec412 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -26,6 +26,10 @@ export class AppComponent { clickEvent = ($event: any) => { console.log('clicked ', $event); + }; + + onClick(evt) { + console.log('clicked', evt); } } diff --git a/src/lib/src/overlay.css b/src/lib/src/overlay.css deleted file mode 100644 index 378b92d..0000000 --- a/src/lib/src/overlay.css +++ /dev/null @@ -1,5 +0,0 @@ -@import "~@angular/cdk/overlay-prebuilt.css"; - -.sh-backdrop{ - background-color: transparent; -} diff --git a/src/lib/src/sh-anchor-for-sub.directive.ts b/src/lib/src/sh-anchor-for-sub.directive.ts new file mode 100644 index 0000000..b6690b5 --- /dev/null +++ b/src/lib/src/sh-anchor-for-sub.directive.ts @@ -0,0 +1,50 @@ +// import {Directive, ElementRef, Input, OnDestroy} from '@angular/core'; +// import {ShContextMenuComponent} from './sh-context-menu.component'; +// import {fromEvent} from 'rxjs/observable/fromEvent'; +// import {Subscription} from 'rxjs/Subscription'; +// import {ShContextMenuService} from './sh-context-menu.service'; +// +// @Directive({ +// selector: '[shAnchorForSub]' +// }) +// export class ShAnchorForSubDirective implements OnDestroy { +// @Input('shAnchorForSub') menu: ShContextMenuComponent; +// @Input('shMenuData') data: any; +// +// sub: Subscription; +// +// open = false; +// +// constructor(private ctxService: ShContextMenuService, +// private elm: ElementRef, +// private hostMenu: ShContextMenuComponent) { +// this.addClasses(); +// +// this.sub = fromEvent(this.elm.nativeElement, 'mouseenter') +// .subscribe(this.openSubMenu.bind(this)); +// } +// +// openSubMenu(event: MouseEvent) { +// if (this.open) { +// return; +// } +// +// this.open = true; +// this.ctxService.openSubMenu({ +// menu: this.menu, +// mouseEvent: event, +// targetElement: this.elm, +// data: this.data, +// hostMenu: this.hostMenu +// }); +// } +// +// addClasses() { +// const element: HTMLElement = this.elm.nativeElement; +// element.classList.add('sh-sub-anchor'); +// } +// +// ngOnDestroy(): void { +// this.sub.unsubscribe(); +// } +// } diff --git a/src/lib/src/sh-anchor-for.directive.ts b/src/lib/src/sh-anchor-for.directive.ts index f7d1a26..0a3152e 100644 --- a/src/lib/src/sh-anchor-for.directive.ts +++ b/src/lib/src/sh-anchor-for.directive.ts @@ -1,4 +1,4 @@ -import {Directive, ElementRef, HostListener, Input, OnDestroy, OnInit} from '@angular/core'; +import {Directive, ElementRef, Input, OnDestroy} from '@angular/core'; import {ShContextMenuService} from './sh-context-menu.service'; import {ShContextMenuComponent} from './sh-context-menu.component'; import {Subscription} from 'rxjs/Subscription'; @@ -13,14 +13,13 @@ export class ShAnchorForDirective implements OnDestroy { sub: Subscription; - constructor(private ctxMenu: ShContextMenuService, private elm: ElementRef) { + constructor(private ctxService: ShContextMenuService, private elm: ElementRef) { this.sub = fromEvent(this.elm.nativeElement, 'contextmenu') .subscribe(this.openMenu.bind(this)); } - // @HostListener('contextmenu', ['$event']) openMenu(event: MouseEvent) { - this.ctxMenu.openMenu({ + this.ctxService.openMenu({ menu: this.menu, mouseEvent: event, targetElement: this.elm, diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index 2249728..ef1c144 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -1,9 +1,16 @@ -import {Directive, TemplateRef} from '@angular/core'; +import {Directive, Input, OnInit, TemplateRef} from '@angular/core'; +import {ShContextMenuComponent} from './sh-context-menu.component'; @Directive({ selector: '[shContextMenuItem]' }) -export class ShContextMenuItemDirective { +export class ShContextMenuItemDirective implements OnInit { + @Input('shContextMenuItemSubMenu') subMenu: ShContextMenuComponent; + constructor(public template: TemplateRef) { } + + ngOnInit(): void { + console.log(this.subMenu); + } } diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 65ef725..c8ac32f 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -1,51 +1,91 @@ import { - AfterContentInit, AfterViewInit, Component, ContentChildren, ElementRef, OnInit, QueryList, ViewChildren, - ViewContainerRef, ViewEncapsulation + Component, ContentChildren, ElementRef, QueryList, ViewChildren, + ViewEncapsulation } from '@angular/core'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; +import {ShContextMenuService} from './sh-context-menu.service'; class ContextMenuItemWithData extends ShContextMenuItemDirective { context: { $implicit: any }; + open: boolean; } @Component({ selector: 'sh-context-menu', encapsulation: ViewEncapsulation.None, - styleUrls: ['overlay.css'], + styleUrls: ['sh-context-menu.css'], template: `
- +
- +
` }) export class ShContextMenuComponent { - @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems = new QueryList(); - @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems = new QueryList(); + @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems; + @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; items: ContextMenuItemWithData[] = []; - show(data: any) { + constructor(private ctxService: ShContextMenuService) { + this.contentChildrenItems = new QueryList(); + this.viewChildrenItems = new QueryList(); + } + + get menuItems() { // when using the ShContextMenuComponent as menu, the ContentChildren is the source if (this.contentChildrenItems.length) { - this.showMenu(this.contentChildrenItems, data); + return this.contentChildrenItems; } // when using a custom component as menu the ViewChildren is the source - if (this.viewChildrenItems.length) { - this.showMenu(this.viewChildrenItems, data); - } + return this.viewChildrenItems; + } + + set menuItems(items: ContextMenuItemWithData[]) { + this.items = [].concat(items); + } + + show(data: any) { + this.menuItems = this.menuItems.map(this.mapItems(data)); } - private showMenu(items: QueryList, data: any) { - this.items = items.map((item) => { + private mapItems(data) { + return (item) => { return {...item, context: {$implicit: data}}; - }); + }; } -} + onEnter($event: MouseEvent, item: ContextMenuItemWithData, elm: HTMLElement) { + if (!item.subMenu) { + // after small delay - close all child submenus if any + return; + } + + if (!item.open) { + item.open = true; + this.ctxService.openSubMenu({ + hostMenu: this, + data: item.context.$implicit, + targetElement: new ElementRef(elm), + menu: item.subMenu, + mouseEvent: $event + }); + } + } + + onLeave($event: MouseEvent, item: ContextMenuItemWithData, elm: HTMLElement) { + + } +} diff --git a/src/lib/src/sh-context-menu.css b/src/lib/src/sh-context-menu.css new file mode 100644 index 0000000..59f0814 --- /dev/null +++ b/src/lib/src/sh-context-menu.css @@ -0,0 +1,49 @@ +@import "~@angular/cdk/overlay-prebuilt.css"; + +.sh-backdrop { + background-color: transparent; +} + +.sh-context-menu { + background: #ececec; + min-width: 150px; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 3px; + box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1); + color: black; + padding: 5px 0; + margin: 0; +} + +.sh-context-menu--item { + padding: 5px 10px 5px 15px; + transition: all 0.15s; +} + +.sh-context-menu--item:hover { + background-color: #4b8bec; + color: white; + cursor: pointer; +} + +.sh-context-menu--item .sh-divider { + height: 1px; + margin: 1px 1px 8px 1px; + overflow: hidden; + border-bottom: 1px solid #d0d0d0; +} + +.sh-context-menu--item.sh-sub-anchor { + position: relative; +} + +.sh-sub-anchor:after { + content: ""; + top: 50%; + right: 6px; + transform: translateY(-50%); + position: absolute; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 8px solid black; +} diff --git a/src/lib/src/sh-context-menu.models.ts b/src/lib/src/sh-context-menu.models.ts index 9e976cd..98d29e5 100644 --- a/src/lib/src/sh-context-menu.models.ts +++ b/src/lib/src/sh-context-menu.models.ts @@ -7,3 +7,7 @@ export class ContextMenuEvent { targetElement: ElementRef; data: any; } + +export class ContextSubMenuEvent extends ContextMenuEvent{ + hostMenu: ShContextMenuComponent; +} diff --git a/src/lib/src/sh-context-menu.module.ts b/src/lib/src/sh-context-menu.module.ts index e28079c..50096ef 100755 --- a/src/lib/src/sh-context-menu.module.ts +++ b/src/lib/src/sh-context-menu.module.ts @@ -11,12 +11,12 @@ import {OverlayModule} from '@angular/cdk/overlay'; declarations: [ ShAnchorForDirective, ShContextMenuComponent, - ShContextMenuItemDirective + ShContextMenuItemDirective, ], exports: [ ShAnchorForDirective, ShContextMenuComponent, - ShContextMenuItemDirective + ShContextMenuItemDirective, ], providers: [ ShContextMenuService diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index 4d02d51..aca8903 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -3,10 +3,11 @@ import {ShContextMenuComponent} from './sh-context-menu.component'; import {CloseScrollStrategy, Overlay} from '@angular/cdk/overlay'; import {ComponentPortal} from '@angular/cdk/portal'; import {ConnectedPositionStrategy} from '@angular/cdk/overlay/typings/position/connected-position-strategy'; -import {ContextMenuEvent} from './sh-context-menu.models'; -import {OverlayRef} from '@angular/cdk/overlay/typings/overlay-ref'; +import {ContextMenuEvent, ContextSubMenuEvent} from './sh-context-menu.models'; +import {OverlayRef} from '@angular/cdk/overlay'; import {fromEvent} from 'rxjs/observable/fromEvent'; import {Subscription} from 'rxjs/Subscription'; +import {merge} from 'rxjs/observable/merge'; @Injectable() export class ShContextMenuService implements OnDestroy { @@ -35,24 +36,47 @@ export class ShContextMenuService implements OnDestroy { this.openOverlays.push(overlayRef); - this.registerToBackdropClick(overlayRef); + this.registerBackdropEvents(overlayRef); } - private registerToBackdropClick(overlayRef: OverlayRef) { - this.backDropSub = fromEvent(overlayRef.backdropElement, 'mousedown') + openSubMenu(ctxEvent: ContextSubMenuEvent) { + const {menu, mouseEvent, targetElement, data} = ctxEvent; + + mouseEvent.preventDefault(); + mouseEvent.stopPropagation(); + + const scrollStrategy = this.buildCloseScrollStrategy(); + const positionStrategy = this.buildConnectedPositionStrategyForSubMenu(targetElement); + const {overlayRef, componentRef} = this.createAndAttachOverlay(positionStrategy, scrollStrategy, false); + + this.setupComponentBindings(componentRef, menu, data); + componentRef.instance.show(data); + this.openOverlays.push(overlayRef); + } + + private registerBackdropEvents(overlayRef: OverlayRef) { + const elm = overlayRef.backdropElement; + + const mouseup$ = fromEvent(elm, 'mouseup'); + const context$ = fromEvent(elm, 'contextmenu'); + + this.backDropSub = merge(mouseup$, context$) .subscribe(this.closeCurrentOverlays.bind(this)); } - private setupComponentBindings(componentRef: ComponentRef, menu: ShContextMenuComponent, data: any) { + private setupComponentBindings(componentRef: ComponentRef, + menu: ShContextMenuComponent, data: any) { componentRef.instance.viewChildrenItems = menu.viewChildrenItems; componentRef.instance.contentChildrenItems = menu.contentChildrenItems; } - private createAndAttachOverlay(positionStrategy: ConnectedPositionStrategy, scrollStrategy: CloseScrollStrategy) { + private createAndAttachOverlay(positionStrategy: ConnectedPositionStrategy, + scrollStrategy: CloseScrollStrategy, + hasBackdrop: boolean = true) { const overlayRef = this.overlay.create({ positionStrategy, scrollStrategy, - hasBackdrop: true, + hasBackdrop: hasBackdrop, backdropClass: 'sh-backdrop' }); @@ -91,6 +115,18 @@ export class ShContextMenuService implements OnDestroy { {overlayX: 'end', overlayY: 'center'}); } + private buildConnectedPositionStrategyForSubMenu(elm: ElementRef): ConnectedPositionStrategy { + return this + .overlay + .position() + .connectedTo(elm, + {originX: 'end', originY: 'bottom'}, + {overlayX: 'start', overlayY: 'top'}) + .withFallbackPosition( + {originX: 'start', originY: 'top'}, + {overlayX: 'start', overlayY: 'bottom'}); + } + /* we need to override getBoundingClientRect() to return the position of the menu. this is done because @angular/cdk use this function internally to determine where the overlay should be positioned From 27ff0c0fb68d8f708e774976a058129fd4934ba4 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Mon, 2 Apr 2018 18:20:34 +0300 Subject: [PATCH 12/30] min widht on sub menu anchor --- src/lib/src/sh-context-menu.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/src/sh-context-menu.css b/src/lib/src/sh-context-menu.css index 59f0814..bb0bd1e 100644 --- a/src/lib/src/sh-context-menu.css +++ b/src/lib/src/sh-context-menu.css @@ -35,6 +35,7 @@ .sh-context-menu--item.sh-sub-anchor { position: relative; + min-width: 160px; } .sh-sub-anchor:after { From 3d42574ecbd17ccce2e5340f84c18af7d4738edd Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Tue, 3 Apr 2018 17:32:02 +0300 Subject: [PATCH 13/30] backdrop events --- src/lib/src/sh-context-menu.service.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index aca8903..00bc97b 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -57,10 +57,7 @@ export class ShContextMenuService implements OnDestroy { private registerBackdropEvents(overlayRef: OverlayRef) { const elm = overlayRef.backdropElement; - const mouseup$ = fromEvent(elm, 'mouseup'); - const context$ = fromEvent(elm, 'contextmenu'); - - this.backDropSub = merge(mouseup$, context$) + this.backDropSub = fromEvent(elm, 'mousedown') .subscribe(this.closeCurrentOverlays.bind(this)); } From 02b73a5549855921bde3a8da6cee5315a70043e1 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Tue, 3 Apr 2018 17:58:57 +0300 Subject: [PATCH 14/30] divider --- src/app/app.component.html | 2 +- src/lib/src/sh-context-menu-item.directive.ts | 7 ++----- src/lib/src/sh-context-menu.component.ts | 11 +++++++---- src/lib/src/sh-context-menu.css | 8 +++++++- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 47c6a61..095ac98 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -13,7 +13,7 @@

{{title}}

two - {{item.label}}
-
+
one - {{item.label}} diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index ef1c144..a49e017 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -4,13 +4,10 @@ import {ShContextMenuComponent} from './sh-context-menu.component'; @Directive({ selector: '[shContextMenuItem]' }) -export class ShContextMenuItemDirective implements OnInit { +export class ShContextMenuItemDirective { @Input('shContextMenuItemSubMenu') subMenu: ShContextMenuComponent; + @Input('shContextMenuItemDivider') divider = false; constructor(public template: TemplateRef) { } - - ngOnInit(): void { - console.log(this.subMenu); - } } diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index c8ac32f..71f72ab 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -21,13 +21,16 @@ class ContextMenuItemWithData extends ShContextMenuItemDirective {
- - + + + +
` diff --git a/src/lib/src/sh-context-menu.css b/src/lib/src/sh-context-menu.css index bb0bd1e..d44ca3b 100644 --- a/src/lib/src/sh-context-menu.css +++ b/src/lib/src/sh-context-menu.css @@ -26,7 +26,13 @@ cursor: pointer; } -.sh-context-menu--item .sh-divider { +.sh-context-menu--item.sh-context-menu--item__divider:hover { + background-color: #ececec; + color: black; + cursor: default; +} + +.sh-context-menu--item__divider { height: 1px; margin: 1px 1px 8px 1px; overflow: hidden; From 43e1b7ccb7978c0e2fa02ffcc5228c4a9323d089 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Sat, 7 Apr 2018 07:30:11 +0300 Subject: [PATCH 15/30] menu item 'with' syntax for sub menus --- src/app/app.component.html | 10 ++++------ src/app/app.component.ts | 8 ++++++++ src/lib/src/sh-context-menu-item.directive.ts | 4 ++-- src/lib/src/sh-context-menu.component.ts | 9 ++++++++- src/lib/src/sh-context-menu.module.ts | 4 ++-- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 095ac98..fbc03ef 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -6,11 +6,11 @@

{{title}}

{{title}}

- +
one - {{item.label}}
-
+
two - {{item.label}}
@@ -19,13 +19,11 @@

{{title}}

one - {{item.label}}
-
- -
+
-
+
From Sub Menu - {{item.label}}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 08ec412..a48a51b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -42,6 +42,14 @@ export class AppComponent {
from comp !! - {{item.label}}
+ + + + + + + + ` }) export class MyMenuComponent extends ShContextMenuComponent { diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index a49e017..73e8482 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -1,11 +1,11 @@ -import {Directive, Input, OnInit, TemplateRef} from '@angular/core'; +import {Directive, EventEmitter, Input, OnInit, Output, TemplateRef} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; @Directive({ selector: '[shContextMenuItem]' }) export class ShContextMenuItemDirective { - @Input('shContextMenuItemSubMenu') subMenu: ShContextMenuComponent; + @Input('shContextMenuItemWith') subMenu: ShContextMenuComponent; @Input('shContextMenuItemDivider') divider = false; constructor(public template: TemplateRef) { diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 71f72ab..5d315fa 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -72,7 +72,7 @@ export class ShContextMenuComponent { onEnter($event: MouseEvent, item: ContextMenuItemWithData, elm: HTMLElement) { if (!item.subMenu) { - // after small delay - close all child submenus if any + // TODO: after small delay - close all child submenus if any return; } @@ -91,4 +91,11 @@ export class ShContextMenuComponent { onLeave($event: MouseEvent, item: ContextMenuItemWithData, elm: HTMLElement) { } + + onClick($event: MouseEvent, item: ContextMenuItemWithData) { + console.log('from component', $event, item); + if (!item.divider) { + // this.ctxService.close(this); + } + } } diff --git a/src/lib/src/sh-context-menu.module.ts b/src/lib/src/sh-context-menu.module.ts index 50096ef..e28079c 100755 --- a/src/lib/src/sh-context-menu.module.ts +++ b/src/lib/src/sh-context-menu.module.ts @@ -11,12 +11,12 @@ import {OverlayModule} from '@angular/cdk/overlay'; declarations: [ ShAnchorForDirective, ShContextMenuComponent, - ShContextMenuItemDirective, + ShContextMenuItemDirective ], exports: [ ShAnchorForDirective, ShContextMenuComponent, - ShContextMenuItemDirective, + ShContextMenuItemDirective ], providers: [ ShContextMenuService From e745769ac3b61aea7b7bd4bbe7f5db38699b72b6 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Sun, 8 Apr 2018 14:03:42 +0300 Subject: [PATCH 16/30] few additions: click handler with 'on' syntax, support different triggers, set the 'this' context when invoking the click handler using [thisContext] --- src/app/app.component.html | 18 +++--- src/app/app.component.ts | 24 ++++---- src/lib/src/sh-anchor-for.directive.ts | 27 +++++++-- src/lib/src/sh-context-menu-item.directive.ts | 23 +++++++- src/lib/src/sh-context-menu.component.ts | 59 ++++++++++--------- src/lib/src/sh-context-menu.service.ts | 7 ++- 6 files changed, 98 insertions(+), 60 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index fbc03ef..366649c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,21 +1,21 @@ -
-

{{title}}

+
+

Menu One

-
-

{{title}}

+
+

Menu Two

- -
+ +
one - {{item.label}}
-
+
two - {{item.label}}
-
+
-
+
one - {{item.label}}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a48a51b..5081b0b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -11,25 +11,21 @@ export class AppComponent { title = 'Right Click Me'; items: any[]; + thisContext = this; + constructor() { this.items = [ { - label: 'text', - onClick: this.clickEvent, + label: 'Item One' }, { - label: 'Edit', - onClick: this.clickEvent + label: 'Item Two' } ]; } - clickEvent = ($event: any) => { - console.log('clicked ', $event); - }; - - onClick(evt) { - console.log('clicked', evt); + onClick(item) { + console.log('clicked', this); } } @@ -43,12 +39,12 @@ export class AppComponent { from comp !! - {{item.label}}
- + - - - + + + ` }) diff --git a/src/lib/src/sh-anchor-for.directive.ts b/src/lib/src/sh-anchor-for.directive.ts index 0a3152e..9fa4a51 100644 --- a/src/lib/src/sh-anchor-for.directive.ts +++ b/src/lib/src/sh-anchor-for.directive.ts @@ -1,21 +1,38 @@ -import {Directive, ElementRef, Input, OnDestroy} from '@angular/core'; +import {Directive, ElementRef, Input, OnDestroy, OnInit} from '@angular/core'; import {ShContextMenuService} from './sh-context-menu.service'; import {ShContextMenuComponent} from './sh-context-menu.component'; import {Subscription} from 'rxjs/Subscription'; import {fromEvent} from 'rxjs/observable/fromEvent'; +import {merge} from 'rxjs/observable/merge'; @Directive({ selector: '[shAnchorFor]' }) -export class ShAnchorForDirective implements OnDestroy { +export class ShAnchorForDirective implements OnDestroy, OnInit { @Input('shAnchorFor') menu: ShContextMenuComponent; + @Input('shMenuTriggers') triggers: string[]; @Input('shMenuData') data: any; - sub: Subscription; constructor(private ctxService: ShContextMenuService, private elm: ElementRef) { - this.sub = fromEvent(this.elm.nativeElement, 'contextmenu') - .subscribe(this.openMenu.bind(this)); + } + + ngOnInit(): void { + this.setupEvents(); + } + + private setupEvents() { + const observables = []; + + if (!this.triggers) { + observables.push(fromEvent(this.elm.nativeElement, 'contextmenu')); + } else { + this.triggers.forEach((t) => { + observables.push(fromEvent(this.elm.nativeElement, t)); + }); + } + + this.sub = merge(...observables).subscribe(this.openMenu.bind(this)); } openMenu(event: MouseEvent) { diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index 73e8482..bd6b73c 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -1,13 +1,30 @@ -import {Directive, EventEmitter, Input, OnInit, Output, TemplateRef} from '@angular/core'; +import {Directive, Input, OnInit, Optional, TemplateRef} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; +export class MenuItemContext { + $implicit: any; + subscriber: { + click?: any + }; + + constructor() { + this.subscriber = {}; + this.$implicit = {}; + } +} + @Directive({ selector: '[shContextMenuItem]' }) export class ShContextMenuItemDirective { @Input('shContextMenuItemWith') subMenu: ShContextMenuComponent; - @Input('shContextMenuItemDivider') divider = false; + @Input('shContextMenuItemOn') on: (data) => {}; + + @Input() divider = false; + + open: boolean; + context: MenuItemContext = new MenuItemContext(); - constructor(public template: TemplateRef) { + constructor(@Optional() public template: TemplateRef) { } } diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 5d315fa..6d048b6 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -1,17 +1,10 @@ import { - Component, ContentChildren, ElementRef, QueryList, ViewChildren, + Component, ContentChildren, ElementRef, Input, QueryList, ViewChildren, ViewEncapsulation } from '@angular/core'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; import {ShContextMenuService} from './sh-context-menu.service'; -class ContextMenuItemWithData extends ShContextMenuItemDirective { - context: { - $implicit: any - }; - open: boolean; -} - @Component({ selector: 'sh-context-menu', encapsulation: ViewEncapsulation.None, @@ -24,7 +17,8 @@ class ContextMenuItemWithData extends ShContextMenuItemDirective { [ngClass]="{'sh-sub-anchor': item.subMenu, 'sh-context-menu--item__divider': item.divider}" class="sh-context-menu--item" (mouseenter)="onEnter($event, item, itemElement)" - (mouseleave)="onLeave($event, item, itemElement)"> + (mouseleave)="onLeave($event, item, itemElement)" + (click)="onClick($event, item)"> (); - this.viewChildrenItems = new QueryList(); + this.contentChildrenItems = new QueryList(); + this.viewChildrenItems = new QueryList(); } get menuItems() { // when using the ShContextMenuComponent as menu, the ContentChildren is the source if (this.contentChildrenItems.length) { - return this.contentChildrenItems; + return this.contentChildrenItems.toArray(); } // when using a custom component as menu the ViewChildren is the source - return this.viewChildrenItems; + return this.viewChildrenItems.toArray(); } - set menuItems(items: ContextMenuItemWithData[]) { + set menuItems(items: ShContextMenuItemDirective[]) { this.items = [].concat(items); } show(data: any) { - this.menuItems = this.menuItems.map(this.mapItems(data)); + const cloned = [].concat(this.menuItems); + cloned.forEach((item) => { + item.context.$implicit = data; + }); + this.menuItems = cloned; } - private mapItems(data) { - return (item) => { - return {...item, context: {$implicit: data}}; - }; - } - - onEnter($event: MouseEvent, item: ContextMenuItemWithData, elm: HTMLElement) { + onEnter($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { if (!item.subMenu) { // TODO: after small delay - close all child submenus if any return; @@ -88,14 +82,23 @@ export class ShContextMenuComponent { } } - onLeave($event: MouseEvent, item: ContextMenuItemWithData, elm: HTMLElement) { + onLeave($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { } - onClick($event: MouseEvent, item: ContextMenuItemWithData) { - console.log('from component', $event, item); - if (!item.divider) { - // this.ctxService.close(this); + onClick($event: MouseEvent, item: ShContextMenuItemDirective) { + if (item.divider) { + return; } + + this.ctxService.destroy(); + + if (item.on) { + this.invokeWithContext(item.on, item, item.context.$implicit); + } + } + + private invokeWithContext(fn, fallbackContext, ...args) { + fn.call(this.thisContext ? this.thisContext : fallbackContext, ...args); } } diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index 00bc97b..d1975a4 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -65,6 +65,7 @@ export class ShContextMenuService implements OnDestroy { menu: ShContextMenuComponent, data: any) { componentRef.instance.viewChildrenItems = menu.viewChildrenItems; componentRef.instance.contentChildrenItems = menu.contentChildrenItems; + componentRef.instance.thisContext = menu.thisContext; } private createAndAttachOverlay(positionStrategy: ConnectedPositionStrategy, @@ -153,8 +154,12 @@ export class ShContextMenuService implements OnDestroy { this.openOverlays = []; } - ngOnDestroy(): void { + destroy() { this.closeCurrentOverlays(); this.backDropSub.unsubscribe(); } + + ngOnDestroy(): void { + this.destroy(); + } } From 38ad093786024b9df6511327eeb33d6375a45f7c Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Tue, 10 Apr 2018 01:34:44 +0300 Subject: [PATCH 17/30] saving overlayRef on menu --- src/app/app.component.html | 2 +- src/app/app.component.ts | 12 +++++ src/app/app.module.ts | 5 +- src/lib/src/sh-context-menu-item.directive.ts | 8 +-- src/lib/src/sh-context-menu.component.ts | 50 +++++++++++-------- src/lib/src/sh-context-menu.models.ts | 2 +- src/lib/src/sh-context-menu.service.ts | 10 ++-- 7 files changed, 53 insertions(+), 36 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 366649c..a3a7036 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -19,7 +19,7 @@

Menu Two

one - {{item.label}}
- +
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5081b0b..d25c3a9 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -50,3 +50,15 @@ export class AppComponent { }) export class MyMenuComponent extends ShContextMenuComponent { } + +@Component({ + selector: 'my-content', + template: ` +
+ +
+ ` +}) +export class MyContentComponent { + @Input() item: any; +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 5ed590c..79cfb5f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,13 +1,14 @@ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; -import {AppComponent, MyMenuComponent} from './app.component'; +import {AppComponent, MyContentComponent, MyMenuComponent} from './app.component'; import {ShContextMenuModule} from 'ng2-right-click-menu'; @NgModule({ declarations: [ AppComponent, - MyMenuComponent + MyMenuComponent, + MyContentComponent ], imports: [ BrowserModule, diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index bd6b73c..3b46d76 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -1,14 +1,10 @@ -import {Directive, Input, OnInit, Optional, TemplateRef} from '@angular/core'; +import {Directive, Input, Optional, TemplateRef} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; export class MenuItemContext { $implicit: any; - subscriber: { - click?: any - }; constructor() { - this.subscriber = {}; this.$implicit = {}; } } @@ -19,10 +15,8 @@ export class MenuItemContext { export class ShContextMenuItemDirective { @Input('shContextMenuItemWith') subMenu: ShContextMenuComponent; @Input('shContextMenuItemOn') on: (data) => {}; - @Input() divider = false; - open: boolean; context: MenuItemContext = new MenuItemContext(); constructor(@Optional() public template: TemplateRef) { diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 6d048b6..6d16992 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -1,9 +1,10 @@ import { - Component, ContentChildren, ElementRef, Input, QueryList, ViewChildren, + Component, ContentChildren, ElementRef, Input, OnDestroy, QueryList, ViewChildren, ViewEncapsulation } from '@angular/core'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; import {ShContextMenuService} from './sh-context-menu.service'; +import {OverlayRef} from '@angular/cdk/overlay'; @Component({ selector: 'sh-context-menu', @@ -29,39 +30,41 @@ import {ShContextMenuService} from './sh-context-menu.service';
` }) -export class ShContextMenuComponent { +export class ShContextMenuComponent implements OnDestroy { @Input() thisContext: any; @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems; @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; items: ShContextMenuItemDirective[] = []; + overlayRef: OverlayRef; + isSub = false; constructor(private ctxService: ShContextMenuService) { this.contentChildrenItems = new QueryList(); this.viewChildrenItems = new QueryList(); } - get menuItems() { + get menuItems(): QueryList { // when using the ShContextMenuComponent as menu, the ContentChildren is the source if (this.contentChildrenItems.length) { - return this.contentChildrenItems.toArray(); + return this.contentChildrenItems; } // when using a custom component as menu the ViewChildren is the source - return this.viewChildrenItems.toArray(); + return this.viewChildrenItems; } - set menuItems(items: ShContextMenuItemDirective[]) { - this.items = [].concat(items); + set menuItems(items: QueryList) { + this.items = items.toArray(); } show(data: any) { - const cloned = [].concat(this.menuItems); - cloned.forEach((item) => { + this.menuItems.forEach((item) => { item.context.$implicit = data; }); - this.menuItems = cloned; + + this.menuItems = this.menuItems; } onEnter($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { @@ -70,16 +73,13 @@ export class ShContextMenuComponent { return; } - if (!item.open) { - item.open = true; - this.ctxService.openSubMenu({ - hostMenu: this, - data: item.context.$implicit, - targetElement: new ElementRef(elm), - menu: item.subMenu, - mouseEvent: $event - }); - } + this.ctxService.openSubMenu({ + hostMenu: this, + data: item.context.$implicit, + targetElement: new ElementRef(elm), + menu: item.subMenu, + mouseEvent: $event + }); } onLeave($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { @@ -87,6 +87,8 @@ export class ShContextMenuComponent { } onClick($event: MouseEvent, item: ShContextMenuItemDirective) { + // TODO: handle click in service + if (item.divider) { return; } @@ -94,11 +96,15 @@ export class ShContextMenuComponent { this.ctxService.destroy(); if (item.on) { - this.invokeWithContext(item.on, item, item.context.$implicit); + this.callWithContext(item.on, item, item.context.$implicit); } } - private invokeWithContext(fn, fallbackContext, ...args) { + private callWithContext(fn, fallbackContext, ...args) { fn.call(this.thisContext ? this.thisContext : fallbackContext, ...args); } + + ngOnDestroy(): void { + // this.menuItems.forEach(i => i.open = false); + } } diff --git a/src/lib/src/sh-context-menu.models.ts b/src/lib/src/sh-context-menu.models.ts index 98d29e5..99559e8 100644 --- a/src/lib/src/sh-context-menu.models.ts +++ b/src/lib/src/sh-context-menu.models.ts @@ -8,6 +8,6 @@ export class ContextMenuEvent { data: any; } -export class ContextSubMenuEvent extends ContextMenuEvent{ +export class ContextSubMenuEvent extends ContextMenuEvent { hostMenu: ShContextMenuComponent; } diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index d1975a4..56bac21 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -7,7 +7,6 @@ import {ContextMenuEvent, ContextSubMenuEvent} from './sh-context-menu.models'; import {OverlayRef} from '@angular/cdk/overlay'; import {fromEvent} from 'rxjs/observable/fromEvent'; import {Subscription} from 'rxjs/Subscription'; -import {merge} from 'rxjs/observable/merge'; @Injectable() export class ShContextMenuService implements OnDestroy { @@ -49,8 +48,11 @@ export class ShContextMenuService implements OnDestroy { const positionStrategy = this.buildConnectedPositionStrategyForSubMenu(targetElement); const {overlayRef, componentRef} = this.createAndAttachOverlay(positionStrategy, scrollStrategy, false); - this.setupComponentBindings(componentRef, menu, data); + this.setupComponentBindings(componentRef, menu, overlayRef); + + componentRef.instance.isSub = true; componentRef.instance.show(data); + this.openOverlays.push(overlayRef); } @@ -62,10 +64,12 @@ export class ShContextMenuService implements OnDestroy { } private setupComponentBindings(componentRef: ComponentRef, - menu: ShContextMenuComponent, data: any) { + menu: ShContextMenuComponent, + overlayRef: OverlayRef) { componentRef.instance.viewChildrenItems = menu.viewChildrenItems; componentRef.instance.contentChildrenItems = menu.contentChildrenItems; componentRef.instance.thisContext = menu.thisContext; + componentRef.instance.overlayRef = overlayRef; } private createAndAttachOverlay(positionStrategy: ConnectedPositionStrategy, From b953e5cff11894c98bb231971087dd0f87d88c70 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Wed, 11 Apr 2018 15:54:06 +0300 Subject: [PATCH 18/30] container and template --- src/app/app.component.html | 28 +++++----- src/app/app.component.ts | 1 + src/lib/src/sh-context-menu.component.ts | 70 ++++++++++++++---------- src/lib/src/sh-context-menu.service.ts | 23 ++++++-- 4 files changed, 73 insertions(+), 49 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index a3a7036..62d7464 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -10,9 +10,9 @@

Menu Two

one - {{item.label}}
-
- two - {{item.label}} -
+ + +
@@ -22,16 +22,16 @@

Menu Two

- -
- From Sub Menu - {{item.label}} -
- -
- From Sub Menu Two - {{item.label}} -
-
-
+ + + + + + + + + + - + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d25c3a9..0458db5 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,6 @@ import {Component, Input, ViewEncapsulation} from '@angular/core'; import {ShContextMenuComponent} from '../lib/src/sh-context-menu.component'; +import {Observable} from 'rxjs/Observable'; @Component({ selector: 'app-root', diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 6d16992..9c58440 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -1,5 +1,7 @@ import { - Component, ContentChildren, ElementRef, Input, OnDestroy, QueryList, ViewChildren, + AfterContentInit, + AfterViewInit, + Component, ContentChildren, ElementRef, Input, OnDestroy, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ViewEncapsulation } from '@angular/core'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; @@ -11,40 +13,52 @@ import {OverlayRef} from '@angular/cdk/overlay'; encapsulation: ViewEncapsulation.None, styleUrls: ['sh-context-menu.css'], template: ` -
-
- - - - + + +
+
+ + + + +
-
+ ` }) -export class ShContextMenuComponent implements OnDestroy { +export class ShContextMenuComponent implements OnDestroy, AfterViewInit, AfterContentInit { @Input() thisContext: any; - @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems; @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; - items: ShContextMenuItemDirective[] = []; + @ViewChild('componentTemplate', {read: TemplateRef}) cmpTemplate; + @ViewChild('componentContainer', {read: ViewContainerRef}) cmpContainer; + overlayRef: OverlayRef; isSub = false; + items: ShContextMenuItemDirective[]; constructor(private ctxService: ShContextMenuService) { this.contentChildrenItems = new QueryList(); this.viewChildrenItems = new QueryList(); } + ngAfterViewInit(): void { + console.log('view init'); + } + + ngAfterContentInit(): void { + console.log('content init'); + } + get menuItems(): QueryList { // when using the ShContextMenuComponent as menu, the ContentChildren is the source if (this.contentChildrenItems.length) { @@ -55,21 +69,19 @@ export class ShContextMenuComponent implements OnDestroy { return this.viewChildrenItems; } - set menuItems(items: QueryList) { - this.items = items.toArray(); - } - show(data: any) { this.menuItems.forEach((item) => { item.context.$implicit = data; }); - this.menuItems = this.menuItems; + this.cmpContainer.createEmbeddedView(this.cmpTemplate); } onEnter($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { + // TODO: close all child submenus + this.ctxService.closeSubMenus(this); + if (!item.subMenu) { - // TODO: after small delay - close all child submenus if any return; } @@ -93,9 +105,8 @@ export class ShContextMenuComponent implements OnDestroy { return; } - this.ctxService.destroy(); - if (item.on) { + this.ctxService.destroy(); this.callWithContext(item.on, item, item.context.$implicit); } } @@ -105,6 +116,7 @@ export class ShContextMenuComponent implements OnDestroy { } ngOnDestroy(): void { - // this.menuItems.forEach(i => i.open = false); + this.cmpContainer.detach(); + this.overlayRef.detach(); } } diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index 56bac21..17c2876 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -30,7 +30,7 @@ export class ShContextMenuService implements OnDestroy { const {overlayRef, componentRef} = this.createAndAttachOverlay(positionStrategy, scrollStrategy); - this.setupComponentBindings(componentRef, menu, data); + this.setupComponentBindings(componentRef, menu, overlayRef); componentRef.instance.show(data); this.openOverlays.push(overlayRef); @@ -38,8 +38,8 @@ export class ShContextMenuService implements OnDestroy { this.registerBackdropEvents(overlayRef); } - openSubMenu(ctxEvent: ContextSubMenuEvent) { - const {menu, mouseEvent, targetElement, data} = ctxEvent; + openSubMenu(ctxEvent: ContextSubMenuEvent): ShContextMenuComponent { + const {menu, mouseEvent, targetElement, data, hostMenu} = ctxEvent; mouseEvent.preventDefault(); mouseEvent.stopPropagation(); @@ -51,9 +51,13 @@ export class ShContextMenuService implements OnDestroy { this.setupComponentBindings(componentRef, menu, overlayRef); componentRef.instance.isSub = true; + componentRef.instance.thisContext = hostMenu.thisContext; + // componentRef.instance.items = menu.items; componentRef.instance.show(data); this.openOverlays.push(overlayRef); + + return componentRef.instance; } private registerBackdropEvents(overlayRef: OverlayRef) { @@ -66,8 +70,7 @@ export class ShContextMenuService implements OnDestroy { private setupComponentBindings(componentRef: ComponentRef, menu: ShContextMenuComponent, overlayRef: OverlayRef) { - componentRef.instance.viewChildrenItems = menu.viewChildrenItems; - componentRef.instance.contentChildrenItems = menu.contentChildrenItems; + componentRef.instance.items = menu.menuItems.toArray(); componentRef.instance.thisContext = menu.thisContext; componentRef.instance.overlayRef = overlayRef; } @@ -83,7 +86,6 @@ export class ShContextMenuService implements OnDestroy { }); const menuPortal = new ComponentPortal(ShContextMenuComponent); - const componentRef: ComponentRef = overlayRef.attach(menuPortal); return {overlayRef, componentRef}; @@ -166,4 +168,13 @@ export class ShContextMenuService implements OnDestroy { ngOnDestroy(): void { this.destroy(); } + + closeSubMenus(menu: ShContextMenuComponent) { + const overlayRefs = menu + .items + .filter(i => !!i.subMenu && !!i.subMenu.overlayRef) + .map(i => i.subMenu.overlayRef); + + overlayRefs.forEach(r => r.detach()); + } } From 6138ea3c7fa3fbad0ebc4eb894a2097a4d56a22b Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Wed, 11 Apr 2018 21:34:57 +0300 Subject: [PATCH 19/30] Templateportal --- src/app/app.component.html | 6 +-- src/app/app.component.ts | 6 ++- src/lib/src/sh-context-menu.component.ts | 23 ++------ src/lib/src/sh-context-menu.service.ts | 67 ++++++++++++------------ 4 files changed, 43 insertions(+), 59 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 62d7464..00e9e6c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -2,9 +2,9 @@

Menu One

-
-

Menu Two

-
+ + +
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0458db5..3f5916a 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -25,8 +25,8 @@ export class AppComponent { ]; } - onClick(item) { - console.log('clicked', this); + onClick(data) { + console.log('clicked', this, data); } } @@ -49,6 +49,8 @@ export class AppComponent { ` }) +// TODO: this is not possible now (because the use of TemplatePortal instead of ComponentPortal) +// should later define an interface for using a custom component as context menu export class MyMenuComponent extends ShContextMenuComponent { } diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 9c58440..9afc964 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -17,7 +17,7 @@ import {OverlayRef} from '@angular/cdk/overlay';
` }) -export class ShContextMenuComponent implements OnDestroy, AfterViewInit, AfterContentInit { +export class ShContextMenuComponent implements OnDestroy { @Input() thisContext: any; @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems; @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; @@ -44,21 +44,12 @@ export class ShContextMenuComponent implements OnDestroy, AfterViewInit, AfterCo overlayRef: OverlayRef; isSub = false; - items: ShContextMenuItemDirective[]; constructor(private ctxService: ShContextMenuService) { this.contentChildrenItems = new QueryList(); this.viewChildrenItems = new QueryList(); } - ngAfterViewInit(): void { - console.log('view init'); - } - - ngAfterContentInit(): void { - console.log('content init'); - } - get menuItems(): QueryList { // when using the ShContextMenuComponent as menu, the ContentChildren is the source if (this.contentChildrenItems.length) { @@ -69,14 +60,6 @@ export class ShContextMenuComponent implements OnDestroy, AfterViewInit, AfterCo return this.viewChildrenItems; } - show(data: any) { - this.menuItems.forEach((item) => { - item.context.$implicit = data; - }); - - this.cmpContainer.createEmbeddedView(this.cmpTemplate); - } - onEnter($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { // TODO: close all child submenus this.ctxService.closeSubMenus(this); @@ -99,7 +82,7 @@ export class ShContextMenuComponent implements OnDestroy, AfterViewInit, AfterCo } onClick($event: MouseEvent, item: ShContextMenuItemDirective) { - // TODO: handle click in service + // TODO: move click handling to service if (item.divider) { return; diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index 17c2876..7c50680 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -1,7 +1,7 @@ -import {ComponentRef, ElementRef, Injectable, OnDestroy} from '@angular/core'; +import {ElementRef, Injectable, OnDestroy} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; import {CloseScrollStrategy, Overlay} from '@angular/cdk/overlay'; -import {ComponentPortal} from '@angular/cdk/portal'; +import {TemplatePortal} from '@angular/cdk/portal'; import {ConnectedPositionStrategy} from '@angular/cdk/overlay/typings/position/connected-position-strategy'; import {ContextMenuEvent, ContextSubMenuEvent} from './sh-context-menu.models'; import {OverlayRef} from '@angular/cdk/overlay'; @@ -10,7 +10,7 @@ import {Subscription} from 'rxjs/Subscription'; @Injectable() export class ShContextMenuService implements OnDestroy { - openOverlays: OverlayRef[] = []; + activeOverlays: OverlayRef[] = []; backDropSub: Subscription; constructor(private overlay: Overlay) { @@ -28,17 +28,15 @@ export class ShContextMenuService implements OnDestroy { const scrollStrategy = this.buildCloseScrollStrategy(); const positionStrategy = this.buildConnectedPositionStrategy(targetElement); - const {overlayRef, componentRef} = this.createAndAttachOverlay(positionStrategy, scrollStrategy); + this.attachContextToItems(menu, data); - this.setupComponentBindings(componentRef, menu, overlayRef); - componentRef.instance.show(data); - - this.openOverlays.push(overlayRef); + const overlayRef = this.createAndAttachOverlay(positionStrategy, scrollStrategy, menu, true); + this.activeOverlays.push(overlayRef); this.registerBackdropEvents(overlayRef); } - openSubMenu(ctxEvent: ContextSubMenuEvent): ShContextMenuComponent { + openSubMenu(ctxEvent: ContextSubMenuEvent): any { const {menu, mouseEvent, targetElement, data, hostMenu} = ctxEvent; mouseEvent.preventDefault(); @@ -46,18 +44,18 @@ export class ShContextMenuService implements OnDestroy { const scrollStrategy = this.buildCloseScrollStrategy(); const positionStrategy = this.buildConnectedPositionStrategyForSubMenu(targetElement); - const {overlayRef, componentRef} = this.createAndAttachOverlay(positionStrategy, scrollStrategy, false); - - this.setupComponentBindings(componentRef, menu, overlayRef); - - componentRef.instance.isSub = true; - componentRef.instance.thisContext = hostMenu.thisContext; + // const {overlayRef, componentRef} = this.createAndAttachOverlay(positionStrategy, scrollStrategy, false); + // + // this.setupComponentBindings(componentRef, menu, overlayRef); + // + // componentRef.instance.isSub = true; + // componentRef.instance.thisContext = hostMenu.thisContext; // componentRef.instance.items = menu.items; - componentRef.instance.show(data); - - this.openOverlays.push(overlayRef); - - return componentRef.instance; + // componentRef.instance.show(data); + // + // this.activeOverlays.push(overlayRef); + // + // return componentRef.instance; } private registerBackdropEvents(overlayRef: OverlayRef) { @@ -67,16 +65,9 @@ export class ShContextMenuService implements OnDestroy { .subscribe(this.closeCurrentOverlays.bind(this)); } - private setupComponentBindings(componentRef: ComponentRef, - menu: ShContextMenuComponent, - overlayRef: OverlayRef) { - componentRef.instance.items = menu.menuItems.toArray(); - componentRef.instance.thisContext = menu.thisContext; - componentRef.instance.overlayRef = overlayRef; - } - private createAndAttachOverlay(positionStrategy: ConnectedPositionStrategy, scrollStrategy: CloseScrollStrategy, + menu: ShContextMenuComponent, hasBackdrop: boolean = true) { const overlayRef = this.overlay.create({ positionStrategy, @@ -85,10 +76,14 @@ export class ShContextMenuService implements OnDestroy { backdropClass: 'sh-backdrop' }); - const menuPortal = new ComponentPortal(ShContextMenuComponent); - const componentRef: ComponentRef = overlayRef.attach(menuPortal); + /* + TODO: try passing the TemplatePortal context (data) + and then injecting it to the *ngTemplateOutlet in the component template + */ + const menuPortal = new TemplatePortal(menu.cmpTemplate, menu.cmpContainer); + overlayRef.attach(menuPortal); - return {overlayRef, componentRef}; + return overlayRef; } private buildCloseScrollStrategy() { @@ -152,12 +147,12 @@ export class ShContextMenuService implements OnDestroy { } private closeCurrentOverlays() { - this.openOverlays.forEach((o) => { + this.activeOverlays.forEach((o) => { o.detach(); o.dispose(); }); - this.openOverlays = []; + this.activeOverlays = []; } destroy() { @@ -171,10 +166,14 @@ export class ShContextMenuService implements OnDestroy { closeSubMenus(menu: ShContextMenuComponent) { const overlayRefs = menu - .items + .menuItems .filter(i => !!i.subMenu && !!i.subMenu.overlayRef) .map(i => i.subMenu.overlayRef); overlayRefs.forEach(r => r.detach()); } + + private attachContextToItems(menu: ShContextMenuComponent, data: any) { + menu.menuItems.forEach(i => i.context.$implicit = data); + } } From e40f292830c806e48c694a0b8a6aa3b41afbe55c Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Wed, 11 Apr 2018 21:39:06 +0300 Subject: [PATCH 20/30] this input rename --- src/app/app.component.html | 2 +- src/lib/src/sh-context-menu.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 00e9e6c..367ec55 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -6,7 +6,7 @@

Menu One

- +
one - {{item.label}}
diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 9afc964..f67add1 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -35,7 +35,7 @@ import {OverlayRef} from '@angular/cdk/overlay'; ` }) export class ShContextMenuComponent implements OnDestroy { - @Input() thisContext: any; + @Input('this') thisContext: any; @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems; @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; From 85d32e77645aa55358a561a879003e20fb7a5158 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Wed, 11 Apr 2018 23:07:00 +0300 Subject: [PATCH 21/30] sub menus --- src/app/app.component.html | 33 ++++++++------ src/lib/src/sh-context-menu.component.ts | 12 ++--- src/lib/src/sh-context-menu.models.ts | 2 +- src/lib/src/sh-context-menu.service.ts | 58 +++++++++++++++--------- 4 files changed, 62 insertions(+), 43 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 367ec55..caa8b5a 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -3,16 +3,16 @@

Menu One

- +
one - {{item.label}}
- - - +
+ two - {{item.label}} +
@@ -21,17 +21,22 @@

Menu One

+ +
+ +
+
- - - - - - - - - - + +
+ From Sub Menu - {{item.label}} +
+ +
+ From Sub Menu Two - {{item.label}} +
+
+
diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index f67add1..fa6891d 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -1,7 +1,8 @@ import { AfterContentInit, AfterViewInit, - Component, ContentChildren, ElementRef, Input, OnDestroy, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, + Component, ContentChildren, ElementRef, Host, Input, OnDestroy, Optional, QueryList, TemplateRef, ViewChild, ViewChildren, + ViewContainerRef, ViewEncapsulation } from '@angular/core'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; @@ -24,10 +25,7 @@ import {OverlayRef} from '@angular/cdk/overlay'; (mouseenter)="onEnter($event, item, itemElement)" (click)="onClick($event, item)"> - - +
@@ -69,11 +67,11 @@ export class ShContextMenuComponent implements OnDestroy { } this.ctxService.openSubMenu({ - hostMenu: this, data: item.context.$implicit, targetElement: new ElementRef(elm), menu: item.subMenu, - mouseEvent: $event + mouseEvent: $event, + parentMenu: this }); } diff --git a/src/lib/src/sh-context-menu.models.ts b/src/lib/src/sh-context-menu.models.ts index 99559e8..f6862f0 100644 --- a/src/lib/src/sh-context-menu.models.ts +++ b/src/lib/src/sh-context-menu.models.ts @@ -9,5 +9,5 @@ export class ContextMenuEvent { } export class ContextSubMenuEvent extends ContextMenuEvent { - hostMenu: ShContextMenuComponent; + parentMenu: ShContextMenuComponent; } diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index 7c50680..b2fb3ac 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -32,30 +32,26 @@ export class ShContextMenuService implements OnDestroy { const overlayRef = this.createAndAttachOverlay(positionStrategy, scrollStrategy, menu, true); this.activeOverlays.push(overlayRef); + this.attachOverlayRef(menu, overlayRef); this.registerBackdropEvents(overlayRef); } openSubMenu(ctxEvent: ContextSubMenuEvent): any { - const {menu, mouseEvent, targetElement, data, hostMenu} = ctxEvent; + const {menu, mouseEvent, targetElement, data, parentMenu} = ctxEvent; mouseEvent.preventDefault(); mouseEvent.stopPropagation(); const scrollStrategy = this.buildCloseScrollStrategy(); const positionStrategy = this.buildConnectedPositionStrategyForSubMenu(targetElement); - // const {overlayRef, componentRef} = this.createAndAttachOverlay(positionStrategy, scrollStrategy, false); - // - // this.setupComponentBindings(componentRef, menu, overlayRef); - // - // componentRef.instance.isSub = true; - // componentRef.instance.thisContext = hostMenu.thisContext; - // componentRef.instance.items = menu.items; - // componentRef.instance.show(data); - // - // this.activeOverlays.push(overlayRef); - // - // return componentRef.instance; + const overlayRef = this.createAndAttachOverlay(positionStrategy, scrollStrategy, menu, false); + + this.attachContextToItems(menu, data); + this.attachThisContext(menu, parentMenu); + this.attachOverlayRef(menu, overlayRef); + + this.activeOverlays.push(overlayRef); } private registerBackdropEvents(overlayRef: OverlayRef) { @@ -119,11 +115,17 @@ export class ShContextMenuService implements OnDestroy { .overlay .position() .connectedTo(elm, - {originX: 'end', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}) + { originX: 'end', originY: 'top' }, + { overlayX: 'start', overlayY: 'top' }) .withFallbackPosition( - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'bottom'}); + { originX: 'start', originY: 'top' }, + { overlayX: 'end', overlayY: 'top' }) + .withFallbackPosition( + { originX: 'end', originY: 'bottom' }, + { overlayX: 'start', overlayY: 'bottom' }) + .withFallbackPosition( + { originX: 'start', originY: 'bottom' }, + { overlayX: 'end', overlayY: 'bottom' }); } /* @@ -165,15 +167,29 @@ export class ShContextMenuService implements OnDestroy { } closeSubMenus(menu: ShContextMenuComponent) { - const overlayRefs = menu + const itemsWithSubMenus = menu .menuItems - .filter(i => !!i.subMenu && !!i.subMenu.overlayRef) - .map(i => i.subMenu.overlayRef); + .filter(i => !!i.subMenu && !!i.subMenu.overlayRef); + + if (itemsWithSubMenus.length) { + itemsWithSubMenus.forEach(sm => this.closeSubMenus(sm.subMenu)); - overlayRefs.forEach(r => r.detach()); + const overlayRefs = itemsWithSubMenus + .map(i => i.subMenu.overlayRef); + + overlayRefs.forEach(r => r.dispose()); + } } private attachContextToItems(menu: ShContextMenuComponent, data: any) { menu.menuItems.forEach(i => i.context.$implicit = data); } + + private attachThisContext(menu: ShContextMenuComponent, parentMenu: ShContextMenuComponent) { + menu.thisContext = parentMenu.thisContext; + } + + private attachOverlayRef(menu: ShContextMenuComponent, overlayRef: OverlayRef) { + menu.overlayRef = overlayRef; + } } From 13862ccc5ac5fcc5873e210fbf447f4e57f557f0 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Wed, 11 Apr 2018 23:42:17 +0300 Subject: [PATCH 22/30] while hovering sub menu the parent item remain in active state --- src/lib/src/sh-context-menu-item.directive.ts | 12 +++++++ src/lib/src/sh-context-menu.component.ts | 33 ++++++++++++++----- src/lib/src/sh-context-menu.css | 2 +- src/lib/src/sh-context-menu.service.ts | 27 ++++++++++----- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index 3b46d76..c06fc6e 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -18,7 +18,19 @@ export class ShContextMenuItemDirective { @Input() divider = false; context: MenuItemContext = new MenuItemContext(); + private active: boolean; constructor(@Optional() public template: TemplateRef) { } + + setNotActive() { + this.active = false; + if (this.subMenu) { + this.subMenu.setNotActive(); + } + } + + setActive() { + this.active = true; + } } diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index fa6891d..71e87b8 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -1,7 +1,5 @@ import { - AfterContentInit, - AfterViewInit, - Component, ContentChildren, ElementRef, Host, Input, OnDestroy, Optional, QueryList, TemplateRef, ViewChild, ViewChildren, + Component, ContentChildren, ElementRef, Input, OnDestroy, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ViewEncapsulation } from '@angular/core'; @@ -20,7 +18,9 @@ import {OverlayRef} from '@angular/cdk/overlay';
@@ -36,12 +36,12 @@ export class ShContextMenuComponent implements OnDestroy { @Input('this') thisContext: any; @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems; @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; - @ViewChild('componentTemplate', {read: TemplateRef}) cmpTemplate; - @ViewChild('componentContainer', {read: ViewContainerRef}) cmpContainer; + @ViewChild('componentContainer', {read: ViewContainerRef}) cmpContainer; overlayRef: OverlayRef; - isSub = false; + + private subActive: boolean; constructor(private ctxService: ShContextMenuService) { this.contentChildrenItems = new QueryList(); @@ -61,11 +61,13 @@ export class ShContextMenuComponent implements OnDestroy { onEnter($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { // TODO: close all child submenus this.ctxService.closeSubMenus(this); + this.setNotActive(); if (!item.subMenu) { return; } + this.setActive(item); this.ctxService.openSubMenu({ data: item.context.$implicit, targetElement: new ElementRef(elm), @@ -75,6 +77,11 @@ export class ShContextMenuComponent implements OnDestroy { }); } + private setActive(item: ShContextMenuItemDirective) { + item.setActive(); + this.subActive = true; + } + onLeave($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { } @@ -96,8 +103,18 @@ export class ShContextMenuComponent implements OnDestroy { fn.call(this.thisContext ? this.thisContext : fallbackContext, ...args); } - ngOnDestroy(): void { + close(): void { + this.setNotActive(); this.cmpContainer.detach(); this.overlayRef.detach(); } + + ngOnDestroy(): void { + this.close(); + } + + setNotActive() { + this.subActive = false; + this.menuItems.forEach(i => i.setNotActive()); + } } diff --git a/src/lib/src/sh-context-menu.css b/src/lib/src/sh-context-menu.css index d44ca3b..c9309d9 100644 --- a/src/lib/src/sh-context-menu.css +++ b/src/lib/src/sh-context-menu.css @@ -20,7 +20,7 @@ transition: all 0.15s; } -.sh-context-menu--item:hover { +.sh-context-menu--item:hover, .sh-context-menu--item__sub-active { background-color: #4b8bec; color: white; cursor: pointer; diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index b2fb3ac..addfa7b 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -7,12 +7,15 @@ import {ContextMenuEvent, ContextSubMenuEvent} from './sh-context-menu.models'; import {OverlayRef} from '@angular/cdk/overlay'; import {fromEvent} from 'rxjs/observable/fromEvent'; import {Subscription} from 'rxjs/Subscription'; +import {tokenReference} from '@angular/compiler'; @Injectable() export class ShContextMenuService implements OnDestroy { activeOverlays: OverlayRef[] = []; backDropSub: Subscription; + activeMenu: ShContextMenuComponent; + constructor(private overlay: Overlay) { } @@ -20,6 +23,8 @@ export class ShContextMenuService implements OnDestroy { this.closeCurrentOverlays(); const {menu, mouseEvent, targetElement, data} = ctxEvent; + this.activeMenu = menu; + mouseEvent.preventDefault(); mouseEvent.stopPropagation(); @@ -115,17 +120,17 @@ export class ShContextMenuService implements OnDestroy { .overlay .position() .connectedTo(elm, - { originX: 'end', originY: 'top' }, - { overlayX: 'start', overlayY: 'top' }) + {originX: 'end', originY: 'top'}, + {overlayX: 'start', overlayY: 'top'}) .withFallbackPosition( - { originX: 'start', originY: 'top' }, - { overlayX: 'end', overlayY: 'top' }) + {originX: 'start', originY: 'top'}, + {overlayX: 'end', overlayY: 'top'}) .withFallbackPosition( - { originX: 'end', originY: 'bottom' }, - { overlayX: 'start', overlayY: 'bottom' }) + {originX: 'end', originY: 'bottom'}, + {overlayX: 'start', overlayY: 'bottom'}) .withFallbackPosition( - { originX: 'start', originY: 'bottom' }, - { overlayX: 'end', overlayY: 'bottom' }); + {originX: 'start', originY: 'bottom'}, + {overlayX: 'end', overlayY: 'bottom'}); } /* @@ -155,6 +160,12 @@ export class ShContextMenuService implements OnDestroy { }); this.activeOverlays = []; + + // TODO: create close subject and emit. + // subscribe in component + if (this.activeMenu) { + this.activeMenu.close(); + } } destroy() { From dd1149a051469f126694644faadd38fad4daa066 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Thu, 12 Apr 2018 16:56:07 +0300 Subject: [PATCH 23/30] clean --- src/lib/src/sh-context-menu.component.ts | 25 +++++++++++------------- src/lib/src/sh-context-menu.service.ts | 7 +++---- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 71e87b8..0608528 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -16,16 +16,16 @@ import {OverlayRef} from '@angular/cdk/overlay';
- - + (mouseenter)="onEnter($event, menuItem, itemElement)" + (click)="onClick($event, menuItem)"> + +
@@ -34,13 +34,14 @@ import {OverlayRef} from '@angular/cdk/overlay'; }) export class ShContextMenuComponent implements OnDestroy { @Input('this') thisContext: any; + @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems; @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; - @ViewChild('componentTemplate', {read: TemplateRef}) cmpTemplate; + @ViewChild('componentTemplate', {read: TemplateRef}) cmpTemplate; @ViewChild('componentContainer', {read: ViewContainerRef}) cmpContainer; - overlayRef: OverlayRef; + public overlayRef: OverlayRef; private subActive: boolean; constructor(private ctxService: ShContextMenuService) { @@ -82,10 +83,6 @@ export class ShContextMenuComponent implements OnDestroy { this.subActive = true; } - onLeave($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { - - } - onClick($event: MouseEvent, item: ShContextMenuItemDirective) { // TODO: move click handling to service diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index addfa7b..f88379b 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -7,7 +7,6 @@ import {ContextMenuEvent, ContextSubMenuEvent} from './sh-context-menu.models'; import {OverlayRef} from '@angular/cdk/overlay'; import {fromEvent} from 'rxjs/observable/fromEvent'; import {Subscription} from 'rxjs/Subscription'; -import {tokenReference} from '@angular/compiler'; @Injectable() export class ShContextMenuService implements OnDestroy { @@ -36,7 +35,6 @@ export class ShContextMenuService implements OnDestroy { this.attachContextToItems(menu, data); const overlayRef = this.createAndAttachOverlay(positionStrategy, scrollStrategy, menu, true); - this.activeOverlays.push(overlayRef); this.attachOverlayRef(menu, overlayRef); this.registerBackdropEvents(overlayRef); @@ -55,8 +53,6 @@ export class ShContextMenuService implements OnDestroy { this.attachContextToItems(menu, data); this.attachThisContext(menu, parentMenu); this.attachOverlayRef(menu, overlayRef); - - this.activeOverlays.push(overlayRef); } private registerBackdropEvents(overlayRef: OverlayRef) { @@ -70,6 +66,7 @@ export class ShContextMenuService implements OnDestroy { scrollStrategy: CloseScrollStrategy, menu: ShContextMenuComponent, hasBackdrop: boolean = true) { + const overlayRef = this.overlay.create({ positionStrategy, scrollStrategy, @@ -84,6 +81,8 @@ export class ShContextMenuService implements OnDestroy { const menuPortal = new TemplatePortal(menu.cmpTemplate, menu.cmpContainer); overlayRef.attach(menuPortal); + this.activeOverlays.push(overlayRef); + return overlayRef; } From d91f6bdd7c9ea6fef009c779f00b7b7f6b3e6610 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Sat, 14 Apr 2018 17:26:05 +0300 Subject: [PATCH 24/30] context event --- src/app/app.component.html | 17 ++++++++++++++--- src/app/app.component.ts | 1 + src/lib/src/sh-context-menu-item.directive.ts | 6 ++++-- src/lib/src/sh-context-menu.component.ts | 6 +++--- src/lib/src/sh-context-menu.models.ts | 5 +++++ 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index caa8b5a..77d789e 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -21,13 +21,21 @@

Menu One

- -
- + +
+

Hi !

+
+ From Sub Menu - {{item.label}} +
+
+
+
+ one - {{item.label}} +
From Sub Menu - {{item.label}}
@@ -39,4 +47,7 @@

Menu One

+ +{{itemVisible}} + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 3f5916a..649963e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -13,6 +13,7 @@ export class AppComponent { items: any[]; thisContext = this; + itemVisible = false; constructor() { this.items = [ diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index c06fc6e..5d8d7ea 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -1,5 +1,6 @@ -import {Directive, Input, Optional, TemplateRef} from '@angular/core'; +import {Directive, Input, OnInit, Optional, TemplateRef} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; +import {ContextMenuClickEvent} from './sh-context-menu.models'; export class MenuItemContext { $implicit: any; @@ -14,10 +15,11 @@ export class MenuItemContext { }) export class ShContextMenuItemDirective { @Input('shContextMenuItemWith') subMenu: ShContextMenuComponent; - @Input('shContextMenuItemOn') on: (data) => {}; + @Input('shContextMenuItemOn') on: (data: ContextMenuClickEvent) => {}; @Input() divider = false; context: MenuItemContext = new MenuItemContext(); + private active: boolean; constructor(@Optional() public template: TemplateRef) { diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 0608528..9f28500 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -92,12 +92,12 @@ export class ShContextMenuComponent implements OnDestroy { if (item.on) { this.ctxService.destroy(); - this.callWithContext(item.on, item, item.context.$implicit); + this.callWithContext(item.on, item, item.context.$implicit, $event); } } - private callWithContext(fn, fallbackContext, ...args) { - fn.call(this.thisContext ? this.thisContext : fallbackContext, ...args); + private callWithContext(fn, fallbackContext, data, event) { + fn.call(this.thisContext ? this.thisContext : fallbackContext, {data, event}); } close(): void { diff --git a/src/lib/src/sh-context-menu.models.ts b/src/lib/src/sh-context-menu.models.ts index f6862f0..ee79498 100644 --- a/src/lib/src/sh-context-menu.models.ts +++ b/src/lib/src/sh-context-menu.models.ts @@ -11,3 +11,8 @@ export class ContextMenuEvent { export class ContextSubMenuEvent extends ContextMenuEvent { parentMenu: ShContextMenuComponent; } + +export class ContextMenuClickEvent { + data: any; + event: MouseEvent; +} From 451879aac5f842d82df6826732d89565c6ce93f5 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Sun, 15 Apr 2018 22:59:49 +0300 Subject: [PATCH 25/30] refactoring away from structural directive --- src/app/app.component.html | 60 +++++++++++-------- src/app/app.component.ts | 5 ++ src/lib/src/sh-context-menu-item.directive.ts | 16 ++--- src/lib/src/sh-context-menu.component.ts | 33 ++++++---- src/lib/src/sh-context-menu.models.ts | 8 +-- src/lib/src/sh-context-menu.service.ts | 8 +-- src/polyfills.ts | 2 +- 7 files changed, 79 insertions(+), 53 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 77d789e..3955a01 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -7,42 +7,50 @@

Menu One

-
- one - {{item.label}} -
-
- two - {{item.label}} -
+ +
+ two - {{item.label}} +
+
-
- one - {{item.label}} -
-
+ +
+ one - {{item.label}} +
+
+ -
+ -
+

Hi !

-
+
-
- From Sub Menu - {{item.label}} -
-
-
-
- one - {{item.label}} -
-
- From Sub Menu - {{item.label}} -
+ +
+ From Sub Menu - {{item.label}} +
+
+ + + +
+ one - {{item.label}} +
+
+ +
+ From Sub Menu - {{item.label}} +
+
+ -
+ From Sub Menu Two - {{item.label}} -
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 649963e..1257ea0 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -29,6 +29,11 @@ export class AppComponent { onClick(data) { console.log('clicked', this, data); } + + isVisible(data) { + console.log('isVisible', this, data); + return true; + } } @Component({ diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index 5d8d7ea..1538fdb 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -1,6 +1,6 @@ -import {Directive, Input, OnInit, Optional, TemplateRef} from '@angular/core'; +import {Directive, EventEmitter, Input, Optional, Output, TemplateRef} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; -import {ContextMenuClickEvent} from './sh-context-menu.models'; +import {ShContextMenuClickEvent} from './sh-context-menu.models'; export class MenuItemContext { $implicit: any; @@ -14,25 +14,27 @@ export class MenuItemContext { selector: '[shContextMenuItem]' }) export class ShContextMenuItemDirective { - @Input('shContextMenuItemWith') subMenu: ShContextMenuComponent; - @Input('shContextMenuItemOn') on: (data: ContextMenuClickEvent) => {}; + @Input() subMenu: ShContextMenuComponent; @Input() divider = false; + @Input() visible: (event: ShContextMenuClickEvent) => boolean; + + @Output() click = new EventEmitter(); context: MenuItemContext = new MenuItemContext(); - private active: boolean; + private _active: boolean; constructor(@Optional() public template: TemplateRef) { } setNotActive() { - this.active = false; + this._active = false; if (this.subMenu) { this.subMenu.setNotActive(); } } setActive() { - this.active = true; + this._active = true; } } diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 9f28500..3d3a0b5 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -12,8 +12,8 @@ import {OverlayRef} from '@angular/cdk/overlay'; encapsulation: ViewEncapsulation.None, styleUrls: ['sh-context-menu.css'], template: ` - - + +
@@ -38,8 +39,8 @@ export class ShContextMenuComponent implements OnDestroy { @ContentChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) contentChildrenItems; @ViewChildren(ShContextMenuItemDirective, {read: ShContextMenuItemDirective}) viewChildrenItems; - @ViewChild('componentTemplate', {read: TemplateRef}) cmpTemplate; - @ViewChild('componentContainer', {read: ViewContainerRef}) cmpContainer; + @ViewChild('menuTemplate', {read: TemplateRef}) menuTemplate; + @ViewChild('menuContainer', {read: ViewContainerRef}) menuContainer; public overlayRef: OverlayRef; private subActive: boolean; @@ -83,26 +84,28 @@ export class ShContextMenuComponent implements OnDestroy { this.subActive = true; } - onClick($event: MouseEvent, item: ShContextMenuItemDirective) { + onClick(event: MouseEvent, item: ShContextMenuItemDirective) { // TODO: move click handling to service if (item.divider) { return; } - if (item.on) { - this.ctxService.destroy(); - this.callWithContext(item.on, item, item.context.$implicit, $event); - } + this.ctxService.destroy(); + // this.callWithContext(item.click, item, item.context.$implicit, $event); + item.click.emit({ + data: item.context.$implicit, + event + }); } private callWithContext(fn, fallbackContext, data, event) { - fn.call(this.thisContext ? this.thisContext : fallbackContext, {data, event}); + return fn.call(this.thisContext ? this.thisContext : fallbackContext, {data, event}); } close(): void { this.setNotActive(); - this.cmpContainer.detach(); + this.menuContainer.detach(); this.overlayRef.detach(); } @@ -114,4 +117,12 @@ export class ShContextMenuComponent implements OnDestroy { this.subActive = false; this.menuItems.forEach(i => i.setNotActive()); } + + isVisible(item: ShContextMenuItemDirective) { + if (!item.visible) { + return true; + } + + return this.callWithContext(item.visible, this, item.context.$implicit, null); + } } diff --git a/src/lib/src/sh-context-menu.models.ts b/src/lib/src/sh-context-menu.models.ts index ee79498..cea5449 100644 --- a/src/lib/src/sh-context-menu.models.ts +++ b/src/lib/src/sh-context-menu.models.ts @@ -1,18 +1,18 @@ import {ShContextMenuComponent} from './sh-context-menu.component'; import {ElementRef} from '@angular/core'; -export class ContextMenuEvent { +export class ShContextMenuEvent { menu: ShContextMenuComponent; mouseEvent: MouseEvent; targetElement: ElementRef; data: any; } -export class ContextSubMenuEvent extends ContextMenuEvent { +export class ShContextSubMenuEvent extends ShContextMenuEvent { parentMenu: ShContextMenuComponent; } -export class ContextMenuClickEvent { +export class ShContextMenuClickEvent { data: any; - event: MouseEvent; + event?: MouseEvent; } diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index f88379b..f3e5d29 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -3,7 +3,7 @@ import {ShContextMenuComponent} from './sh-context-menu.component'; import {CloseScrollStrategy, Overlay} from '@angular/cdk/overlay'; import {TemplatePortal} from '@angular/cdk/portal'; import {ConnectedPositionStrategy} from '@angular/cdk/overlay/typings/position/connected-position-strategy'; -import {ContextMenuEvent, ContextSubMenuEvent} from './sh-context-menu.models'; +import {ShContextMenuEvent, ShContextSubMenuEvent} from './sh-context-menu.models'; import {OverlayRef} from '@angular/cdk/overlay'; import {fromEvent} from 'rxjs/observable/fromEvent'; import {Subscription} from 'rxjs/Subscription'; @@ -18,7 +18,7 @@ export class ShContextMenuService implements OnDestroy { constructor(private overlay: Overlay) { } - openMenu(ctxEvent: ContextMenuEvent) { + openMenu(ctxEvent: ShContextMenuEvent) { this.closeCurrentOverlays(); const {menu, mouseEvent, targetElement, data} = ctxEvent; @@ -40,7 +40,7 @@ export class ShContextMenuService implements OnDestroy { this.registerBackdropEvents(overlayRef); } - openSubMenu(ctxEvent: ContextSubMenuEvent): any { + openSubMenu(ctxEvent: ShContextSubMenuEvent): any { const {menu, mouseEvent, targetElement, data, parentMenu} = ctxEvent; mouseEvent.preventDefault(); @@ -78,7 +78,7 @@ export class ShContextMenuService implements OnDestroy { TODO: try passing the TemplatePortal context (data) and then injecting it to the *ngTemplateOutlet in the component template */ - const menuPortal = new TemplatePortal(menu.cmpTemplate, menu.cmpContainer); + const menuPortal = new TemplatePortal(menu.menuTemplate, menu.menuContainer); overlayRef.attach(menuPortal); this.activeOverlays.push(overlayRef); diff --git a/src/polyfills.ts b/src/polyfills.ts index d68672f..6545292 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -34,7 +34,7 @@ // import 'core-js/es6/weak-map'; // import 'core-js/es6/set'; -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +/** IE10 and IE11 requires the following for NgClass support click SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. /** IE10 and IE11 requires the following for the Reflect API. */ From 782b0022879f5b5f5f02754e44a850491525398a Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Mon, 23 Apr 2018 16:44:12 +0300 Subject: [PATCH 26/30] closeOnClick, visible --- src/app/app.component.html | 2 +- src/app/app.component.ts | 7 ++- src/lib/src/sh-context-menu-item.directive.ts | 5 +- src/lib/src/sh-context-menu.component.ts | 17 +++---- src/lib/src/sh-context-menu.service.ts | 48 +++++++++---------- 5 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 3955a01..ef670bb 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -19,7 +19,7 @@

Menu One

one - {{item.label}}
- + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1257ea0..1f379a4 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -26,12 +26,11 @@ export class AppComponent { ]; } - onClick(data) { - console.log('clicked', this, data); + onClick(event) { + console.log('clicked', this, event); } - isVisible(data) { - console.log('isVisible', this, data); + isVisible(event) { return true; } } diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index 1538fdb..5b44754 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -1,4 +1,4 @@ -import {Directive, EventEmitter, Input, Optional, Output, TemplateRef} from '@angular/core'; +import {Directive, EventEmitter, HostBinding, Input, Optional, Output, TemplateRef} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; import {ShContextMenuClickEvent} from './sh-context-menu.models'; @@ -15,8 +15,9 @@ export class MenuItemContext { }) export class ShContextMenuItemDirective { @Input() subMenu: ShContextMenuComponent; - @Input() divider = false; + @Input() divider: boolean; @Input() visible: (event: ShContextMenuClickEvent) => boolean; + @Input() closeOnClick = true; @Output() click = new EventEmitter(); diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index 3d3a0b5..afe87e7 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -22,10 +22,9 @@ import {OverlayRef} from '@angular/cdk/overlay'; 'sh-context-menu--item__divider': menuItem.divider, 'sh-context-menu--item__sub-active': subActive && menuItem.active}" class="sh-context-menu--item" - [style.display]="isVisible(menuItem) ? 'block' : 'none'" (mouseenter)="onEnter($event, menuItem, itemElement)" (click)="onClick($event, menuItem)"> - +
@@ -91,12 +90,14 @@ export class ShContextMenuComponent implements OnDestroy { return; } - this.ctxService.destroy(); - // this.callWithContext(item.click, item, item.context.$implicit, $event); - item.click.emit({ - data: item.context.$implicit, - event - }); + if (!item.subMenu && item.closeOnClick) { + this.ctxService.destroy(); + // this.callWithContext(item.click, item, item.context.$implicit, $event); + item.click.emit({ + data: item.context.$implicit, + event + }); + } } private callWithContext(fn, fallbackContext, data, event) { diff --git a/src/lib/src/sh-context-menu.service.ts b/src/lib/src/sh-context-menu.service.ts index f3e5d29..c20a8fb 100644 --- a/src/lib/src/sh-context-menu.service.ts +++ b/src/lib/src/sh-context-menu.service.ts @@ -55,6 +55,30 @@ export class ShContextMenuService implements OnDestroy { this.attachOverlayRef(menu, overlayRef); } + destroy() { + this.closeCurrentOverlays(); + this.backDropSub.unsubscribe(); + } + + ngOnDestroy(): void { + this.destroy(); + } + + closeSubMenus(menu: ShContextMenuComponent) { + const itemsWithSubMenus = menu + .menuItems + .filter(i => !!i.subMenu && !!i.subMenu.overlayRef); + + if (itemsWithSubMenus.length) { + itemsWithSubMenus.forEach(sm => this.closeSubMenus(sm.subMenu)); + + const overlayRefs = itemsWithSubMenus + .map(i => i.subMenu.overlayRef); + + overlayRefs.forEach(r => r.dispose()); + } + } + private registerBackdropEvents(overlayRef: OverlayRef) { const elm = overlayRef.backdropElement; @@ -167,30 +191,6 @@ export class ShContextMenuService implements OnDestroy { } } - destroy() { - this.closeCurrentOverlays(); - this.backDropSub.unsubscribe(); - } - - ngOnDestroy(): void { - this.destroy(); - } - - closeSubMenus(menu: ShContextMenuComponent) { - const itemsWithSubMenus = menu - .menuItems - .filter(i => !!i.subMenu && !!i.subMenu.overlayRef); - - if (itemsWithSubMenus.length) { - itemsWithSubMenus.forEach(sm => this.closeSubMenus(sm.subMenu)); - - const overlayRefs = itemsWithSubMenus - .map(i => i.subMenu.overlayRef); - - overlayRefs.forEach(r => r.dispose()); - } - } - private attachContextToItems(menu: ShContextMenuComponent, data: any) { menu.menuItems.forEach(i => i.context.$implicit = data); } From 5fa566b97bbfee4dfadc870a0748a0650d873213 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Mon, 23 Apr 2018 22:42:48 +0300 Subject: [PATCH 27/30] clean --- src/lib/src/sh-context-menu-item.directive.ts | 3 ++- src/lib/src/sh-context-menu.component.ts | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/src/sh-context-menu-item.directive.ts b/src/lib/src/sh-context-menu-item.directive.ts index 5b44754..ff4a413 100644 --- a/src/lib/src/sh-context-menu-item.directive.ts +++ b/src/lib/src/sh-context-menu-item.directive.ts @@ -1,4 +1,4 @@ -import {Directive, EventEmitter, HostBinding, Input, Optional, Output, TemplateRef} from '@angular/core'; +import {Directive, EventEmitter, Input, Optional, Output, TemplateRef} from '@angular/core'; import {ShContextMenuComponent} from './sh-context-menu.component'; import {ShContextMenuClickEvent} from './sh-context-menu.models'; @@ -17,6 +17,7 @@ export class ShContextMenuItemDirective { @Input() subMenu: ShContextMenuComponent; @Input() divider: boolean; @Input() visible: (event: ShContextMenuClickEvent) => boolean; + @Input() disabled: (event: ShContextMenuClickEvent) => boolean; @Input() closeOnClick = true; @Output() click = new EventEmitter(); diff --git a/src/lib/src/sh-context-menu.component.ts b/src/lib/src/sh-context-menu.component.ts index afe87e7..c5e4704 100644 --- a/src/lib/src/sh-context-menu.component.ts +++ b/src/lib/src/sh-context-menu.component.ts @@ -60,7 +60,6 @@ export class ShContextMenuComponent implements OnDestroy { } onEnter($event: MouseEvent, item: ShContextMenuItemDirective, elm: HTMLElement) { - // TODO: close all child submenus this.ctxService.closeSubMenus(this); this.setNotActive(); @@ -92,7 +91,7 @@ export class ShContextMenuComponent implements OnDestroy { if (!item.subMenu && item.closeOnClick) { this.ctxService.destroy(); - // this.callWithContext(item.click, item, item.context.$implicit, $event); + item.click.emit({ data: item.context.$implicit, event From e989950950396ce7ab1d41022c9f9c496baa4b6e Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Tue, 24 Apr 2018 16:47:33 +0300 Subject: [PATCH 28/30] rename anchor directive, readme etc --- README.md | 59 +++++++++++++++++++++----- changelog.md | 4 ++ package.json | 4 +- src/app/app.component.html | 26 +++++++++++- src/app/app.component.ts | 9 ---- src/app/app.module.ts | 2 +- src/lib/package.json | 5 ++- src/lib/src/index.ts | 3 +- src/lib/src/sh-anchor-for.directive.ts | 6 +-- src/lib/src/sh-context-menu.css | 2 - src/lib/src/sh-context-menu.module.ts | 6 +-- src/styles.css | 3 ++ 12 files changed, 94 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 69bbad8..7c57f89 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,64 @@ -### WIP ! - # ng2-right-click-menu _Right click context menu for Angular 2+_ __DEMO__ https://msarsha.github.io/ng2-right-click-menu/ -## How to use +### Dependencies + +`@angular/cdk` + +`@angular/cdk/overlay-prebuilt.css"` + +### Setup + +`npm install --save ng2-right-click-menu @angular/cdk` -- `npm install --save ng2-right-click-menu` -- import `ShContextMenuModule` into your app module +import `ShContextMenuModule` -Add the `[sh-context]` directive to the desired element and bind an `IShContextMenuItem` array. +````typescript +import {ShContextMenuModule} from 'ng2-right-click-menu' + +@NgModule({ + //... + imports: [ShContextMenuModule] + //... +}) +```` -Use the `[sh-data-context]` property to inject a context object of type `any`. +import css file in your `styles.css`: + +````css + @import "~@angular/cdk/overlay-prebuilt.css"; +```` + +## Usage + +#### Defining a Basic Menu Template + +The menu template is built using the `sh-context-menu` component as the menu wrapper, +and nesting `ng-template` with the `shContextMenuItem` directive for every menu item: + +The `shContextMenuItem` directive provide a template variable (`let-data`) that gives you access to the data object attached to the menu. ````html -
- // content -
+ + +
+ Menu Item - {{data.label}} +
+
+
```` +#### Attaching The Menu To An Element + +Attaching works by using the `shMenu` directive and providing the `#menu` template variable: + +```html +
Right Click Me
+``` + + ### `[sh-context]` (`IShContextMenuItem[]`) ````typescript diff --git a/changelog.md b/changelog.md index 1d5e3d9..bcde96d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +### 1.0.0 - BREAKING CHANGES + +new api using ng-template [#fc09e66](https://github.com/msarsha/ng2-right-click-menu/commit/fc09e6687ce7ca376708386b8841d5336b3ac82a) + ### 0.0.16 z-index increased to bring menu to foreground [#fc09e66](https://github.com/msarsha/ng2-right-click-menu/commit/fc09e6687ce7ca376708386b8841d5336b3ac82a) diff --git a/package.json b/package.json index aec947c..72fa2ce 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "scripts": { "ng": "ng", "start": "ng serve", - "build": "ng-packagr -p lib/package.json && cp README.md ./lib-build", - "build:win": "ng-packagr -p lib/package.json && copy README.md lib-build", + "build": "ng-packagr -p src/lib/package.json && cp README.md ./lib-build", + "build:win": "ng-packagr -p src/lib/package.json && copy README.md lib-build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" diff --git a/src/app/app.component.html b/src/app/app.component.html index ef670bb..d3d3979 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,4 +1,4 @@ -
+

Menu One

@@ -59,3 +59,27 @@

Hi !

{{itemVisible}} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1f379a4..1a9953c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,5 @@ import {Component, Input, ViewEncapsulation} from '@angular/core'; import {ShContextMenuComponent} from '../lib/src/sh-context-menu.component'; -import {Observable} from 'rxjs/Observable'; @Component({ selector: 'app-root', @@ -44,14 +43,6 @@ export class AppComponent {
from comp !! - {{item.label}}
- - - - - - - - ` }) // TODO: this is not possible now (because the use of TemplatePortal instead of ComponentPortal) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 79cfb5f..459e9a3 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -2,7 +2,7 @@ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {AppComponent, MyContentComponent, MyMenuComponent} from './app.component'; -import {ShContextMenuModule} from 'ng2-right-click-menu'; +import {ShContextMenuModule} from '../lib/src/sh-context-menu.module'; @NgModule({ declarations: [ diff --git a/src/lib/package.json b/src/lib/package.json index cbbf306..3ec4e02 100644 --- a/src/lib/package.json +++ b/src/lib/package.json @@ -1,6 +1,6 @@ { "name": "ng2-right-click-menu", - "version": "0.0.16", + "version": "1.0.0", "author": "Matan Sar-Shalom ", "description": "Right click context menu for Angular 2+", "license": "MIT", @@ -13,7 +13,8 @@ "menu", "right", "click", - "menu" + "menu", + "contextmenu" ], "repository": { "type": "git", diff --git a/src/lib/src/index.ts b/src/lib/src/index.ts index 6cc9961..f96025c 100644 --- a/src/lib/src/index.ts +++ b/src/lib/src/index.ts @@ -1,6 +1,5 @@ export {ShContextMenuModule} from './sh-context-menu.module'; export {ShContextMenuComponent} from './sh-context-menu.component'; -export {ShAnchorForDirective} from './sh-anchor-for.directive'; +export {ShAttachMenuDirective} from './sh-anchor-for.directive'; export {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; export {ShContextMenuService} from './sh-context-menu.service'; - diff --git a/src/lib/src/sh-anchor-for.directive.ts b/src/lib/src/sh-anchor-for.directive.ts index 9fa4a51..b797abc 100644 --- a/src/lib/src/sh-anchor-for.directive.ts +++ b/src/lib/src/sh-anchor-for.directive.ts @@ -6,10 +6,10 @@ import {fromEvent} from 'rxjs/observable/fromEvent'; import {merge} from 'rxjs/observable/merge'; @Directive({ - selector: '[shAnchorFor]' + selector: '[shAttachMenu]' }) -export class ShAnchorForDirective implements OnDestroy, OnInit { - @Input('shAnchorFor') menu: ShContextMenuComponent; +export class ShAttachMenuDirective implements OnDestroy, OnInit { + @Input('shAttachMenu') menu: ShContextMenuComponent; @Input('shMenuTriggers') triggers: string[]; @Input('shMenuData') data: any; sub: Subscription; diff --git a/src/lib/src/sh-context-menu.css b/src/lib/src/sh-context-menu.css index c9309d9..055beef 100644 --- a/src/lib/src/sh-context-menu.css +++ b/src/lib/src/sh-context-menu.css @@ -1,5 +1,3 @@ -@import "~@angular/cdk/overlay-prebuilt.css"; - .sh-backdrop { background-color: transparent; } diff --git a/src/lib/src/sh-context-menu.module.ts b/src/lib/src/sh-context-menu.module.ts index e28079c..12ceb33 100755 --- a/src/lib/src/sh-context-menu.module.ts +++ b/src/lib/src/sh-context-menu.module.ts @@ -2,19 +2,19 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {ShContextMenuComponent} from './sh-context-menu.component'; -import {ShAnchorForDirective} from './sh-anchor-for.directive'; +import {ShAttachMenuDirective} from './sh-anchor-for.directive'; import {ShContextMenuService} from './sh-context-menu.service'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; import {OverlayModule} from '@angular/cdk/overlay'; @NgModule({ declarations: [ - ShAnchorForDirective, + ShAttachMenuDirective, ShContextMenuComponent, ShContextMenuItemDirective ], exports: [ - ShAnchorForDirective, + ShAttachMenuDirective, ShContextMenuComponent, ShContextMenuItemDirective ], diff --git a/src/styles.css b/src/styles.css index 90d4ee0..c12f1b3 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1 +1,4 @@ +@import "~@angular/cdk/overlay-prebuilt.css"; + + /* You can add global styles to this file, and also import other style files */ From c2b96e0227e500525df5295bd894ef2f34c40669 Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Tue, 24 Apr 2018 23:32:24 +0300 Subject: [PATCH 29/30] readme --- README.md | 195 ++++-------------- src/lib/src/index.ts | 2 +- src/lib/src/sh-anchor-for-sub.directive.ts | 50 ----- ...rective.ts => sh-attach-menu.directive.ts} | 0 src/lib/src/sh-context-menu.module.ts | 2 +- 5 files changed, 40 insertions(+), 209 deletions(-) delete mode 100644 src/lib/src/sh-anchor-for-sub.directive.ts rename src/lib/src/{sh-anchor-for.directive.ts => sh-attach-menu.directive.ts} (100%) diff --git a/README.md b/README.md index 7c57f89..042f0f5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ng2-right-click-menu +## ng2-right-click-menu _Right click context menu for Angular 2+_ __DEMO__ https://msarsha.github.io/ng2-right-click-menu/ @@ -36,13 +36,13 @@ import css file in your `styles.css`: #### Defining a Basic Menu Template The menu template is built using the `sh-context-menu` component as the menu wrapper, -and nesting `ng-template` with the `shContextMenuItem` directive for every menu item: +and nested `ng-template` with the `shContextMenuItem` directive for every menu item: The `shContextMenuItem` directive provide a template variable (`let-data`) that gives you access to the data object attached to the menu. ````html - +
Menu Item - {{data.label}}
@@ -50,175 +50,56 @@ The `shContextMenuItem` directive provide a template variable (`let-data`) that
```` -#### Attaching The Menu To An Element +#### Attaching Menu To An Element -Attaching works by using the `shMenu` directive and providing the `#menu` template variable: +Attaching works by using the `shAttachMenu` directive and providing the `#menu` (from the above example) template variable: + +The object provided to the `[shMenuData]` input will be available as a template variable inside `ng-template`s with `shContextMenuItem` ```html -
Right Click Me
+
Right Click Me
``` +## Sub Menus -### `[sh-context]` (`IShContextMenuItem[]`) - -````typescript - interface IShContextMenuItem { - label?: ((context: any) => string) | string; // as of version 0.0.11 this property is rendered as HTML - divider?: boolean; - onClick?($event: any): void; - visible?(context: any): boolean; - disabled?(context: any): boolean; - subMenu?: boolean; - subMenuItems?: IShContextMenuItem[]; -} -```` - -Example: - -````typescript - items: IShContextMenuItem[]; - - this.items = [ - { - label: 'Save', - onClick: this.clickEvent - }, - { - label: (context) => `Edit ${context.someVariable}`, - onClick: this.clickEvent - }, - { - label: 'Sub Menu', - subMenu: true, - subMenuItems: [ - { - label: 'Save', - onClick: this.clickEvent - }, - { - label: 'Edit', - onClick: this.clickEvent - }] - } - { - divider: true - }, - { - label: 'Remove', - disabled: ctx => { - return ctx.Two === 'Two'; - }, - onClick: this.clickEvent - }, - { - label: (context) => `Hide ${context.name}`, - onClick: this.clickEvent, - visible: ctx => { - return ctx.One === 'One'; - } - } - ]; - - clickEvent($event: any){ - console.log('clicked ', $event); - }; -```` - -### Passing a function to the label option - -You can pass either a string or a function that returns a string (using the data context as a parameter) to the `label` option of menu items. Passing a function allows the label to contain dynamic content. - -### onBeforeMenuOpen (v0.0.14) - -The `onBeforeMenuOpen` event can be used to cancel the menu from opening and allow to modify the menu items that will be display by the current event. - -The `open()` callback is used to continue the context menu event and can be injected with the new modified `IShContextMenuItem` items array. (optional. if items array is not provided the original array defined by `[sh-context]` will be used.) - -````html -
- Click Me ! -
-```` - -component: - -````typescript -onBefore = (event: BeforeMenuEvent) => { - event.open([new items]); - }; -```` - -`BeforeMenuEvent` interface: -````typescript -interface BeforeMenuEvent { - event: MouseEvent; - items: IShContextMenuItem[]; - open(items?: IShContextMenuItem[]): void; -} -```` +Sub menu is attached to the `shContextMenuItem` directive using the `[subMenu]` input. -### Options Object (v0.0.10) +The `[subMenu]` input is provided with a `sh-context-menu`'s template variable (just like attaching a menu to an element). ````html -
-```` - -````typescript - options: IShContextOptions = { - // set options - } -```` - -The options object is of type `IShContextOptions` and currently support the following options: - -Options | Type | Default | Description -:---:|:---:|:---:|:---| -rtl|boolean|false|right to left support -theme|string|light|menu color theme - -### Sub Menus (v0.0.9) - -Setting the `subMenu` property to `true` and the `subMenuItems` property to a `IShContextMenuItem[]` will render a sub menu. - -````typescript -{ - label: 'Sub Menu', - subMenu: true, - subMenuItems: [ - { - label: 'Save', - onClick: this.clickEvent - }, - { - label: 'Edit', - onClick: this.clickEvent - }] -} -```` - -#### The `onClick` handler - -The `onClick` handler is a function that is being injected with `$event` parameter. - -The `$event` structure is: - -````typescript - { - menuItem: item, - dataContext: this.dataContext - } + + +
+ Menu Item - {{data.label}} +
+
+ + +
+ Menu Item - {{data.label}} +
+
+
+
```` -Where the `menuItem` property is of type `IShContextMenuItem` and is the clicked menu item. - -And the `dataContext` is the object used on the `[sh-data-context]` binding. +## API +#### sh-context-menu -#### The `disabled` and `visible` handlers +Name | Type | Default | Description +:---:|:---:|:---:|:---: +[this]|any|null|the `this` context for input callbacks (visible) - typically the menu's host component -Both get injected with the object used on the `[sh-data-context]` binding +#### shContextMenuItem -And should return a `boolean` to indicate if the current `IShContextMenuItem` is disabled or visible. +Name | Type | Default | Description +:---:|:---:|:---:|:---: +[subMenu]|ShContextMenuComponent|null|sub menu +[divider]|boolean|false|render a divider +[closeOnClick]|boolean|true|should the menu close on click +[visible]|(event: ShContextMenuClickEvent) => boolean|null|function to determine if a item is visible +(click)|ShContextMenuClickEvent|null|click handler ### Setting up development env diff --git a/src/lib/src/index.ts b/src/lib/src/index.ts index f96025c..27c1db5 100644 --- a/src/lib/src/index.ts +++ b/src/lib/src/index.ts @@ -1,5 +1,5 @@ export {ShContextMenuModule} from './sh-context-menu.module'; export {ShContextMenuComponent} from './sh-context-menu.component'; -export {ShAttachMenuDirective} from './sh-anchor-for.directive'; +export {ShAttachMenuDirective} from './sh-attach-menu.directive'; export {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; export {ShContextMenuService} from './sh-context-menu.service'; diff --git a/src/lib/src/sh-anchor-for-sub.directive.ts b/src/lib/src/sh-anchor-for-sub.directive.ts deleted file mode 100644 index b6690b5..0000000 --- a/src/lib/src/sh-anchor-for-sub.directive.ts +++ /dev/null @@ -1,50 +0,0 @@ -// import {Directive, ElementRef, Input, OnDestroy} from '@angular/core'; -// import {ShContextMenuComponent} from './sh-context-menu.component'; -// import {fromEvent} from 'rxjs/observable/fromEvent'; -// import {Subscription} from 'rxjs/Subscription'; -// import {ShContextMenuService} from './sh-context-menu.service'; -// -// @Directive({ -// selector: '[shAnchorForSub]' -// }) -// export class ShAnchorForSubDirective implements OnDestroy { -// @Input('shAnchorForSub') menu: ShContextMenuComponent; -// @Input('shMenuData') data: any; -// -// sub: Subscription; -// -// open = false; -// -// constructor(private ctxService: ShContextMenuService, -// private elm: ElementRef, -// private hostMenu: ShContextMenuComponent) { -// this.addClasses(); -// -// this.sub = fromEvent(this.elm.nativeElement, 'mouseenter') -// .subscribe(this.openSubMenu.bind(this)); -// } -// -// openSubMenu(event: MouseEvent) { -// if (this.open) { -// return; -// } -// -// this.open = true; -// this.ctxService.openSubMenu({ -// menu: this.menu, -// mouseEvent: event, -// targetElement: this.elm, -// data: this.data, -// hostMenu: this.hostMenu -// }); -// } -// -// addClasses() { -// const element: HTMLElement = this.elm.nativeElement; -// element.classList.add('sh-sub-anchor'); -// } -// -// ngOnDestroy(): void { -// this.sub.unsubscribe(); -// } -// } diff --git a/src/lib/src/sh-anchor-for.directive.ts b/src/lib/src/sh-attach-menu.directive.ts similarity index 100% rename from src/lib/src/sh-anchor-for.directive.ts rename to src/lib/src/sh-attach-menu.directive.ts diff --git a/src/lib/src/sh-context-menu.module.ts b/src/lib/src/sh-context-menu.module.ts index 12ceb33..4e73183 100755 --- a/src/lib/src/sh-context-menu.module.ts +++ b/src/lib/src/sh-context-menu.module.ts @@ -2,7 +2,7 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {ShContextMenuComponent} from './sh-context-menu.component'; -import {ShAttachMenuDirective} from './sh-anchor-for.directive'; +import {ShAttachMenuDirective} from './sh-attach-menu.directive'; import {ShContextMenuService} from './sh-context-menu.service'; import {ShContextMenuItemDirective} from './sh-context-menu-item.directive'; import {OverlayModule} from '@angular/cdk/overlay'; From f7dcc868c3ce3206dfb1fdf5c0e0cf62927b068f Mon Sep 17 00:00:00 2001 From: Matan Sar-Shalom Date: Tue, 24 Apr 2018 23:37:00 +0300 Subject: [PATCH 30/30] version 1.0.0 --- src/lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/package.json b/src/lib/package.json index 3ec4e02..52cb498 100644 --- a/src/lib/package.json +++ b/src/lib/package.json @@ -2,7 +2,7 @@ "name": "ng2-right-click-menu", "version": "1.0.0", "author": "Matan Sar-Shalom ", - "description": "Right click context menu for Angular 2+", + "description": "Right click context menu for Angular", "license": "MIT", "private": false, "keywords": [