From 51d50f0ef642e0f996a1c8b8d2ef4838bdfec892 Mon Sep 17 00:00:00 2001 From: Tal Gitelman Date: Sun, 10 Dec 2017 18:55:03 +0200 Subject: Final commit to master merge from Change-Id: Ib464f9a8828437c86fe6def8af238aaf83473507 Issue-ID: SDC-714 Signed-off-by: Tal Gitelman --- .../ui/popover/popover-content.component.html | 24 ++ .../ui/popover/popover-content.component.less | 77 ++++++ .../ui/popover/popover-content.component.ts | 278 +++++++++++++++++++++ .../ng2/components/ui/popover/popover.component.ts | 179 +++++++++++++ .../ng2/components/ui/popover/popover.module.ts | 45 ++++ 5 files changed, 603 insertions(+) create mode 100644 catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.html create mode 100644 catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.less create mode 100644 catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.ts create mode 100644 catalog-ui/src/app/ng2/components/ui/popover/popover.component.ts create mode 100644 catalog-ui/src/app/ng2/components/ui/popover/popover.module.ts (limited to 'catalog-ui/src/app/ng2/components/ui/popover') diff --git a/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.html b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.html new file mode 100644 index 0000000000..6d76f0ad06 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.html @@ -0,0 +1,24 @@ +
+
+
+
+ {{ title }} + +
+ + +
+ + diff --git a/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.less b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.less new file mode 100644 index 0000000000..fbdec0b16b --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.less @@ -0,0 +1,77 @@ +@import '../../../../../assets/styles/variables'; +@import '../../../../../assets/styles/mixins'; +@import '../../../../../assets/styles/sprite-old'; +.popover .virtual-area { + height: 11px; + width: 100%; + position: absolute; +} +.popover.top .virtual-area { + bottom: -11px; +} +.popover.bottom .virtual-area { + top: -11px; +} +.popover.left .virtual-area { + right: -11px; +} +.popover.right .virtual-area { + left: -11px; +} +.popover.hide-arrow{ + margin: 0; +} + +.popover-header{ + .m_14_m; + font-weight: bold; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + height: 40px; + line-height: 48px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + text-align: left; + border-bottom: solid 1px @main_color_o; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin: 0px 20px; + max-width:350px; + + .title{ + flex: 1; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .close-button{ + .sprite; + .sprite.x-btn-black; + cursor: pointer; + } +} + +.popover-footer{ + background-color: @tlv_color_t; + height: 40px; + clear: both; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + button{ + margin: 8px 12px 8px 6px; + } +} diff --git a/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.ts b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.ts new file mode 100644 index 0000000000..6eb36287d0 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.ts @@ -0,0 +1,278 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +import {Component, Input, Output, AfterViewInit, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, EventEmitter, Renderer } from "@angular/core"; +import {ButtonsModelMap} from "app/models"; +import {PopoverComponent} from "./popover.component"; + +@Component({ + selector: "popover-content", + templateUrl:'./popover-content.component.html', + styleUrls:['popover-content.component.less'] +}) +export class PopoverContentComponent implements AfterViewInit, OnDestroy { + + @Input() public title: string; + @Input() public buttons:ButtonsModelMap; + + @Input() + content: string; + + @Input() + placement: "top"|"bottom"|"left"|"right"|"auto"|"auto top"|"auto bottom"|"auto left"|"auto right" = "bottom"; + + @Input() + animation: boolean = true; + + @Input() + closeOnClickOutside: boolean = false; + + @Input() + closeOnMouseOutside: boolean = false; + + @Input() + hideArrow: boolean = false; + + @ViewChild("popoverDiv") + popoverDiv: ElementRef; + + buttonsNames:Array; + + popover: PopoverComponent; + onCloseFromOutside = new EventEmitter(); + top: number = -10000; + left: number = -10000; + isIn: boolean = false; + displayType: string = "none"; + effectivePlacement: string; + + onDocumentMouseDown = (event: any) => { + const element = this.element.nativeElement; + if (!element || !this.popover) return; + if (element.contains(event.target) || this.popover.getElement().contains(event.target)) return; + this.hide(); + this.onCloseFromOutside.emit(undefined); + }; + + + constructor(protected element: ElementRef, + protected cdr: ChangeDetectorRef, + protected renderer: Renderer) { + } + + listenClickFunc: any; + listenMouseFunc: any; + + ngAfterViewInit(): void { + this.buttonsNames = Object.keys(this.buttons); + if (this.closeOnClickOutside) + this.listenClickFunc = this.renderer.listenGlobal("document", "mousedown", (event: any) => this.onDocumentMouseDown(event)); + if (this.closeOnMouseOutside) + this.listenMouseFunc = this.renderer.listenGlobal("document", "mouseover", (event: any) => this.onDocumentMouseDown(event)); + + this.show(); + this.cdr.detectChanges(); + } + + ngOnDestroy() { + if (this.closeOnClickOutside) + this.listenClickFunc(); + if (this.closeOnMouseOutside) + this.listenMouseFunc(); + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + show(): void { + if (!this.popover || !this.popover.getElement()) + return; + + const p = this.positionElements(this.popover.getElement(), this.popoverDiv.nativeElement, this.placement); + this.displayType = "block"; + this.top = p.top; + this.left = p.left; + this.isIn = true; + } + + hide(): void { + this.top = -10000; + this.left = -10000; + this.isIn = true; + this.popover.hide(); + } + + hideFromPopover() { + this.top = -10000; + this.left = -10000; + this.isIn = true; + } + + // ------------------------------------------------------------------------- + // Protected Methods + // ------------------------------------------------------------------------- + + protected positionElements(hostEl: HTMLElement, targetEl: HTMLElement, positionStr: string, appendToBody: boolean = false): { top: number, left: number } { + let positionStrParts = positionStr.split("-"); + let pos0 = positionStrParts[0]; + let pos1 = positionStrParts[1] || "center"; + let hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + let targetElWidth = targetEl.offsetWidth; + let targetElHeight = targetEl.offsetHeight; + + this.effectivePlacement = pos0 = this.getEffectivePlacement(pos0, hostEl, targetEl); + + let shiftWidth: any = { + center: function (): number { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function (): number { + return hostElPos.left; + }, + right: function (): number { + return hostElPos.left + hostElPos.width - targetElWidth; + } + }; + + let shiftHeight: any = { + center: function (): number { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function (): number { + return hostElPos.top; + }, + bottom: function (): number { + return hostElPos.top + hostElPos.height - targetElHeight; + } + }; + + let targetElPos: { top: number, left: number }; + switch (pos0) { + case "right": + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left + hostElPos.width + }; + break; + + case "left": + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + + case "bottom": + targetElPos = { + top: hostElPos.top + hostElPos.height, + left: shiftWidth[pos1]() + }; + break; + + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; + } + + protected position(nativeEl: HTMLElement): { width: number, height: number, top: number, left: number } { + let offsetParentBCR = { top: 0, left: 0 }; + const elBCR = this.offset(nativeEl); + const offsetParentEl = this.parentOffsetEl(nativeEl); + if (offsetParentEl !== window.document) { + offsetParentBCR = this.offset(offsetParentEl); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + const boundingClientRect = nativeEl.getBoundingClientRect(); + return { + width: boundingClientRect.width || nativeEl.offsetWidth, + height: boundingClientRect.height || nativeEl.offsetHeight, + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + } + + protected offset(nativeEl: any): { width: number, height: number, top: number, left: number } { + const boundingClientRect = nativeEl.getBoundingClientRect(); + return { + width: boundingClientRect.width || nativeEl.offsetWidth, + height: boundingClientRect.height || nativeEl.offsetHeight, + top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop), + left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft) + }; + } + + protected getStyle(nativeEl: HTMLElement, cssProp: string): string { + if ((nativeEl as any).currentStyle) // IE + return (nativeEl as any).currentStyle[cssProp]; + + if (window.getComputedStyle) + return (window.getComputedStyle as any)(nativeEl)[cssProp]; + + // finally try and get inline style + return (nativeEl.style as any)[cssProp]; + } + + protected isStaticPositioned(nativeEl: HTMLElement): boolean { + return (this.getStyle(nativeEl, "position") || "static" ) === "static"; + } + + protected parentOffsetEl(nativeEl: HTMLElement): any { + let offsetParent: any = nativeEl.offsetParent || window.document; + while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || window.document; + } + + protected getEffectivePlacement(placement: string, hostElement: HTMLElement, targetElement: HTMLElement): string { + const placementParts = placement.split(" "); + if (placementParts[0] !== "auto") { + return placement; + } + + const hostElBoundingRect = hostElement.getBoundingClientRect(); + + const desiredPlacement = placementParts[1] || "bottom"; + + if (desiredPlacement === "top" && hostElBoundingRect.top - targetElement.offsetHeight < 0) { + return "bottom"; + } + if (desiredPlacement === "bottom" && hostElBoundingRect.bottom + targetElement.offsetHeight > window.innerHeight) { + return "top"; + } + if (desiredPlacement === "left" && hostElBoundingRect.left - targetElement.offsetWidth < 0) { + return "right"; + } + if (desiredPlacement === "right" && hostElBoundingRect.right + targetElement.offsetWidth > window.innerWidth) { + return "left"; + } + + return desiredPlacement; + } +} diff --git a/catalog-ui/src/app/ng2/components/ui/popover/popover.component.ts b/catalog-ui/src/app/ng2/components/ui/popover/popover.component.ts new file mode 100644 index 0000000000..e976bc7bf2 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/popover/popover.component.ts @@ -0,0 +1,179 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +import { Directive, HostListener, ComponentRef, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, Input, OnChanges, SimpleChange, Output, EventEmitter } from "@angular/core"; +import {PopoverContentComponent} from "./popover-content.component"; + +@Directive({ + selector: "[popover]", + exportAs: "popover" +}) +export class PopoverComponent implements OnChanges { + + protected PopoverComponent = PopoverContentComponent; + protected popover: ComponentRef; + protected visible: boolean; + + + constructor(protected viewContainerRef: ViewContainerRef, + protected resolver: ComponentFactoryResolver) { + } + + @Input("popover") + content: string|PopoverContentComponent; + + @Input() + popoverDisabled: boolean; + + @Input() + popoverAnimation: boolean; + + @Input() + popoverPlacement: "top"|"bottom"|"left"|"right"|"auto"|"auto top"|"auto bottom"|"auto left"|"auto right"; + + @Input() + popoverTitle: string; + + @Input() + popoverOnHover: boolean = false; + + @Input() + popoverCloseOnClickOutside: boolean; + + @Input() + popoverCloseOnMouseOutside: boolean; + + @Input() + popoverDismissTimeout: number = 0; + + @Output() + onShown = new EventEmitter(); + + @Output() + onHidden = new EventEmitter(); + + @HostListener("click") + showOrHideOnClick(): void { + if (this.popoverOnHover) return; + if (this.popoverDisabled) return; + this.toggle(); + } + + @HostListener("focusin") + @HostListener("mouseenter") + showOnHover(): void { + if (!this.popoverOnHover) return; + if (this.popoverDisabled) return; + this.show(); + } + + @HostListener("focusout") + @HostListener("mouseleave") + hideOnHover(): void { + if (this.popoverCloseOnMouseOutside) return; + if (!this.popoverOnHover) return; + if (this.popoverDisabled) return; + this.hide(); + } + + ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { + if (changes["popoverDisabled"]) { + if (changes["popoverDisabled"].currentValue) { + this.hide(); + } + } + } + + toggle() { + if (!this.visible) { + this.show(); + } else { + this.hide(); + } + } + + show() { + if (this.visible) return; + + this.visible = true; + if (typeof this.content === "string") { + const factory = this.resolver.resolveComponentFactory(this.PopoverComponent); + if (!this.visible) + return; + + this.popover = this.viewContainerRef.createComponent(factory); + const popover = this.popover.instance as PopoverContentComponent; + popover.popover = this; + popover.content = this.content as string; + if (this.popoverPlacement !== undefined) + popover.placement = this.popoverPlacement; + if (this.popoverAnimation !== undefined) + popover.animation = this.popoverAnimation; + if (this.popoverTitle !== undefined) + popover.title = this.popoverTitle; + if (this.popoverCloseOnClickOutside !== undefined) + popover.closeOnClickOutside = this.popoverCloseOnClickOutside; + if (this.popoverCloseOnMouseOutside !== undefined) + popover.closeOnMouseOutside = this.popoverCloseOnMouseOutside; + + popover.onCloseFromOutside.subscribe(() => this.hide()); + if (this.popoverDismissTimeout > 0) + setTimeout(() => this.hide(), this.popoverDismissTimeout); + } else { + const popover = this.content as PopoverContentComponent; + popover.popover = this; + if (this.popoverPlacement !== undefined) + popover.placement = this.popoverPlacement; + if (this.popoverAnimation !== undefined) + popover.animation = this.popoverAnimation; + if (this.popoverTitle !== undefined) + popover.title = this.popoverTitle; + if (this.popoverCloseOnClickOutside !== undefined) + popover.closeOnClickOutside = this.popoverCloseOnClickOutside; + if (this.popoverCloseOnMouseOutside !== undefined) + popover.closeOnMouseOutside = this.popoverCloseOnMouseOutside; + + popover.onCloseFromOutside.subscribe(() => this.hide()); + if (this.popoverDismissTimeout > 0) + setTimeout(() => this.hide(), this.popoverDismissTimeout); + popover.show(); + } + + this.onShown.emit(this); + } + + hide() { + if (!this.visible) return; + + this.visible = false; + if (this.popover) + this.popover.destroy(); + + if (this.content instanceof PopoverContentComponent) + (this.content as PopoverContentComponent).hideFromPopover(); + + this.onHidden.emit(this); + } + + getElement() { + return this.viewContainerRef.element.nativeElement; + } + +} diff --git a/catalog-ui/src/app/ng2/components/ui/popover/popover.module.ts b/catalog-ui/src/app/ng2/components/ui/popover/popover.module.ts new file mode 100644 index 0000000000..d8cacc1696 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/popover/popover.module.ts @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +/** + * Created by rc2122 on 5/17/2017. + */ +import {NgModule} from "@angular/core"; +import { CommonModule } from '@angular/common'; +import {PopoverComponent} from "./popover.component"; +import {PopoverContentComponent} from "./popover-content.component"; + +@NgModule({ + declarations: [ + PopoverComponent, + PopoverContentComponent + ], + imports: [ + CommonModule + ], + exports: [ + PopoverComponent, + PopoverContentComponent + ], + providers: [] +}) +export class PopoverModule { + +} -- cgit 1.2.3-korg