Add paletx common & util 37/38937/1
authorYuanHu <yuan.hu1@zte.com.cn>
Tue, 27 Mar 2018 07:50:00 +0000 (15:50 +0800)
committerYuanHu <yuan.hu1@zte.com.cn>
Tue, 27 Mar 2018 07:50:00 +0000 (15:50 +0800)
Add paletx common & util

Issue-ID: SDC-1130,SDC-1131

Change-Id: Ibbdd8d4fabf45637be7d44400c18d34efc15d46e
Signed-off-by: YuanHu <yuan.hu1@zte.com.cn>
sdc-workflow-designer-ui/src/app/paletx/common/shared.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/util/popup.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/util/positioning.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/util/triggers.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/util/util.ts [new file with mode: 0644]

diff --git a/sdc-workflow-designer-ui/src/app/paletx/common/shared.ts b/sdc-workflow-designer-ui/src/app/paletx/common/shared.ts
new file mode 100644 (file)
index 0000000..e0d9bdb
--- /dev/null
@@ -0,0 +1,283 @@
+import {CommonModule} from '@angular/common';
+import {AfterContentInit, ContentChild, ContentChildren, Directive, EmbeddedViewRef, EventEmitter, Input, NgModule} from '@angular/core';
+import {OnDestroy, OnInit, Output, QueryList, TemplateRef, ViewContainerRef} from '@angular/core';
+import {Component} from '@angular/core';
+
+@Component({selector: 'plx-header', template: '<ng-content></ng-content>'})
+export class PlxHeaderComponent {
+}
+
+@Component({selector: 'plx-footer', template: '<ng-content></ng-content>'})
+export class PlxFooterComponent {
+}
+
+@Directive({selector: '[pTemplate]'})
+export class PlxPrimeTemplateDirective {
+  @Input() public type: string;
+
+  @Input() public pxTemplate: string;
+
+  constructor(public template: TemplateRef<any>) {}
+
+  getType(): string {
+       if (this.type) {
+               console.log(
+                       'Defining a pTemplate with type property is deprecated use pTemplate="type" instead.');
+               return this.type;
+       } else {
+               return this.pxTemplate;
+       }
+  }
+}
+
+@Directive({selector: '[pxTemplateWrapper]'})
+export class PlxTemplateWrapperDirective implements OnInit, OnDestroy {
+  @Input() public item: any;
+
+  @Input() public index: number;
+
+  @Input() public pxTemplateWrapper: TemplateRef<any>;
+
+  view: EmbeddedViewRef<any>;
+
+  constructor(public viewContainer: ViewContainerRef) {}
+
+  ngOnInit() {
+       this.view = this.viewContainer.createEmbeddedView(
+               this.pxTemplateWrapper, {'\$implicit': this.item, 'index': this.index});
+  }
+
+  ngOnDestroy() {
+       this.view.destroy();
+  }
+}
+
+@Component({selector: 'plx-column', template: ``})
+export class PlxColumnComponent implements AfterContentInit {
+  @Input() public field: string;
+  @Input() public sortField: string;
+  @Input() public header: string;
+  @Input() public footer: string;
+  @Input() public sortable: any;
+  @Input() public editable: boolean;
+  @Input() public filter: boolean;
+  @Input() public filterMatchMode: string;
+  @Input() public rowspan: number;
+  @Input() public colspan: number;
+  @Input() public style: any;
+  @Input() public styleClass: string;
+  @Input() public hidden: boolean;
+  @Input() public expander: boolean;
+  @Input() public selectionMode: string;
+  @Input() public filterPlaceholder: string;
+  @Input() public frozen: boolean;
+  @Output() sortFunction: EventEmitter<any> = new EventEmitter();
+  @ContentChildren(PlxPrimeTemplateDirective) templates: QueryList<any>;
+  @ContentChild(TemplateRef) template: TemplateRef<any>;
+
+  headerTemplate: TemplateRef<any>;
+  bodyTemplate: TemplateRef<any>;
+  footerTemplate: TemplateRef<any>;
+  filterTemplate: TemplateRef<any>;
+  editorTemplate: TemplateRef<any>;
+
+  ngAfterContentInit(): void {
+       this.templates.forEach((item) => {
+               switch (item.getType()) {
+               case 'header':
+                       this.headerTemplate = item.template;
+                       break;
+
+               case 'body':
+                       this.bodyTemplate = item.template;
+                       break;
+
+               case 'footer':
+                       this.footerTemplate = item.template;
+                       break;
+
+               case 'filter':
+                       this.filterTemplate = item.template;
+                       break;
+
+               case 'editor':
+                       this.editorTemplate = item.template;
+                       break;
+
+               default:
+                       this.bodyTemplate = item.template;
+               }
+       });
+  }
+}
+
+@Component({selector: 'plx-row', template: ``})
+export class PlxRowComponent {
+  @ContentChildren(PlxColumnComponent) columns: QueryList<PlxColumnComponent>;
+}
+
+@Component({selector: 'plx-header-column-group', template: ``})
+export class PlxHeaderColumnGroupComponent {
+  @ContentChildren(PlxRowComponent) rows: QueryList<any>;
+}
+
+@Component({selector: 'plx-footer-column-group', template: ``})
+export class PlxFooterColumnGroupComponent {
+  @ContentChildren(PlxRowComponent) rows: QueryList<any>;
+}
+
+@Component({selector: 'plx-column-body-template-loader', template: ``})
+export class PlxColumnBodyTemplateLoaderComponent implements OnInit, OnDestroy {
+  @Input() public column: any;
+
+  @Input() public rowData: any;
+
+  @Input() public rowIndex: number;
+
+  view: EmbeddedViewRef<any>;
+
+  constructor(public viewContainer: ViewContainerRef) {}
+
+  ngOnInit() {
+       this.view =
+               this.viewContainer.createEmbeddedView(this.column.bodyTemplate, {
+                       '\$implicit': this.column,
+                       'rowData': this.rowData,
+                       'rowIndex': this.rowIndex
+               });
+  }
+
+  ngOnDestroy() {
+       this.view.destroy();
+  }
+}
+
+@Component({selector: 'plx-column-header-template-loader', template: ``})
+export class PlxColumnHeaderTemplateLoaderComponent implements OnInit,
+                                                                                                                               OnDestroy {
+  @Input() public column: any;
+
+  view: EmbeddedViewRef<any>;
+
+  constructor(public viewContainer: ViewContainerRef) {}
+
+  ngOnInit() {
+       this.view = this.viewContainer.createEmbeddedView(
+               this.column.headerTemplate, {'\$implicit': this.column});
+  }
+
+  ngOnDestroy() {
+       this.view.destroy();
+  }
+}
+
+@Component({selector: 'plx-column--footer-template-loader', template: ``})
+export class PlxColumnFooterTemplateLoaderComponent implements OnInit,
+                                                                                                                               OnDestroy {
+  @Input() public column: any;
+
+  view: EmbeddedViewRef<any>;
+
+  constructor(public viewContainer: ViewContainerRef) {}
+
+  ngOnInit() {
+       this.view = this.viewContainer.createEmbeddedView(
+               this.column.footerTemplate, {'\$implicit': this.column});
+  }
+
+  ngOnDestroy() {
+       this.view.destroy();
+  }
+}
+
+@Component({selector: 'plx-column-filter-template-loader', template: ``})
+export class PlxColumnFilterTemplateLoaderComponent implements OnInit,
+                                                                                                                               OnDestroy {
+  @Input() public column: any;
+
+  view: EmbeddedViewRef<any>;
+
+  constructor(public viewContainer: ViewContainerRef) {}
+
+  ngOnInit() {
+       this.view = this.viewContainer.createEmbeddedView(
+               this.column.filterTemplate, {'\$implicit': this.column});
+  }
+
+  ngOnDestroy() {
+       this.view.destroy();
+  }
+}
+
+@Component({selector: 'plx-column-editor-template-loader', template: ``})
+export class PlxColumnEditorTemplateLoaderComponent implements OnInit,
+                                                                                                                               OnDestroy {
+  @Input() public column: any;
+
+  @Input() public rowData: any;
+
+  @Input() public rowIndex: any;
+
+  view: EmbeddedViewRef<any>;
+
+  constructor(public viewContainer: ViewContainerRef) {}
+
+  ngOnInit() {
+       this.view =
+               this.viewContainer.createEmbeddedView(this.column.editorTemplate, {
+                       '\$implicit': this.column,
+                       'rowData': this.rowData,
+                       'rowIndex': this.rowIndex
+               });
+  }
+
+  ngOnDestroy() {
+       this.view.destroy();
+  }
+}
+
+@Component({selector: 'plx-template-loader', template: ``})
+export class PlxTemplateLoaderComponent implements OnInit, OnDestroy {
+  @Input() public template: TemplateRef<any>;
+
+  @Input() public data: any;
+
+  view: EmbeddedViewRef<any>;
+
+  constructor(public viewContainer: ViewContainerRef) {}
+
+  ngOnInit() {
+       if (this.template) {
+               this.view = this.viewContainer.createEmbeddedView(
+                       this.template, {'\$implicit': this.data});
+       }
+  }
+
+  ngOnDestroy() {
+       if (this.view) {
+               this.view.destroy();
+       }
+  }
+}
+
+@NgModule({
+  imports: [CommonModule],
+  exports: [
+       PlxHeaderComponent, PlxFooterComponent, PlxColumnComponent,
+       PlxTemplateWrapperDirective, PlxColumnHeaderTemplateLoaderComponent,
+       PlxColumnBodyTemplateLoaderComponent, PlxColumnFooterTemplateLoaderComponent,
+       PlxColumnFilterTemplateLoaderComponent, PlxPrimeTemplateDirective,
+       PlxTemplateLoaderComponent, PlxRowComponent, PlxHeaderColumnGroupComponent,
+       PlxFooterColumnGroupComponent, PlxColumnEditorTemplateLoaderComponent
+  ],
+  declarations: [
+       PlxHeaderComponent, PlxFooterComponent, PlxColumnComponent,
+       PlxTemplateWrapperDirective, PlxColumnHeaderTemplateLoaderComponent,
+       PlxColumnBodyTemplateLoaderComponent, PlxColumnFooterTemplateLoaderComponent,
+       PlxColumnFilterTemplateLoaderComponent, PlxPrimeTemplateDirective,
+       PlxTemplateLoaderComponent, PlxRowComponent, PlxHeaderColumnGroupComponent,
+       PlxFooterColumnGroupComponent, PlxColumnEditorTemplateLoaderComponent
+  ]
+})
+export class PlxSharedModule {
+}
\ No newline at end of file
diff --git a/sdc-workflow-designer-ui/src/app/paletx/util/popup.ts b/sdc-workflow-designer-ui/src/app/paletx/util/popup.ts
new file mode 100644 (file)
index 0000000..dacbc0b
--- /dev/null
@@ -0,0 +1,59 @@
+import {
+       Injector,
+       TemplateRef,
+       ViewRef,
+       ViewContainerRef,
+       Renderer,
+       ComponentRef,
+       ComponentFactory,
+       ComponentFactoryResolver
+} from '@angular/core';
+
+export class ContentRef {
+       constructor(public nodes: any[], public viewRef?: ViewRef, public componentRef?: ComponentRef<any>) {
+       }
+}
+
+export class PopupService<T> {
+       private _windowFactory: ComponentFactory<T>;
+       private _windowRef: ComponentRef<T>;
+       private _contentRef: ContentRef;
+
+       constructor(type: any, private _injector: Injector, private _viewContainerRef: ViewContainerRef, private _renderer: Renderer,
+                               componentFactoryResolver: ComponentFactoryResolver) {
+               this._windowFactory = componentFactoryResolver.resolveComponentFactory<T>(type);
+       }
+
+       public open(content?: string | TemplateRef<any>, context?: any): ComponentRef<T> {
+               if (!this._windowRef) {
+                       this._contentRef = this._getContentRef(content, context);
+                       this._windowRef =
+                               this._viewContainerRef.createComponent(this._windowFactory, 0, this._injector, this._contentRef.nodes);
+               }
+
+               return this._windowRef;
+       }
+
+       public close() {
+               if (this._windowRef) {
+                       this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._windowRef.hostView));
+                       this._windowRef = null;
+
+                       if (this._contentRef.viewRef) {
+                               this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._contentRef.viewRef));
+                               this._contentRef = null;
+                       }
+               }
+       }
+
+       private _getContentRef(content: string | TemplateRef<any>, context?: any): ContentRef {
+               if (!content) {
+                       return new ContentRef([]);
+               } else if (content instanceof TemplateRef) {
+                       const viewRef = this._viewContainerRef.createEmbeddedView(<TemplateRef<T>>content, context);
+                       return new ContentRef([viewRef.rootNodes], viewRef);
+               } else {
+                       return new ContentRef([[this._renderer.createText(null, `${content}`)]]);
+               }
+       }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/util/positioning.ts b/sdc-workflow-designer-ui/src/app/paletx/util/positioning.ts
new file mode 100644 (file)
index 0000000..79399d6
--- /dev/null
@@ -0,0 +1,425 @@
+// previous version:
+// https://github.com/angular-ui/bootstrap/blob/07c31d0731f7cb068a1932b8e01d2312b796b4ec/src/position/position.js
+export class Positioning {
+    private getStyle(element: HTMLElement, prop: string): string {
+        return window.getComputedStyle(element)[prop];
+    }
+
+    private isStaticPositioned(element: HTMLElement): boolean {
+        return (this.getStyle(element, 'position') || 'static') === 'static';
+    }
+
+    private offsetParent(element: HTMLElement): HTMLElement {
+        let offsetParentEl = <HTMLElement>element.offsetParent || document.documentElement;
+
+        while (offsetParentEl && offsetParentEl !== document.documentElement && this.isStaticPositioned(offsetParentEl)) {
+            offsetParentEl = <HTMLElement>offsetParentEl.offsetParent;
+        }
+
+        return offsetParentEl || document.documentElement;
+    }
+
+   public position(element: HTMLElement, round = true): ClientRect {
+        let elPosition: ClientRect;
+        let parentOffset: ClientRect = {width: 0, height: 0, top: 0, bottom: 0, left: 0, right: 0};
+
+        if (this.getStyle(element, 'position') === 'fixed') {
+            elPosition = element.getBoundingClientRect();
+        } else {
+            const offsetParentEl = this.offsetParent(element);
+
+            elPosition = this.offset(element, false);
+
+            if (offsetParentEl !== document.documentElement) {
+                parentOffset = this.offset(offsetParentEl, false);
+            }
+
+            parentOffset.top += offsetParentEl.clientTop;
+            parentOffset.left += offsetParentEl.clientLeft;
+        }
+
+        elPosition.top -= parentOffset.top;
+        elPosition.bottom -= parentOffset.top;
+        elPosition.left -= parentOffset.left;
+        elPosition.right -= parentOffset.left;
+
+        if (round) {
+            elPosition.top = Math.round(elPosition.top);
+            elPosition.bottom = Math.round(elPosition.bottom);
+            elPosition.left = Math.round(elPosition.left);
+            elPosition.right = Math.round(elPosition.right);
+        }
+
+        return elPosition;
+    }
+
+    public offset(element: HTMLElement, round = true): ClientRect {
+        const elBcr = element.getBoundingClientRect();
+        const viewportOffset = {
+            top: window.pageYOffset - document.documentElement.clientTop,
+            left: window.pageXOffset - document.documentElement.clientLeft
+        };
+
+        let elOffset = {
+            height: elBcr.height || element.offsetHeight,
+            width: elBcr.width || element.offsetWidth,
+            top: elBcr.top + viewportOffset.top,
+            bottom: elBcr.bottom + viewportOffset.top,
+            left: elBcr.left + viewportOffset.left,
+            right: elBcr.right + viewportOffset.left
+        };
+
+        if (round) {
+            elOffset.height = Math.round(elOffset.height);
+            elOffset.width = Math.round(elOffset.width);
+            elOffset.top = Math.round(elOffset.top);
+            elOffset.bottom = Math.round(elOffset.bottom);
+            elOffset.left = Math.round(elOffset.left);
+            elOffset.right = Math.round(elOffset.right);
+        }
+
+        return elOffset;
+    }
+
+
+    public getPlacementPrimary(hostElement: HTMLElement, targetElement: HTMLElement, placement: string): string {
+
+        // let placementPrimaryArray = ['right', 'bottom', 'left', 'top'];
+        // placementPrimaryArray.splice(placementPrimaryArray.indexOf(placementPrimary), 1);
+        // placementPrimaryArray.splice(0, 0, placementPrimary);
+
+        let placementPrimaryArray = this.getTotalPlacementArr(placement);
+        let placementPrimary;
+        let placementSecondary;
+        let rect;
+
+        let result = placementPrimaryArray.find(place => {
+            placementPrimary = place.split('-')[0] || 'top';
+            placementSecondary = place.split('-')[1] || 'center';
+            rect = this.getBoundingClientRect(placementPrimary, placementSecondary, hostElement, targetElement);
+            return this.canDisplay(rect);
+        });
+
+        if (!result) {
+            return placement;
+        } else {
+            return result;
+        }
+    }
+
+    private getTotalPlacementArr(placement: string): any {
+        let placementPrimary = placement.split('-')[0] || 'top';
+
+        let placementBasic = ['right', 'bottom', 'left', 'top'];
+        placementBasic.splice(placementBasic.indexOf(placementPrimary), 1);
+        placementBasic.splice(0, 0, placementPrimary);
+        let placeTotal = {
+            right: [
+                'right',
+                'right-top',
+                'right-bottom'
+            ],
+            bottom: [
+                'bottom',
+                'bottom-left',
+                'bottom-right'
+            ],
+            left: [
+                'left',
+                'left-top',
+                'left-bottom'
+            ],
+            top: [
+                'top',
+                'top-left',
+                'top-right'
+            ]
+        }
+        let placeArr = [];
+        placeArr.push(placement);
+        placementBasic.forEach(placePri => {
+            placeTotal[placePri].forEach(palce => {
+                if (placement !== palce) {
+                    placeArr.push(palce);
+                }
+            });
+        });
+        return placeArr;
+    }
+
+       private canDisplay(rect): boolean {
+               if(this.isSheltered(rect, window)) {
+                       return false;
+               } else {
+                       var pElement = this.getParentElement(window);
+                       if(pElement) {
+                               const shelter = this.getShelter(pElement, window.parent, rect);
+                               if(shelter) {
+                                       return false;
+                               } else {
+                                       return true;
+                               }
+                       } else {
+                               return true;
+                       }
+
+               }
+       }
+
+       /**
+        * 判断当前位置在对应的窗口中是否会被遮挡
+        * 用于iframe嵌套的场景
+        * @param position
+        * @param currentWindow
+        */
+       private isSheltered(position, currentWindow) {
+               if(position.left < 0 || position.top < 0
+                       || position.right > currentWindow.document.documentElement.clientWidth
+                       || position.bottom > currentWindow.document.documentElement.clientHeight) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * 递归判断当前元素是否会被视口遮挡
+        * @param element
+        * @param currentWindow
+        * @param position
+        */
+       public getShelter(element, currentWindow, position) {
+               var rect = element.getBoundingClientRect();
+               position.left += rect.left;
+               position.right += rect.left;
+               position.top += rect.top;
+               position.bottom += rect.top;
+
+               if(this.isSheltered(position, currentWindow)) {
+                       return element;
+               } else {
+                       if(currentWindow.parent != currentWindow) { // 判断是否到达最顶级容器
+                               var pElement = this.getParentElement(currentWindow);
+                               return this.getShelter(pElement, currentWindow.parent, position);
+                       } else {
+                               return null;
+                       }
+               }
+       }
+
+       /**
+        * 查找当前容器在父窗口中的dom元素
+        * 如:iframe场景中,则为在子页面中查找父页面中的iframe节点
+        * @param currentWindow
+        */
+       public getParentElement(currentWindow) {
+               if(currentWindow.parent !== currentWindow) {
+                       var parentWindow = currentWindow.parent;
+                       var frames = parentWindow.document.getElementsByTagName("iframe");
+                       for(var i=0; i<frames.length; i++) {
+                               if(frames[i].contentWindow === currentWindow) {
+                                       return frames[i];
+                               }
+                       }
+               }
+       }
+
+    private getBoundingClientRect(placementPrimary, placementSecondary, hostElement, targetElement) {
+        const hostBcr = hostElement.getBoundingClientRect();
+
+        // const shiftWidth = {
+        //     left: hostBcr.left,
+        //     center: hostBcr.left + hostElement.offsetWidth/2 - targetElement.offsetWidth/2,
+        //     right: hostBcr.left + hostElement.offsetWidth,
+        // };
+        // const shiftHeight = {
+        //     top: hostBcr.top,
+        //     center: hostBcr.top + hostElement.offsetHeight/2 - targetElement.offsetHeight/2,
+        //     bottom: hostBcr.top + hostElement.offsetHeight,
+        // };
+
+        const shiftWidth: any = {
+            left: hostBcr.left,
+            center: hostBcr.left + hostBcr.width / 2 - targetElement.offsetWidth / 2,
+            right: hostBcr.right - targetElement.offsetWidth
+        };
+        const shiftHeight: any = {
+            top: hostBcr.top,
+            center: hostBcr.top + hostBcr.height / 2 - targetElement.offsetHeight / 2,
+            bottom: hostBcr.bottom - targetElement.offsetHeight
+        };
+        switch (placementPrimary) {
+            case 'top':
+                return {
+                    top: hostBcr.top - targetElement.offsetHeight,
+                    bottom: hostBcr.top,
+                    left: shiftWidth[placementSecondary],
+                    right: shiftWidth[placementSecondary] + targetElement.offsetWidth
+                };
+            case 'bottom':
+                return {
+                    top: hostBcr.bottom,
+                    bottom: hostBcr.bottom + targetElement.offsetHeight,
+                    left: shiftWidth[placementSecondary],
+                    right: shiftWidth[placementSecondary] + targetElement.offsetWidth
+                };
+            case 'left':
+                return {
+                    top: shiftHeight[placementSecondary],
+                    bottom: shiftHeight[placementSecondary] + targetElement.offsetHeight,
+                    left: hostBcr.left - targetElement.offsetWidth,
+                    right: hostBcr.left
+                };
+            case 'right':
+                return {
+                    top: shiftHeight[placementSecondary],
+                    bottom: shiftHeight[placementSecondary] + targetElement.offsetHeight,
+                    left: hostBcr.right,
+                    right: hostBcr.right + targetElement.offsetWidth,
+                };
+        }
+    }
+
+       public  positionElements(hostElement: HTMLElement, targetElement: HTMLElement, placement: string, appendToBody?: boolean): ClientRect {
+        const hostElPosition = appendToBody ? this.offset(hostElement, false) : this.position(hostElement, false);
+        const shiftWidthPri: any = {
+            left: hostElPosition.left,
+            right: hostElPosition.left + hostElPosition.width
+        };
+        const shiftHeightPri: any = {
+            top: hostElPosition.top,
+            bottom: hostElPosition.top + hostElPosition.height
+        };
+        const targetElBCR = targetElement.getBoundingClientRect();
+        placement = this.getPlacementPrimary(hostElement, targetElement, placement);
+        let placementPrimary = placement.split('-')[0] || 'top';
+        const placementSecondary = placement.split('-')[1] || 'center';
+
+        let targetElPosition: ClientRect = {
+            height: targetElBCR.height || targetElement.offsetHeight,
+            width: targetElBCR.width || targetElement.offsetWidth,
+            top: 0,
+            bottom: targetElBCR.height || targetElement.offsetHeight,
+            left: 0,
+            right: targetElBCR.width || targetElement.offsetWidth
+        };
+      const shiftWidthSec: any = {
+          left: hostElPosition.left,
+          center: hostElPosition.left + hostElPosition.width / 2 - targetElement.offsetWidth / 2,
+          right: hostElPosition.right - targetElement.offsetWidth
+      };
+      const shiftHeightSec: any = {
+          top: hostElPosition.top,
+          center: hostElPosition.top + hostElPosition.height / 2 - targetElement.offsetHeight / 2,
+          bottom: hostElPosition.bottom - targetElement.offsetHeight
+      };
+
+        switch (placementPrimary) {
+            case 'top':
+                targetElPosition.top = hostElPosition.top - targetElement.offsetHeight;
+                targetElPosition.bottom += hostElPosition.top - targetElement.offsetHeight;
+                targetElPosition.left = shiftWidthSec[placementSecondary];
+                targetElPosition.right += shiftWidthSec[placementSecondary];
+                break;
+            case 'bottom':
+                targetElPosition.top = shiftHeightPri[placementPrimary];
+                targetElPosition.bottom += shiftHeightPri[placementPrimary];
+                targetElPosition.left = shiftWidthSec[placementSecondary];
+                targetElPosition.right += shiftWidthSec[placementSecondary];
+                break;
+            case 'left':
+                targetElPosition.top = shiftHeightSec[placementSecondary];
+                targetElPosition.bottom += shiftHeightSec[placementSecondary];
+                targetElPosition.left = hostElPosition.left - targetElement.offsetWidth;
+                targetElPosition.right += hostElPosition.left - targetElement.offsetWidth;
+                break;
+            case 'right':
+                targetElPosition.top = shiftHeightSec[placementSecondary];
+                targetElPosition.bottom += shiftHeightSec[placementSecondary];
+                targetElPosition.left = shiftWidthPri[placementPrimary];
+                targetElPosition.right += shiftWidthPri[placementPrimary];
+                break;
+        }
+
+        targetElPosition.top = Math.round(targetElPosition.top);
+        targetElPosition.bottom = Math.round(targetElPosition.bottom);
+        targetElPosition.left = Math.round(targetElPosition.left);
+        targetElPosition.right = Math.round(targetElPosition.right);
+
+        return targetElPosition;
+    }
+
+       public  positionElements_bak(hostElement: HTMLElement, targetElement: HTMLElement, placement: string, appendToBody?: boolean): ClientRect {
+        const hostElPosition = appendToBody ? this.offset(hostElement, false) : this.position(hostElement, false);
+        const shiftWidth: any = {
+            left: hostElPosition.left,
+            center: hostElPosition.left + hostElPosition.width / 2 - targetElement.offsetWidth / 2,
+            right: hostElPosition.left + hostElPosition.width
+        };
+        const shiftHeight: any = {
+            top: hostElPosition.top,
+            center: hostElPosition.top + hostElPosition.height / 2 - targetElement.offsetHeight / 2,
+            bottom: hostElPosition.top + hostElPosition.height
+        };
+        const targetElBCR = targetElement.getBoundingClientRect();
+        const placementPrimary = placement.split('-')[0] || 'top';
+        const placementSecondary = placement.split('-')[1] || 'center';
+
+        let targetElPosition: ClientRect = {
+            height: targetElBCR.height || targetElement.offsetHeight,
+            width: targetElBCR.width || targetElement.offsetWidth,
+            top: 0,
+            bottom: targetElBCR.height || targetElement.offsetHeight,
+            left: 0,
+            right: targetElBCR.width || targetElement.offsetWidth
+        };
+
+        switch (placementPrimary) {
+            case 'top':
+                targetElPosition.top = hostElPosition.top - targetElement.offsetHeight;
+                targetElPosition.bottom += hostElPosition.top - targetElement.offsetHeight;
+                targetElPosition.left = shiftWidth[placementSecondary];
+                targetElPosition.right += shiftWidth[placementSecondary];
+                break;
+            case 'bottom':
+                targetElPosition.top = shiftHeight[placementPrimary];
+                targetElPosition.bottom += shiftHeight[placementPrimary];
+                targetElPosition.left = shiftWidth[placementSecondary];
+                targetElPosition.right += shiftWidth[placementSecondary];
+                break;
+            case 'left':
+                targetElPosition.top = shiftHeight[placementSecondary];
+                targetElPosition.bottom += shiftHeight[placementSecondary];
+                targetElPosition.left = hostElPosition.left - targetElement.offsetWidth;
+                targetElPosition.right += hostElPosition.left - targetElement.offsetWidth;
+                break;
+            case 'right':
+                targetElPosition.top = shiftHeight[placementSecondary];
+                targetElPosition.bottom += shiftHeight[placementSecondary];
+                targetElPosition.left = shiftWidth[placementPrimary];
+                targetElPosition.right += shiftWidth[placementPrimary];
+                break;
+        }
+
+        targetElPosition.top = Math.round(targetElPosition.top);
+        targetElPosition.bottom = Math.round(targetElPosition.bottom);
+        targetElPosition.left = Math.round(targetElPosition.left);
+        targetElPosition.right = Math.round(targetElPosition.right);
+
+        return targetElPosition;
+    }
+}
+
+const positionService = new Positioning();
+export function positionElements(hostElement: HTMLElement, targetElement: HTMLElement, placement: string, appendToBody?: boolean): void {
+    const pos = positionService.positionElements(hostElement, targetElement, placement, appendToBody);
+
+    targetElement.style.top = `${pos.top}px`;
+    targetElement.style.left = `${pos.left}px`;
+}
+
+export function getPlacement(hostElement: HTMLElement, targetElement: HTMLElement, placement: string): any {
+    const placementPrimary = positionService.getPlacementPrimary(hostElement, targetElement, placement);
+       console.log(placementPrimary);
+       return placementPrimary;
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/util/triggers.ts b/sdc-workflow-designer-ui/src/app/paletx/util/triggers.ts
new file mode 100644 (file)
index 0000000..708790a
--- /dev/null
@@ -0,0 +1,66 @@
+export class Trigger {
+       constructor(public open: string, public close?: string) {
+               if (!close) {
+                       this.close = open;
+               }
+       }
+
+       public isManual() {
+               return this.open === 'manual' || this.close === 'manual';
+       }
+}
+
+const DEFAULT_ALIASES = {
+       hover: ['mouseenter', 'mouseleave']
+};
+
+export function parseTriggers(triggers: string, aliases = DEFAULT_ALIASES): Trigger[] {
+       const trimmedTriggers = (triggers || '').trim();
+
+       if (trimmedTriggers.length === 0) {
+               return [];
+       }
+
+       const parsedTriggers = trimmedTriggers.split(/\s+/).map(trigger => trigger.split(':')).map((triggerPair) => {
+               let alias = aliases[triggerPair[0]] || triggerPair;
+               return new Trigger(alias[0], alias[1]);
+       });
+
+       const manualTriggers = parsedTriggers.filter(triggerPair => triggerPair.isManual());
+
+       if (manualTriggers.length > 1) {
+               throw 'Triggers parse error: only one manual trigger is allowed';
+       }
+
+       if (manualTriggers.length === 1 && parsedTriggers.length > 1) {
+               throw 'Triggers parse error: manual trigger can\'t be mixed with other triggers';
+       }
+
+       return parsedTriggers;
+}
+
+const noopFn = () => {
+    // TO DO
+};
+
+export function listenToTriggers(renderer: any, nativeElement: any, triggers: string, openFn, closeFn, toggleFn) {
+       const parsedTriggers = parseTriggers(triggers);
+       const listeners = [];
+
+       if (parsedTriggers.length === 1 && parsedTriggers[0].isManual()) {
+               return noopFn;
+       }
+
+       parsedTriggers.forEach((trigger: Trigger) => {
+               if (trigger.open === trigger.close) {
+                       listeners.push(renderer.listen(nativeElement, trigger.open, toggleFn));
+               } else {
+                       listeners.push(
+                               renderer.listen(nativeElement, trigger.open, openFn), renderer.listen(nativeElement, trigger.close, closeFn));
+               }
+       });
+
+       return () => {
+               listeners.forEach(unsubscribeFn => unsubscribeFn());
+       };
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/util/util.ts b/sdc-workflow-designer-ui/src/app/paletx/util/util.ts
new file mode 100644 (file)
index 0000000..5df0381
--- /dev/null
@@ -0,0 +1,65 @@
+export function toInteger(value: any): number {
+       return parseInt(`${value}`, 10);
+}
+
+export function toString(value: any): string {
+       return (value !== undefined && value !== null) ? `${value}` : '';
+}
+
+export function getValueInRange(value: number, max: number, min = 0): number {
+       return Math.max(Math.min(value, max), min);
+}
+
+export function isString(value: any): boolean {
+       return typeof value === 'string';
+}
+
+export function isNumber(value: any): boolean {
+       return !isNaN(toInteger(value));
+}
+
+export function isInteger(value: any): boolean {
+       return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
+}
+
+export function isDefined(value: any): boolean {
+       return value !== undefined && value !== null;
+}
+
+export function padNumber(value: number) {
+       if (isNumber(value)) {
+               return `0${value}`.slice(-2);
+       } else {
+               return '';
+       }
+}
+
+export function regExpEscape(text) {
+       return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+}
+
+
+export function parseDate(date: Date, format) {
+       let o = {
+               'M+': date.getMonth() + 1, // month
+               'd+': date.getDate(),    // day
+               'h+': date.getHours(),   // hour
+               'm+': date.getMinutes(), // minute
+               's+': date.getSeconds(), // second
+               'q+': Math.floor((date.getMonth() + 3) / 3),  // quarter
+               'S': date.getMilliseconds() // millisecond
+       };
+       if (/(y+)/.test(format)) {
+               format = format.replace(RegExp.$1,
+                       (date.getFullYear() + '').substr(4 - RegExp.$1.length));
+       }
+       for (let k in o) {
+               if (new RegExp('(' + k + ')').test(format)) {
+                       format = format.replace(RegExp.$1,
+                               RegExp.$1.length === 1 ? o[k] :
+                                       ('00' + o[k]).substr(('' + o[k]).length));
+               }
+       }
+       return format;
+}
+