aboutsummaryrefslogtreecommitdiffstats
path: root/catalog-ui/src/app/ng2/components
diff options
context:
space:
mode:
Diffstat (limited to 'catalog-ui/src/app/ng2/components')
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less3
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts117
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts34
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html1
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less2
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts27
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html3
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less11
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts33
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html13
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less17
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts22
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html26
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less36
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts39
-rw-r--r--catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts34
-rw-r--r--catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html26
-rw-r--r--catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less38
-rw-r--r--catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts77
-rw-r--r--catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts12
-rw-r--r--catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html13
-rw-r--r--catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less51
-rw-r--r--catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts28
-rw-r--r--catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html37
-rw-r--r--catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts33
-rw-r--r--catalog-ui/src/app/ng2/components/popover/popover-content.component.html24
-rw-r--r--catalog-ui/src/app/ng2/components/popover/popover-content.component.less73
-rw-r--r--catalog-ui/src/app/ng2/components/popover/popover-content.component.ts258
-rw-r--r--catalog-ui/src/app/ng2/components/popover/popover.component.ts159
-rw-r--r--catalog-ui/src/app/ng2/components/popover/popover.module.ts27
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.html24
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.less35
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.ts46
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html66
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less48
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts149
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.html33
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.less3
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.ts85
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.html38
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.ts121
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html135
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less188
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts93
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.html41
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.less71
-rw-r--r--catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.ts37
-rw-r--r--catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html12
-rw-r--r--catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less11
-rw-r--r--catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts195
-rw-r--r--catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts81
-rw-r--r--catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts25
52 files changed, 2811 insertions, 0 deletions
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less
new file mode 100644
index 0000000000..e219d49aa4
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less
@@ -0,0 +1,3 @@
+dynamic-element {
+
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts
new file mode 100644
index 0000000000..de5965e488
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts
@@ -0,0 +1,117 @@
+import { Component, Compiler, EventEmitter, ViewContainerRef, ViewChild, Input, Output, ElementRef, ComponentRef, ComponentFactory, ComponentFactoryResolver } from '@angular/core'
+import { UiElementCheckBoxComponent } from './elements-ui/checkbox/ui-element-checkbox.component';
+import { UiElementDropDownComponent, DropdownValue } from './elements-ui/dropdown/ui-element-dropdown.component';
+import { UiElementInputComponent } from './elements-ui/input/ui-element-input.component';
+import {UiElementPopoverInputComponent} from "./elements-ui/popover-input/ui-element-popover-input.component";
+import {ValidationConfiguration} from "app/models";
+
+@Component({
+ selector: 'dynamic-element',
+ template: `<div #target></div>`,
+ styleUrls: ['./dynamic-element.component.less'],
+ entryComponents: [
+ UiElementInputComponent,
+ UiElementDropDownComponent,
+ UiElementCheckBoxComponent,
+ UiElementPopoverInputComponent
+ ]
+})
+export class DynamicElementComponent {
+
+ @ViewChild('target', { read: ViewContainerRef }) target: any;
+ @Input() type: any;
+ @Input() name: string;
+ value:any;
+
+ // Two way binding for value (need to write the "Change" word like this)
+ @Output('valueChange') emitter: EventEmitter<string> = new EventEmitter<any>();
+ @Input('value') set setValueValue(value) {
+ this.value = value;
+ }
+
+ cmpRef: ComponentRef<any>;
+ private isViewInitialized: boolean = false;
+ validation = ValidationConfiguration.validation;
+
+ constructor(
+ private componentFactoryResolver: ComponentFactoryResolver,
+ private compiler: Compiler,
+ private el: ElementRef) {
+ }
+
+ updateComponent() {
+ if (!this.isViewInitialized) {
+ return;
+ }
+ if (this.cmpRef) {
+ this.cmpRef.destroy();
+ }
+
+ // Factory to create component based on type or peroperty name.
+ switch(this.type) {
+ case 'list':
+ case 'integer':
+ this.createComponent(UiElementInputComponent);
+ this.cmpRef.instance.pattern = this.validation.validationPatterns.integer;
+ break;
+ case 'string':
+ switch(this.name.toUpperCase()) {
+ case 'SUBNETPOOLID':
+ this.createComponent(UiElementPopoverInputComponent);
+ break;
+ default:
+ this.createComponent(UiElementInputComponent);
+ }
+ break;
+ case 'boolean':
+ //this.createComponent(UiElementCheckBoxComponent);
+
+ this.createComponent(UiElementDropDownComponent);
+
+ // Build drop down values
+ let tmp = [];
+ tmp.push(new DropdownValue('true','TRUE'));
+ tmp.push(new DropdownValue('false','FALSE'));
+ this.cmpRef.instance.values = tmp;
+ break;
+ default:
+ this.createComponent(UiElementInputComponent);
+ console.log("ERROR: No ui component to handle type: " + this.type);
+ }
+
+ // Additional attributes in base element class
+ if (this.cmpRef) {
+ this.cmpRef.instance.name = this.name;
+ this.cmpRef.instance.type = this.type;
+ this.cmpRef.instance.value = this.value;
+ }
+
+ // Subscribe to change event of of ui-element-basic and fire event to change the value
+ this.cmpRef.instance.baseEmitter.subscribe((value):void => {
+ this.emitter.emit(value)
+ });
+
+ }
+
+ createComponent(ComponentToCreate:any):void {
+ let factory = this.componentFactoryResolver.resolveComponentFactory(ComponentToCreate);
+ this.cmpRef = this.target.createComponent(factory);
+ }
+
+ ngOnChanges() {
+ this.updateComponent();
+ }
+
+ ngAfterContentInit() {
+ //console.log("DynamicElementComponent: ngAfterContentInit: type: " + this.type + " value: " + this.value);
+ this.isViewInitialized = true;
+ this.updateComponent();
+ }
+
+ ngOnDestroy() {
+ if (this.cmpRef) {
+ this.cmpRef.destroy();
+ }
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts
new file mode 100644
index 0000000000..18b044bc9d
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts
@@ -0,0 +1,34 @@
+import { NgModule } from "@angular/core";
+import { UiElementCheckBoxComponent } from './elements-ui/checkbox/ui-element-checkbox.component';
+import { UiElementDropDownComponent } from './elements-ui/dropdown/ui-element-dropdown.component';
+import { UiElementInputComponent } from './elements-ui/input/ui-element-input.component';
+import { DynamicElementComponent } from "app/ng2/components/dynamic-element/dynamic-element.component";
+import { BrowserModule } from '@angular/platform-browser'
+import { FormsModule, ReactiveFormsModule } from '@angular/forms'
+import { UiElementPopoverInputComponent } from "./elements-ui/popover-input/ui-element-popover-input.component";
+import {PopoverModule} from "../popover/popover.module";
+import {TooltipModule} from "../tooltip/tooltip.module";
+
+@NgModule({
+ declarations: [
+ DynamicElementComponent,
+ UiElementInputComponent,
+ UiElementCheckBoxComponent,
+ UiElementDropDownComponent,
+ UiElementPopoverInputComponent
+ ],
+ imports: [
+ BrowserModule,
+ FormsModule,
+ PopoverModule,
+ ReactiveFormsModule,
+ TooltipModule
+ ],
+ exports: [
+ DynamicElementComponent
+ ],
+ providers: []
+})
+export class DynamicElementModule {
+
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html
new file mode 100644
index 0000000000..2ad3d8e94a
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html
@@ -0,0 +1 @@
+<input #{{name}} [(ngModel)]="value" type="checkbox" (change)="onSave(value)" />
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less
new file mode 100644
index 0000000000..bed097fe5e
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less
@@ -0,0 +1,2 @@
+/deep/ ui-element-checkbox {
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts
new file mode 100644
index 0000000000..152303aee7
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts
@@ -0,0 +1,27 @@
+import { Component, ViewChild, ElementRef, ContentChildren, Input } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser'
+import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component';
+
+@Component({
+ selector: 'ui-element-checkbox',
+ templateUrl: './ui-element-checkbox.component.html',
+ styleUrls: ['./ui-element-checkbox.component.less'],
+})
+export class UiElementCheckBoxComponent extends UiElementBase implements UiElementBaseInterface {
+
+ constructor() {
+ super();
+ }
+
+ ngAfterContentInit() {
+ // Convert the value to boolean (instanceOf does not work, the type is undefined).
+ if (this.value==='true' || this.value==='false') {
+ this.value = this.value==='true'?true:false;
+ }
+ }
+
+ onSave() {
+ this.baseEmitter.emit(this.value);
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html
new file mode 100644
index 0000000000..589d00e42d
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html
@@ -0,0 +1,3 @@
+<select name='{{name}}' [(ngModel)]="value" #t (change)="onSave()">
+ <option *ngFor="let ddvalue of values" [value]="ddvalue.value">{{ddvalue.label}}</option>
+</select>
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less
new file mode 100644
index 0000000000..ea3e35140e
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less
@@ -0,0 +1,11 @@
+@import '../../../../../../assets/styles/variables';
+
+/deep/ ui-element-dropdown {
+
+ select {
+ text-indent: 6px;
+ border: solid 1px @main_color_o;
+ width: 100%;
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts
new file mode 100644
index 0000000000..208bf54983
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts
@@ -0,0 +1,33 @@
+import { Component, EventEmitter, Output, Input } from '@angular/core'
+import { BrowserModule } from '@angular/platform-browser'
+import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component';
+
+export class DropdownValue {
+ value:string;
+ label:string;
+
+ constructor(value:string,label:string) {
+ this.value = value;
+ this.label = label;
+ }
+}
+
+@Component({
+ selector: 'ui-element-dropdown',
+ templateUrl: './ui-element-dropdown.component.html',
+ styleUrls: ['./ui-element-dropdown.component.less'],
+})
+export class UiElementDropDownComponent extends UiElementBase implements UiElementBaseInterface {
+
+ @Input()
+ values: DropdownValue[];
+
+ constructor() {
+ super();
+ }
+
+ onSave() {
+ this.baseEmitter.emit(this.value);
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html
new file mode 100644
index 0000000000..00fea65b72
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html
@@ -0,0 +1,13 @@
+<input
+ class="value-input"
+ [ngClass]="{'error': control.invalid}"
+ type="text"
+ [name]="name"
+ [(ngModel)]="value"
+ (change)="onSave()"
+ [attr.maxlength]="validation.propertyValue.max"
+ [attr.minlength]="validation.propertyValue.min"
+ [pattern]="pattern"
+ [formControl]="control"
+ tooltip="{{value}}"
+ />
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less
new file mode 100644
index 0000000000..d320c7ff8b
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less
@@ -0,0 +1,17 @@
+@import '../../../../../../assets/styles/variables';
+
+/deep/ ui-element-input {
+
+ input {
+ text-indent: 6px;
+ border: solid 1px @main_color_o;
+ }
+
+ .error {
+ border: solid 1px @func_color_q;
+ color: @func_color_q;
+ outline: none;
+ box-sizing: border-box;
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts
new file mode 100644
index 0000000000..5a14d8f206
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts
@@ -0,0 +1,22 @@
+import { Component, ViewChild, ElementRef, ContentChildren } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser'
+import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component';
+
+@Component({
+ selector: 'ui-element-input',
+ templateUrl: './ui-element-input.component.html',
+ styleUrls: ['./ui-element-input.component.less'],
+})
+export class UiElementInputComponent extends UiElementBase implements UiElementBaseInterface {
+
+ constructor() {
+ super();
+ this.pattern = this.validation.validationPatterns.comment;
+ }
+
+ onSave() {
+ if (!this.control.invalid){
+ this.baseEmitter.emit(this.value);
+ }
+ }
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html
new file mode 100644
index 0000000000..5adceb49a0
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html
@@ -0,0 +1,26 @@
+<div class="popover-input-wrapper" tooltip="{{value}}">
+ <input
+ class="value-input"
+ type="text"
+ [ngClass]="{'error': control.invalid}"
+ [name]="name"
+ [value]="value"
+ disabled
+ />
+ <button [popover]="popoverForm">Edit</button>
+</div>
+
+<popover-content #popoverForm [title]="name" [buttons]="buttonsArray" [placement]="'top'" [closeOnClickOutside]="true">
+ <div class="edit-subnet-wrapper">
+ <textarea rows="5"
+ #textArea
+ class="subnet-value"
+ [ngClass]="{'error': control.invalid}"
+ [(ngModel)]="value"
+ [attr.maxlength]="validation.propertyValue.max"
+ [attr.minlength]="validation.propertyValue.min"
+ [pattern]="pattern"
+ [formControl]="control"
+ ></textarea>
+ </div>
+</popover-content>
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less
new file mode 100644
index 0000000000..5be443f7b6
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less
@@ -0,0 +1,36 @@
+@import '../../../../../../assets/styles/variables';
+
+.popover-input-wrapper {
+ display: flex;
+}
+
+/deep/ ui-element-popover-input {
+
+ .popover {
+ max-width: 350px;
+ width: 350px;
+ }
+
+ .edit-subnet-wrapper {
+ padding: 12px;
+
+ .subnet-value {
+ width: 100%;
+ resize: none;
+ }
+ }
+
+ input {
+ padding-right: 6px;
+ padding-left: 6px;
+ border: solid 1px @main_color_o;
+ }
+
+ .error {
+ border: solid 1px @func_color_q;
+ color: @func_color_q;
+ outline: none;
+ box-sizing: border-box;
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts
new file mode 100644
index 0000000000..7300417686
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts
@@ -0,0 +1,39 @@
+import {Component, ViewChild, ElementRef} from '@angular/core';
+import {UiElementBase, UiElementBaseInterface} from "../ui-element-base.component";
+import {ButtonsModelMap, ButtonModel} from "app/models";
+import { PopoverContentComponent } from "app/ng2/components/popover/popover-content.component"
+import { PopoverComponent } from "app/ng2/components/popover/popover.component"
+
+@Component({
+ selector: 'ui-element-popover-input',
+ templateUrl: './ui-element-popover-input.component.html',
+ styleUrls: ['./ui-element-popover-input.component.less']
+})
+export class UiElementPopoverInputComponent extends UiElementBase implements UiElementBaseInterface {
+
+ @ViewChild('textArea') textArea: ElementRef;
+ @ViewChild('popoverForm') popoverContentComponent: PopoverContentComponent;
+
+ saveButton: ButtonModel;
+ buttonsArray: ButtonsModelMap;
+
+ onSave = ():void => {
+ if (!this.control.invalid){
+ this.baseEmitter.emit(this.value);
+ this.popoverContentComponent.hide();
+ }
+ }
+
+ constructor() {
+ super();
+ // Create Save button and insert to buttons map
+ this.saveButton = new ButtonModel('save', 'blue', this.onSave);
+ this.buttonsArray = { 'test': this.saveButton };
+
+ // Define the regex pattern for this controller
+ this.pattern = this.validation.validationPatterns.comment;
+
+ // Disable / Enable button according to validation
+ //this.control.valueChanges.subscribe(data => this.saveButton.disabled = this.control.invalid);
+ }
+}
diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts
new file mode 100644
index 0000000000..b60271f6f0
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts
@@ -0,0 +1,34 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core'
+import { ValidationConfiguration } from "app/models";
+import { FormControl, Validators } from '@angular/forms';
+
+export interface UiElementBaseInterface {
+ onSave();
+}
+
+@Component({
+ template: ``,
+ styles: []
+})
+export class UiElementBase {
+
+ protected validation = ValidationConfiguration.validation;
+ protected control: FormControl;
+
+ // Two way binding for value (need to write the "Change" word like this)
+ @Output('valueChange') baseEmitter: EventEmitter<string> = new EventEmitter<any>();
+ @Input('value') set setValueValue(value) {
+ this.value = value;
+ }
+
+ protected name: string;
+ protected type: string;
+ protected value: any;
+ protected pattern: any;
+
+ constructor() {
+ //this.control = new FormControl('', [Validators.required]);
+ this.control = new FormControl('', []);
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html
new file mode 100644
index 0000000000..4d2b91f3b4
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html
@@ -0,0 +1,26 @@
+<popover-content #filterPopover [title]="'Filters'" [buttons]="footerButtons" placement="bottom-right" [hideArrow]="true">
+ <!--<form [formGroup]="filterForm">-->
+ <form>
+ <div class="field">
+ <label>Resource Type</label>
+ <div>
+ <checkbox [label]="'All'" [(checked)]="allSelected" (checkedChange)="selectAll()"></checkbox>
+ </div>
+ <div *ngFor="let type of typesOptions">
+ <checkbox [label]="type" [(checked)]="selectedTypes[type]" (checkedChange)="onTypeSelected(type)"></checkbox>
+ </div>
+ </div>
+ <div class="field">
+ <label>Property Name</label>
+ <input class="i-sdc-form-input"
+ name="propertyName"
+ [(ngModel)]="filterData.propertyName"
+ placeholder="Type here"
+ required
+ />
+ </div>
+ </form>
+</popover-content>
+<div class="open-filter-button" [popover]="filterPopover" [ngClass]="{'open':showPopover}" (onShown)="showPopover = true" (onHidden)="showPopover = false">
+ <div class="sprite-new filter-icon"></div>
+</div>
diff --git a/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less
new file mode 100644
index 0000000000..c1341b8dcf
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less
@@ -0,0 +1,38 @@
+@import '../../../../assets/styles/variables';
+form{
+ margin: 0 20px;
+ .field{
+ padding:20px 0;
+ &:not(:last-child){
+ border-bottom: solid 1px @main_color_o;
+ }
+ }
+ /deep/ [ng-reflect-checked="true"]{
+ /deep/ .checkbox-label-content{
+ color: @main_color_a;
+ }
+ }
+}
+
+.open-filter-button{
+ cursor: pointer;
+ width: 25px;
+ height: 34px;
+ display: inline-block;
+ &.open{
+ z-index: 1061;
+ position: relative;
+ top: 2px;
+ background-color: @main_color_p;
+ border: solid 1px @main_color_c;
+ border-bottom: none;
+ }
+ .filter-icon{
+ top: 8px;
+ position: relative;
+ }
+}
+
+/deep/ .popover{
+ border: solid 1px @main_color_c !important;
+}
diff --git a/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts
new file mode 100644
index 0000000000..c23e08bc0d
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts
@@ -0,0 +1,77 @@
+/**
+ * Created by rc2122 on 5/16/2017.
+ */
+import {Component, Input, Output, EventEmitter, ViewChild} from '@angular/core';
+import {ButtonModel, ButtonsModelMap, FilterPropertiesAssignmentData} from "app/models";
+import {PopoverComponent} from "../popover/popover.component";
+import * as sdcConfig from "../../../../../configurations/dev"
+
+@Component({
+ selector: 'filter-properties-assignment',
+ templateUrl: './filter-properties-assignment.component.html',
+ styleUrls: ['./filter-properties-assignment.component.less']
+})
+
+export class FilterPropertiesAssignmentComponent {
+ @Input() componentType: string;
+ @Output() searchProperties: EventEmitter<FilterPropertiesAssignmentData> = new EventEmitter<FilterPropertiesAssignmentData>();
+ footerButtons:ButtonsModelMap = {};
+ typesOptions:Array<string>;//All optional types
+ selectedTypes:Object = {};
+ allSelected:boolean = false;//if all option selected
+ filterData:FilterPropertiesAssignmentData = new FilterPropertiesAssignmentData();
+ @ViewChild('filterPopover') filterPopover: PopoverComponent;
+
+ ngOnInit() {
+ this.footerButtons['Apply'] = new ButtonModel('Apply', 'blue', this.search, this.someTypesSelectedAndThereIsPropertyName);
+ this.footerButtons['Close'] = new ButtonModel('Close', 'grey', this.close);
+ this.componentType = this.componentType.toLocaleLowerCase();
+ this.typesOptions = sdcConfig.resourceTypesFilter[this.componentType];
+ }
+
+ selectAll = () => {
+ _.forEach(this.typesOptions, (type) => {
+ this.selectedTypes[type] = this.allSelected;
+ });
+ }
+
+ onTypeSelected = (type:string) => {
+ if(!this.selectedTypes[type]){
+ this.allSelected = false;//unselected 'All'
+ }
+ };
+
+ search = () => {
+ console.log('search props');
+ this.filterData.selectedTypes = [];
+ _.forEach(sdcConfig.resourceTypesFilter[this.componentType], (type) => {
+ if(this.selectedTypes[type]){
+ this.filterData.selectedTypes.push(type);
+ }
+ });
+ this.searchProperties.emit(this.filterData);
+ this.filterPopover.hide();
+ }
+
+ close = () => {
+ this.filterPopover.hide();
+ }
+
+ someTypesSelectedAndThereIsPropertyName = ():boolean => {
+ if( _.find(Object.keys(this.selectedTypes),(key) => {
+ return this.selectedTypes[key];
+ }) && this.filterData.propertyName ){
+ return null
+ }
+ return true;
+ }
+
+ clearAll = ():void => {
+ this.filterData.propertyName = "";
+ _.forEach(this.selectedTypes,(value, key) => {
+ this.selectedTypes[key] = false;
+ });
+ this.allSelected = false;
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts
new file mode 100644
index 0000000000..7045286ccd
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts
@@ -0,0 +1,12 @@
+export class HierarchyDisplayOptions {
+ idProperty: string;
+ valueProperty: string;
+ childrenProperty: string;
+ searchText:string;
+ constructor(idProperty:string, valueProperty:string, childrenProperty?:string, searchText?:string) {
+ this.idProperty = idProperty;
+ this.valueProperty = valueProperty;
+ this.childrenProperty = childrenProperty;
+ this.searchText = searchText;
+ }
+}
diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html
new file mode 100644
index 0000000000..40a1c37cee
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html
@@ -0,0 +1,13 @@
+<div class="navigation-wrapper">
+ <div class="node-item" *ngFor="let item of displayData" (click)="onClick($event, item)">
+ <div class="node-data-wrapper" [ngClass]="{'selected': selectedItem && selectedItem === item[displayOptions.idProperty]}">
+ <span class="node-data" [ngClass]="{'mark':item[displayOptions.valueProperty] === displayOptions.searchText}">{{item[displayOptions.valueProperty]}}</span>
+ </div>
+ <div class="children-node" *ngIf="item[displayOptions.childrenProperty]">
+ <hierarchy-navigation class="children-hierarchy" [displayData]="item[displayOptions.childrenProperty]"
+ [selectedItem]="selectedItem"
+ [displayOptions]="displayOptions"
+ (updateSelected)="onSelectedUpdate($event)"></hierarchy-navigation>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less
new file mode 100644
index 0000000000..57d51616a8
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less
@@ -0,0 +1,51 @@
+.navigation-wrapper {
+ text-align: left;
+}
+
+.node-item {
+ border: 1px dotted;
+ border-right: none;
+ border-bottom: none;
+}
+
+.node-item:last-child {
+ border-left: none;
+}
+
+.node-data-wrapper {
+ cursor: default;
+ height: 30px;
+ line-height: 2.6em;;
+ position: relative;
+ top: -1.1em;
+ background-color: white;
+ margin-left: 0.7em;
+}
+
+.children-node {
+ padding-left: 40px;
+}
+
+.node-data {
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+.node-data-wrapper.selected {
+ background-color: #e6f6fb;
+
+ .node-data {
+ color: #009fdb;
+ }
+}
+
+.node-data-wrapper:hover {
+ background-color: #eaeaea;
+}
+
+.mark{
+ background-color: yellow;
+}
+
+
+
diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts
new file mode 100644
index 0000000000..428bbb4b04
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts
@@ -0,0 +1,28 @@
+import {Component, Input, Output, EventEmitter} from '@angular/core';
+import {HierarchyDisplayOptions} from './hierarchy-display-options';
+
+
+@Component({
+ selector: 'hierarchy-navigation',
+ templateUrl: './hierarchy-navigation.component.html',
+ styleUrls: ['./hierarchy-navigation.component.less']
+})
+
+export class HierarchyNavigationComponent {
+ @Input() displayData: Array<any>;
+ @Input() selectedItem: any;
+ @Input() displayOptions: HierarchyDisplayOptions;
+
+ @Output() updateSelected:EventEmitter<any> = new EventEmitter();
+
+ onClick = ($event, item) => {
+ $event.stopPropagation();
+ this.selectedItem = item;
+ this.updateSelected.emit(item);
+ };
+
+ onSelectedUpdate = ($event) => {
+ this.selectedItem = $event;
+ this.updateSelected.emit($event);
+ }
+}
diff --git a/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html
new file mode 100644
index 0000000000..e7801b82cf
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html
@@ -0,0 +1,37 @@
+<div class="properties-table">
+ <div class="table-header">
+ <div class="table-cell col1">Property Name</div>
+ <div class="table-cell col2">Type</div>
+ <div class="table-cell col3">ES</div>
+ <div class="table-cell valueCol">Value</div>
+ </div>
+ <div class="table-body">
+ <div class="no-data" *ngIf="!inputs || !inputs.length">No data to display</div>
+ <div class="table-row" *ngFor="let input of inputs">
+ <div class="table-cell col1">
+ <span tooltip="{{input.name}}" >{{input.name}}</span>
+ <span *ngIf="input.description"
+ class="property-description-icon sprite-new show-desc"
+ tooltip="{{input.description}}"></span>
+ </div>
+ <div class="table-cell col2">{{input.type | contentAfterLastDot}}</div>
+ <div class="table-cell col3">{{input.schema && input.schema.property && input.schema.property.type ? (input.schema.property.type | contentAfterLastDot) : ''}}</div>
+ <div class="table-cell valueCol input-value-col" [class.inner-table-container]="input.childrenProperties || !input.isSimpleType">
+ <dynamic-element class="value-input"
+ *ngIf="input.isSimpleType"
+ pattern="validationUtils.getValidationPattern(input.type)"
+ [(value)]="input.defaultValue"
+ [type]="input.type"
+ [name]="input.name"
+ (change)="onInputValueChanged(input);">
+ </dynamic-element>
+ <div class="delete-button-container">
+ <span class="sprite-new delete-btn" (click)="onDeleteInput(input)"></span>
+ </div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+
diff --git a/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts
new file mode 100644
index 0000000000..ea01edf043
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts
@@ -0,0 +1,33 @@
+/**
+ * Created by rc2122 on 5/4/2017.
+ */
+import {Component, Input, Output, EventEmitter} from "@angular/core";
+import {InputFEModel} from "app/models";
+
+@Component({
+ selector: 'inputs-table',
+ templateUrl: './inputs-table.component.html',
+ styleUrls: ['../properties-table/properties-table.component.less']
+})
+export class InputsTableComponent {
+
+ @Input() inputs: Array<InputFEModel>;
+
+ @Output() inputValueChanged: EventEmitter<any> = new EventEmitter<any>();
+ @Output() deleteInput: EventEmitter<any> = new EventEmitter<any>();
+
+ constructor (){
+ }
+
+ onInputValueChanged = (input) => {
+ this.inputValueChanged.emit(input);
+ };
+
+ onDeleteInput = (input) => {
+ this.deleteInput.emit(input);
+ }
+
+
+}
+
+
diff --git a/catalog-ui/src/app/ng2/components/popover/popover-content.component.html b/catalog-ui/src/app/ng2/components/popover/popover-content.component.html
new file mode 100644
index 0000000000..6d76f0ad06
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/popover/popover-content.component.html
@@ -0,0 +1,24 @@
+<div #popoverDiv class="popover {{ effectivePlacement }}"
+ [style.top]="top + 'px'"
+ [style.left]="left + 'px'"
+ [class.in]="isIn"
+ [class.fade]="animation"
+ style="display: block"
+ role="popover"
+ [ngClass]="{'hide-arrow':hideArrow}">
+ <div [hidden]="!closeOnMouseOutside" class="virtual-area"></div>
+ <div class="arrow" *ngIf="!hideArrow"></div>
+ <div class="popover-header">
+ <span class="title">{{ title }}</span>
+ <span class="close-button" (click)="popover.hide()"></span>
+ </div>
+ <ng-content></ng-content>
+ <div class="popover-footer">
+ <button *ngFor="let buttonName of buttonsNames"
+ class="tlv-btn {{buttons[buttonName].cssClass}}"
+ [disabled] = "buttons[buttonName].getDisabled && buttons[buttonName].getDisabled()"
+ (click) = "buttons[buttonName].callback()">{{buttons[buttonName].text}}</button>
+ </div>
+</div>
+
+
diff --git a/catalog-ui/src/app/ng2/components/popover/popover-content.component.less b/catalog-ui/src/app/ng2/components/popover/popover-content.component.less
new file mode 100644
index 0000000000..f7b62e91f7
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/popover/popover-content.component.less
@@ -0,0 +1,73 @@
+@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;
+ .title{
+ -webkit-box-flex: 999;
+ -ms-flex-positive: 999;
+ flex-grow: 999;
+ }
+ .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/popover/popover-content.component.ts b/catalog-ui/src/app/ng2/components/popover/popover-content.component.ts
new file mode 100644
index 0000000000..c4489f59b9
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/popover/popover-content.component.ts
@@ -0,0 +1,258 @@
+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<string>;
+
+ 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/popover/popover.component.ts b/catalog-ui/src/app/ng2/components/popover/popover.component.ts
new file mode 100644
index 0000000000..a7e2881b29
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/popover/popover.component.ts
@@ -0,0 +1,159 @@
+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<PopoverContentComponent>;
+ 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<PopoverComponent>();
+
+ @Output()
+ onHidden = new EventEmitter<PopoverComponent>();
+
+ @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/popover/popover.module.ts b/catalog-ui/src/app/ng2/components/popover/popover.module.ts
new file mode 100644
index 0000000000..4bd8426ce1
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/popover/popover.module.ts
@@ -0,0 +1,27 @@
+/**
+ * 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: [
+ // PopoverComponent,
+ // PopoverContentComponent
+ CommonModule
+ ],
+ exports: [
+ PopoverComponent,
+ PopoverContentComponent
+ ],
+ providers: []
+})
+export class PopoverModule {
+
+}
diff --git a/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.html b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.html
new file mode 100644
index 0000000000..6e7d4e882b
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.html
@@ -0,0 +1,24 @@
+<!-- TODO: refactor this to be dynamic -->
+<div class="derived-property-row" *ngIf="propertyObj.isDeclared || propertyObj.derivedDataType == derivedPropertyTypes.SIMPLE">
+ <div class="table-cell" [ngClass]="{'iltered':propertyObj.name === propertyNameSearchText}"><checkbox [label]="propertyObj.name" [(checked)]="propertyObj.isSelected" [disabled]="propertyObj.isDisabled ||propertyObj.isDeclared" (checkedChange)="checkedChange($event)"></checkbox></div>
+
+ <div class="table-cell simple">
+ <input type="text" [(ngModel)]="propertyObj.value" [disabled]="propertyObj.isDisabled || propertyObj.isDeclared" (change)="valueChanged.emit()" />
+ </div>
+</div>
+<div class="derived-property-row" *ngIf="!propertyObj.isDeclared && propertyObj.derivedDataType == derivedPropertyTypes.COMPLEX">
+ <div class="table-cell" [ngClass]="{'filtered':propertyObj.name === propertyNameSearchText}"><checkbox [label]="propertyObj.name" [(checked)]="propertyObj.isSelected" [disabled]="propertyObj.isDisabled ||propertyObj.isDeclared" (checkedChange)="checkedChange($event)"></checkbox></div>
+ <div class="table-cell complex">
+ <span class="datatype-text">{{propertyObj.type | contentAfterLastDot }}</span>
+ <span (click)="expandChildById(propertyObj.propertiesName)" class="expand-icon" [class.expanded]="expanded">V</span>
+ </div>
+</div>
+<div class="derived-property-row" *ngIf="!propertyObj.isDeclared && propertyObj.derivedDataType == derivedPropertyTypes.SIMPLELIST ||propertyObj.derivedDataType == derivedPropertyTypes.SIMPLEMAP">
+ <div class="table-cell">
+ <span [ngClass]="{'filtered':propertyObj.name === propertyNameSearchText}">{{propertyObj.name}}</span>
+ </div>
+ <div class="table-cell simple2">
+ <div class="sprite-new add-item-icon" (click)="addRows(propertyObj)"></div>
+ </div>
+</div>
+
diff --git a/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.less b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.less
new file mode 100644
index 0000000000..3102c5ceb8
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.less
@@ -0,0 +1,35 @@
+.derived-property-row {
+ display:flex;
+ flex-direction:row;
+ position:relative;
+ border-top: #d2d2d2 solid 1px;
+
+ &:first-child {
+ border-top:none;
+ }
+ .table-cell {
+ flex: 0 0 50%;
+ padding:5px;
+ position:relative;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &:first-child {
+ border-right:#d2d2d2 solid 1px;
+ overflow:hidden;
+ }
+ span.expand-icon {
+ position: absolute;
+ right: 10px;
+ transition: 200ms transform ease-in-out;
+ }
+ span.expand-icon.expanded {
+ transform: rotate(-180deg);
+ }
+ }
+}
+.filtered {
+ /deep/ .checkbox-label-content{
+ background-color: yellow;
+ }
+}
diff --git a/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.ts b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.ts
new file mode 100644
index 0000000000..16f069a685
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.ts
@@ -0,0 +1,46 @@
+/**
+ * Created by rc2122 on 4/20/2017.
+ */
+import {Component, Input, Output, EventEmitter} from "@angular/core";
+import { DerivedFEProperty, DerivedPropertyType} from "app/models";
+import {PropertiesService} from "../../../services/properties.service";
+
+@Component({
+ selector: 'derived-property',
+ templateUrl: './derived-property.component.html',
+ styleUrls: ['./derived-property.component.less']
+})
+export class DerivedPropertyComponent {
+
+ derivedPropertyTypes = DerivedPropertyType; //http://stackoverflow.com/questions/35835984/how-to-use-a-typescript-enum-value-in-an-angular2-ngswitch-statement
+
+ @Input() propertyObj: DerivedFEProperty;
+ @Input() propertyNameSearchText: string;
+ @Input() expanded: boolean;
+ @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>();
+ @Output() expandChild: EventEmitter<string> = new EventEmitter<string>();
+ @Output() selectProperty: EventEmitter<boolean> = new EventEmitter<boolean>();
+
+
+ constructor ( private propertiesService:PropertiesService){
+ }
+
+
+ propValueChanged = () => {
+ this.valueChanged.emit(this.propertyObj);
+ };
+
+ expandChildById = (id: string) => {
+ this.expandChild.emit(id);
+ }
+
+ checkedChange = (isChecked:boolean) => {
+ this.selectProperty.emit(isChecked);
+ }
+
+ addRows = (flatProperty: DerivedFEProperty): void => {
+ console.log("ADDING A ROW OF TYPE " + flatProperty.type);
+ console.log(flatProperty);
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html
new file mode 100644
index 0000000000..17e4002ae5
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html
@@ -0,0 +1,66 @@
+<div class="dynamic-property-row"
+ [ngClass]="{'selected': selectedPropertyId === property.uniqueId }"
+ [class.with-top-border]="property.isChildOfListOrMap"
+ (click)="onClickPropertyRow(property, $event)">
+ <!-- LEFT CELL - for all NON-PropFEModel properties except for MAP)-->
+ <ng-container *ngIf="!isPropertyFEModel">
+ <div class="table-cell" *ngIf="canBeDeclared"> <!-- simple children of complex type -->
+ <checkbox [label]="property.name" [(checked)]="property.isSelected" [disabled]="property.isDisabled ||property.isDeclared" (checkedChange)="checkProperty.emit(property.propertiesName)"
+ ></checkbox>
+ </div>
+ <div class="table-cell" *ngIf="!canBeDeclared && !property.isChildOfListOrMap">{{property.name}}</div> <!-- simple children of complex type within map or list -->
+ </ng-container>
+ <!-- RIGHT CELL -->
+ <ng-container *ngIf="propType == derivedPropertyTypes.SIMPLE || property.isDeclared">
+ <div class="table-cell" [class.full-width]="isPropertyFEModel || property.isChildOfListOrMap">
+ <dynamic-element class="value-input"
+ pattern="validationUtils.getValidationPattern(property.type)"
+ [(value)]="property.value"
+ [type]="property.type"
+ [name]="property.name"
+ (valueChange)="propValueChanged(property);"
+ > <!--[disabled]="property.isDisabled || property.isDeclared" -->
+ </dynamic-element>
+ </div>
+ </ng-container>
+ <ng-container *ngIf="!property.isDeclared"> <!-- other cases need to fail if isDeclared, in which case we need to use full width value above instead) -->
+
+ <ng-container *ngIf="propType == derivedPropertyTypes.COMPLEX && !isPropertyFEModel">
+ <div class="table-cell complex" [class.full-width]="property.isChildOfListOrMap">
+ <span>{{property.type | contentAfterLastDot }}</span>
+ </div>
+ </ng-container>
+ <ng-container *ngIf="property.isChildOfListOrMap && propType == derivedPropertyTypes.MAP">
+ <div class="table-cell map-entry"><input type="text" /></div>
+ <div class="table-cell" *ngIf="property.schema.property.isSimpleType">
+ <input type="text" [(ngModel)]="property.value" /></div>
+ <div class="table-cell" *ngIf="!property.schema.property.isSimpleType">
+ <span>{{property.schema.property.type | contentAfterLastDot }}</span>
+ </div>
+ </ng-container>
+
+ <!-- ICONS: add, delete, and expand -->
+ <div *ngIf="(propType == derivedPropertyTypes.LIST || propType == derivedPropertyTypes.MAP) && !property.isChildOfListOrMap" class="table-cell"><span class="sprite-new add-item-icon" (click)="addRows()"></span></div>
+ <span *ngIf="property.isChildOfListOrMap" class="delete-span sprite-new delete-item-icon" (click)="deleteItem.emit(property.propertiesName)"></span>
+ <span *ngIf="propType == derivedPropertyTypes.COMPLEX || (property.isChildOfListOrMap && propChildren)" (click)="expandChildById(propPath)" class="expand-icon" [class.expanded]="propPath == expandedChildId">V</span>
+ </ng-container>
+
+</div>
+<!-- Now, recurse into children -->
+<ng-container *ngIf="propChildren && !property.isDeclared">
+ <ng-container *ngFor="let prop of propChildren | filterChildProperties: expandedChildId; trackBy:prop?.propertiesName">
+ <dynamic-property
+ [selectedPropertyId]="selectedPropertyId"
+ [canBeDeclared]="childrenCanBeDeclared"
+ [property]="prop"
+ [expandedChildId]="expandedChildId"
+ [propChildren]=""
+ (valueChanged)="propValueChanged(prop);"
+ (expandChild)="expandChildById($event)"
+ (deleteItem)="deleteListOrMapItem($event)"
+ (clickOnPropertyRow)="onClickPropertyRow($event)"
+ (checkProperty)="checkedChange($event)"
+ >
+ </dynamic-property>
+ </ng-container>
+</ng-container>
diff --git a/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less
new file mode 100644
index 0000000000..53cde3035a
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less
@@ -0,0 +1,48 @@
+.dynamic-property-row {
+ display:flex;
+ flex-direction:row;
+ position:relative;
+
+ .table-cell {
+ flex: 1 0 50%;
+ padding:5px;
+ position:relative;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+
+ &.full-width {
+ border-right:none;
+ flex: 0 0 100%;
+ }
+
+ &:first-child:not(:only-child) {
+ border-right:#d2d2d2 solid 1px;
+ overflow:hidden;
+ }
+
+
+ }
+ span.delete-item-icon{
+ position: absolute;
+ right: 25px;
+ top: 10px;
+ }
+ span.add-item-icon {
+ float:right;
+ }
+ span.expand-icon {
+ position: absolute;
+ top:6px;
+ right: 10px;
+ transition: 200ms transform ease-in-out;
+ }
+ span.expand-icon.expanded {
+ transform: rotate(-180deg);
+ }
+}
+.filtered {
+ /deep/ .checkbox-label-content{
+ background-color: yellow;
+ }
+}
diff --git a/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts
new file mode 100644
index 0000000000..0ca93a773f
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts
@@ -0,0 +1,149 @@
+import {Component, Input, Output, EventEmitter} from "@angular/core";
+import { PropertyBEModel, PropertyFEModel, DerivedFEProperty, DerivedPropertyType, SchemaPropertyGroupModel, DataTypeModel } from "app/models";
+import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils';
+import { PropertiesUtils } from "app/ng2/pages/properties-assignment/properties.utils";
+import { DataTypeService } from "../../../services/data-type.service";
+import { UUID } from "angular2-uuid";
+
+
+@Component({
+ selector: 'dynamic-property',
+ templateUrl: './dynamic-property.component.html',
+ styleUrls: ['./dynamic-property.component.less']
+})
+export class DynamicPropertyComponent {
+
+ derivedPropertyTypes = DerivedPropertyType; //http://stackoverflow.com/questions/35835984/how-to-use-a-typescript-enum-value-in-an-angular2-ngswitch-statement
+ propType: DerivedPropertyType;
+ propPath: string;
+ isPropertyFEModel: boolean;
+ mapOfIDsAndKeys: Map<string, string> = new Map(); //used for map and list
+
+ childrenCanBeDeclared: boolean;
+ @Input() canBeDeclared: boolean;
+ @Input() property: PropertyFEModel | DerivedFEProperty;
+ @Input() propChildren: Array<DerivedFEProperty>;
+ @Input() expandedChildId: string;
+ @Input() selectedPropertyId: string;
+
+ @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>();
+ @Output() expandChild: EventEmitter<string> = new EventEmitter<string>();
+ @Output() checkProperty: EventEmitter<string> = new EventEmitter<string>();
+ @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>();
+ @Output() clickOnPropertyRow: EventEmitter<PropertyFEModel | DerivedFEProperty> = new EventEmitter<PropertyFEModel | DerivedFEProperty>();
+
+ constructor(private propertiesUtils: PropertiesUtils, private dataTypeService: DataTypeService) {
+ }
+
+ ngOnInit() {
+ this.isPropertyFEModel = this.property instanceof PropertyFEModel;
+ if (this.property instanceof PropertyFEModel) {
+ this.propType = this.getDerivedPropertyType(this.property.type);
+ this.propPath = this.property.name;
+ } else {
+ this.propType = this.property.derivedDataType;
+ this.propPath = this.property.propertiesName;
+ }
+
+ this.childrenCanBeDeclared = this.canBeDeclared && this.propType != this.derivedPropertyTypes.MAP && this.propType != this.derivedPropertyTypes.LIST;
+
+ if (this.propType == this.derivedPropertyTypes.LIST || this.propType == this.derivedPropertyTypes.MAP) {
+ this.initializeValues();
+ }
+
+ }
+
+ initializeValues = () => {
+ let tempValue: any;
+ if (this.property.value) {
+ tempValue = JSON.parse(this.property.value);
+ if (!_.isEmpty(tempValue)) {
+ tempValue.forEach((element, key) => {
+ let newChildID: string = this.createNewChildProperty(JSON.stringify(element));
+ this.mapOfIDsAndKeys[newChildID] = key;
+ console.log(this.mapOfIDsAndKeys);
+ });
+ }
+ }
+ //this.pseudoChildren = [];
+ //this.valueObjRef = [];
+ //TODO: generate necessary elements for existing values here
+ // if (this.propType == this.derivedPropertyTypes.LIST) {
+ // this.valueObjRef = (this.property.value) ? JSON.parse(this.property.value) : [];
+ // } else if (this.propType == this.derivedPropertyTypes.MAP) {
+ // this.valueObjRef = (this.property.value)? JSON.parse(this.property.value) : {};
+ // }
+ console.log(this.property.value);
+ }
+
+ onClickPropertyRow = (property, event) => {
+ // Because DynamicPropertyComponent is recrusive second time the event is fire event.stopPropagation = undefined
+ event && event.stopPropagation && event.stopPropagation();
+ this.clickOnPropertyRow.emit(property);
+ }
+
+ deleteListOrMapItem = (itemName: string) => {
+ this.propChildren = this.propChildren.filter(prop => prop.propertiesName.indexOf(itemName) != 0); //remove item and children;
+ }
+
+ propValueChanged = (property) => {
+ console.log("property value change!! Prop type: " + property.type + " New value: " + property.value);
+ this.valueChanged.emit(property);
+ };
+
+ expandChildById = (id: string) => {
+ this.expandedChildId = id;
+ this.expandChild.emit(id);
+ }
+
+ checkedChange = (propName: string) => {
+ this.checkProperty.emit(propName);
+ }
+
+
+
+ addRows = (): void => { //from within the template, when creating empty item
+ let childPropId = this.createNewChildProperty();
+ this.expandChildById(this.propPath + "#" + childPropId);
+ }
+
+ createNewChildProperty = (value?:string):string => {
+ let propUUID:string = UUID.UUID();
+ let newProp: DerivedFEProperty;
+ if (this.propType == this.derivedPropertyTypes.LIST) { //for list - create new prop of schema type
+ newProp = new DerivedFEProperty(propUUID, this.propPath, this.property.schema.property.type, value, true);
+ } else { //for map - create new prop of type map, with schema, but with flag that its a child
+ newProp = new DerivedFEProperty(propUUID, this.propPath, this.property.type, value, true, this.property.schema);
+ }
+
+
+ this.propChildren = this.propChildren || [];
+ this.propChildren.push(newProp);
+
+ //if it's a complex type, add children properties
+ if (!this.property.schema.property.isSimpleType) {
+ let schemaDataType: DataTypeModel = this.dataTypeService.getDataTypeByTypeName(this.property.schema.property.type);
+ this.dataTypeService.getDerivedDataTypeProperties(schemaDataType, this.propChildren, newProp.propertiesName);
+ this.propertiesUtils.assignValuesRecursively(JSON.parse(value), this.propChildren, newProp.propertiesName);
+ console.log(JSON.stringify(this.propChildren));
+ }
+
+ return propUUID;
+ }
+
+
+
+ //TODO: remove this and move to somewhere central!! (or make all properties be the same type...)
+ getDerivedPropertyType = (type) => {
+ if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(type) > -1) {
+ return DerivedPropertyType.SIMPLE;
+ } else if (type == PROPERTY_TYPES.LIST) {
+ return DerivedPropertyType.LIST;
+ } else if (type == PROPERTY_TYPES.MAP) {
+ return DerivedPropertyType.MAP;
+ } else {
+ return DerivedPropertyType.COMPLEX;
+ }
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.html b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.html
new file mode 100644
index 0000000000..a251d33649
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.html
@@ -0,0 +1,33 @@
+<div class="add-data-row">
+ <div class="sprite-new add-item-icon" (click)="addListItem()"></div>
+</div>
+<div *ngIf="property.schema.property.isSimpleType && property.valueObjectRef && property.valueObjectRef.length">
+ <!--the ngFor runs on dummy array in the list array length and not on list array in order to prevent from page do the ngFor again when user changes an item-->
+ <div class="simple-list-item" *ngFor="let item of getNumber(property.valueObjectRef); let i = index">
+ <input class="value-input" [(ngModel)]="property.valueObjectRef[i]"
+ type="property.schema.property.derivedFromSimpleTypeName || property.schema.property.type"
+ (change)="propValueChanged()"/>
+ <span class="delete-span sprite-new delete-item-icon" (click)="deleteListItem(i)"></span>
+ </div>
+</div>
+<div class="test" *ngIf="property.schema.property.isDataType && property.childrenProperties && property.childrenProperties.length">
+ <template ngFor let-item [ngForOf]="property.childrenProperties" let-i="index">
+ <div class="table-inner-row" (click)="onChildPropertySelected(item)" [ngClass]="{'selected': selectedPropertyId === item.treeNodeId}">
+ <div class="table-cell">{{item.name}}</div>
+ <div class="table-cell">
+ <span class="delete-span sprite-new delete-item-icon" (click)="deleteListItem(i)"></span>
+ <span (click)="property.updateExpandedChildPropertyId(item.treeNodeId)">V</span>
+ </div>
+ </div>
+ <div class="table-inner-row" *ngIf="property.expandedChildPropertyId == item.treeNodeId">
+ <div class="inner-table-container">
+ <properties-value-inner-table [property]="item"
+ (selectChildProperty)="onChildPropertySelected($event)"
+ [selectedPropertyId]="selectedPropertyId"
+ [propertyNameSearchText]="propertyNameSearchText"
+ (valueChanged)="propValueChanged()"></properties-value-inner-table>
+ </div>
+ </div>
+ </template>
+
+</div>
diff --git a/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.less b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.less
new file mode 100644
index 0000000000..7c4d90a38d
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.less
@@ -0,0 +1,3 @@
+.simple-list-item{
+ position: relative;
+}
diff --git a/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.ts b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.ts
new file mode 100644
index 0000000000..96f8c680a2
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.ts
@@ -0,0 +1,85 @@
+/**
+ * Created by rc2122 on 4/23/2017.
+ */
+import {Component, Input, Output, EventEmitter} from "@angular/core";
+import { PropertyFEModel} from "app/models";
+import {PropertiesService} from "app/ng2/services/properties.service";
+import { ContentAfterLastDotPipe } from "app/ng2/pipes/contentAfterLastDot.pipe";
+import {UUID} from "angular2-uuid";
+import {ComponentType} from "app/utils";
+
+@Component({
+ selector: 'list-property',
+ templateUrl: './list-property.component.html',
+ styleUrls: ['../properties-value-inner-table/properties-value-inner-table.component.less', './list-property.component.less']
+})
+export class ListPropertyComponent {
+
+ @Input() property: PropertyFEModel;
+ @Input() selectedPropertyId: string;
+ @Input() propertyNameSearchText:string;
+
+ @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>();
+ @Output() selectChildProperty: EventEmitter<any> = new EventEmitter<PropertyFEModel>();
+
+ constructor ( private propertiesService:PropertiesService, private contentAfterLastDotPipe:ContentAfterLastDotPipe ){
+ }
+
+ propValueChanged = () => {
+ this.valueChanged.emit(this.property);
+ };
+
+ onChildPropertySelected = (property) => {
+ this.selectChildProperty.emit(property);
+ };
+
+ getNumber = (valueObjectRef: any): Array<any> => {
+ let num: number = (valueObjectRef) ? valueObjectRef.length : 0;
+ return new Array(num);
+ }
+
+ createNewChildProperty = ():void => {
+ let newProperty: PropertyFEModel = new PropertyFEModel(this.contentAfterLastDotPipe.transform(this.property.schema.property.type),
+ this.property.schema.property.type,
+ UUID.UUID(),
+ this.property,
+ this.property.valueObjectRef[this.property.childrenProperties.length]
+ );
+ this.propertiesService.createPropertiesTreeForProp(newProperty);
+ this.property.childrenProperties.push(newProperty);
+ }
+
+ addListItem = ():void => {
+ this.property.valueObjectRef = this.property.valueObjectRef || [];
+ this.property.childrenProperties = this.property.childrenProperties || [];
+ if (this.property.schema.property.isSimpleType){
+ if( this.property.valueObjectRef.indexOf("") == -1 ) {//prevent insert multiple empty simple type items to list
+ this.property.valueObjectRef.push("");
+ }
+ }else{
+ this.property.valueObjectRef[this.property.childrenProperties.length] = {};
+ this.property.childrenProperties = this.property.childrenProperties || [];
+ this.createNewChildProperty();
+ this.valueChanged.emit(this.property);
+ }
+ }
+
+ deleteListItem = (indexInList:number):void => {
+ this.property.valueObjectRef.splice(indexInList, 1);
+ if(this.property.childrenProperties){
+ this.property.childrenProperties.splice(indexInList, 1);
+ }
+ if (!this.property.valueObjectRef.length) {//only when user removes all items from list - put the default
+ if ( this.property.defaultValue ) {
+ angular.copy(JSON.parse(this.property.defaultValue), this.property.valueObjectRef);
+ if (this.property.schema.property.isDataType){
+ _.forEach(this.property.valueObjectRef, () => {
+ this.createNewChildProperty();
+ });
+ }
+ }
+ }
+ this.valueChanged.emit(this.property);
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.html b/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.html
new file mode 100644
index 0000000000..e1975175a8
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.html
@@ -0,0 +1,38 @@
+<div class="add-data-row">
+ <div class="sprite-new add-item-icon" (click)="addMapItemFields()"></div>
+</div>
+
+<!--the ngFor runs on dummy array in mapKey array length and not on maKeys array in order to prevent from page do the ngFor again when user changes a key-->
+<template ngFor let-num [ngForOf]="getNumber(mapKeys.length)" let-i="index">
+ <div class="table-inner-row"
+ [ngClass]="{'selected': property.schema.property.isDataType && selectedPropertyId === property.childrenProperties[i].treeNodeId}"
+ (click)="property.schema.property.isDataType && onChildPropertySelected(property.childrenProperties[i])">
+ <div class="table-cell">
+ <input class="value-input" (keyup)="changeKeyOfMap(mapKeys[i], i);$event.stopPropagation();"
+ [(ngModel)]="mapKeys[i]"
+ name="mapKey{{property.treeNodeId}}{{i}}"
+ (change)="propValueChanged()"/>
+ </div>
+ <div class="table-cell">
+ <input class="value-input" *ngIf="property.schema.property.isSimpleType"
+ [(ngModel)]="property.valueObjectRef[mapKeys[i]]"
+ type="property.schema.property.derivedFromSimpleTypeName || property.schema.property.type"
+ (change)="propValueChanged()"/>
+ <div *ngIf="property.schema.property.isDataType">
+ <div>{{ property.schema.property.type | contentAfterLastDot }}</div>
+ <span (click)="property.updateExpandedChildPropertyId(property.childrenProperties[i].treeNodeId)">V</span>
+ </div>
+ <span class="delete-span sprite-new delete-item-icon" (click)="deleteMapItem(i)"></span>
+ </div>
+ </div>
+ <div class="table-inner-row" *ngIf="property.schema.property.isDataType && property.expandedChildPropertyId == property.childrenProperties[i].treeNodeId">
+ <div class="inner-table-container">
+ <properties-value-inner-table [property]="property.childrenProperties[i]"
+ [selectedPropertyId]="selectedPropertyId"
+ [propertyNameSearchText]="propertyNameSearchText"
+ (selectChildProperty)="onChildPropertySelected($event)"
+ (valueChanged)="propValueChanged()"></properties-value-inner-table>
+ </div>
+ </div>
+</template>
+
diff --git a/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.ts b/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.ts
new file mode 100644
index 0000000000..d62d0b94e3
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.ts
@@ -0,0 +1,121 @@
+/**
+ * Created by rc2122 on 4/24/2017.
+ */
+/**
+ * Created by rc2122 on 4/23/2017.
+ */
+import {Component, Input, Output, EventEmitter} from "@angular/core";
+import { PropertyFEModel} from "app/models";
+import { PropertiesService } from "../../../services/properties.service";
+import {ComponentType} from "app/utils";
+import {UUID} from "angular2-uuid";
+
+@Component({
+ selector: 'map-property',
+ templateUrl: './map-property.component.html',
+ styleUrls: ['../properties-value-inner-table/properties-value-inner-table.component.less']
+})
+export class MapPropertyComponent {
+
+ @Input() property: PropertyFEModel;
+ @Input() selectedPropertyId: string;
+ @Input() propertyNameSearchText:string;
+
+ @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>();
+ @Output() selectChildProperty: EventEmitter<any> = new EventEmitter<PropertyFEModel>();
+
+ constructor ( private propertiesService:PropertiesService){
+ }
+
+ mapKeys:Array<string>;
+
+ ngOnInit() {
+ this.mapKeys = Object.keys(this.property.valueObjectRef);
+ }
+
+ propValueChanged = () => {
+ this.valueChanged.emit(this.property);
+ };
+
+ onChildPropertySelected = (property) => {
+ this.selectChildProperty.emit(property);
+ };
+
+ getNumber = (num:number):Array<any> => {
+ return new Array(num);
+ }
+
+ createNewChildProperty = (mapKey:string):void => {
+
+ let newProperty: PropertyFEModel = new PropertyFEModel(mapKey,
+ this.property.schema.property.type,
+ UUID.UUID(), this.property,
+ this.property.valueObjectRef[mapKey]);
+ this.propertiesService.createPropertiesTreeForProp(newProperty);
+ this.property.childrenProperties = this.property.childrenProperties || [];
+ this.property.childrenProperties.push(newProperty);
+ }
+
+ //get: new key and the index of this item in the map
+ //This method checks if the new key isn't exist already in the map and update the object and the children array with the new key
+ changeKeyOfMap = (newKey:string, index:number):void => {
+ //let fieldName:string = "mapKey" + this.property.treeNodeId + index;
+ let oldKey:string = Object.keys(this.property.valueObjectRef)[index];
+ let existsKeyIndex:number = Object.keys(this.property.valueObjectRef).indexOf(newKey);
+ if (existsKeyIndex > -1 && existsKeyIndex != index) {
+ //error for exists key validation
+ } else {
+ //remove error for exists key validation and if the form is valid - update the map object
+ let newObj = {};
+ angular.forEach(this.property.valueObjectRef,function(value:any,key:string){
+ if(key == oldKey){
+ newObj[newKey] = value;
+ }else{
+ newObj[key] = value;
+ }
+ });
+ this.property.valueObjectRef = newObj;
+ this.property.parent.valueObjectRef[this.property.name] = this.property.valueObjectRef;//in order to prevent break ref
+ if(this.property.childrenProperties){
+ this.property.childrenProperties[index].name = newKey;//update this property childrenProperties with the new key
+ }
+ }
+ }
+
+ //get: index of the item in the map
+ //This method removes item from map.
+ deleteMapItem = (index:number):void=> {
+ delete this.property.valueObjectRef[this.mapKeys[index]];
+ this.mapKeys.splice(index, 1);
+ if(this.property.childrenProperties){
+ this.property.childrenProperties.splice(index, 1);
+ }
+ if (!this.mapKeys.length) {//only when user removes all pairs of key-value fields - put the default
+ if (this.property.defaultValue) {
+ angular.copy(JSON.parse(this.property.defaultValue), this.property.valueObjectRef);
+ this.mapKeys = Object.keys(this.property.valueObjectRef);
+ if (this.property.schema.property.isDataType){
+ angular.forEach(this.property.valueObjectRef, (value, key) => {
+ this.createNewChildProperty(key);
+ }, this);
+ }
+ }
+ }
+ this.valueChanged.emit(this.property);
+ }
+
+ //This method inserts new empty item to map
+ addMapItemFields = ():void => {
+ this.property.valueObjectRef = this.property.valueObjectRef || {};
+ if (this.property.schema.property.isSimpleType){
+ this.property.valueObjectRef[''] = null;
+ }else{
+ if(!this.property.valueObjectRef['']){
+ this.property.valueObjectRef[''] = {};
+ this.createNewChildProperty('');
+ }
+ }
+ this.mapKeys = Object.keys(this.property.valueObjectRef);
+ }
+}
+
diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html
new file mode 100644
index 0000000000..3ab47074e7
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html
@@ -0,0 +1,135 @@
+
+
+<div class="properties-table">
+ <div class="table-header">
+ <div class="table-cell col1">Property Name</div>
+ <div class="table-cell col2">Type</div>
+ <div class="table-cell col3">ES</div>
+ <div class="table-cell valueCol">Value</div>
+ </div>
+ <div class="table-body">
+ <div class="no-data" *ngIf="!feInstancesNames">No data to display</div>
+
+ <div *ngFor="let instanceName of feInstancesNames; trackBy:instanceName">
+ <div class="table-rows-header">{{instanceName | contentAfterLastDot}}</div>
+
+ <div class="table-row" *ngFor="let property of fePropertiesMap[instanceName]; trackBy:property?.name" (click)="onClickPropertyRow(property, instanceName, $event)" [ngClass]="{'selected': selectedPropertyId === property.uniqueId}">
+ <div class="table-cell col1">
+ <checkbox [label]="property.name"
+ [(checked)]="property.isSelected"
+ [disabled]="property.isDisabled || property.isDeclared"
+ (checkedChange)="propertyChecked(property)"
+ tooltip="{{property.name}}"></checkbox>
+ <span *ngIf="property.description" class="property-description-icon sprite-new show-desc" tooltip="{{property.description}}"></span>
+ </div>
+ <div class="table-cell col2">{{property.type | contentAfterLastDot}}</div>
+ <div class="table-cell col3">{{property.schema && property.schema.property && property.schema.property.type ? (property.schema.property.type
+ | contentAfterLastDot) : ''}}</div>
+ <div class="table-cell valueCol">
+ <!-- [ngClass]="{'filtered':property.name === propertyNameSearchText}" (selectProperty)="propertySelected(property, $event, flatProperty.propertiesName)" [propType]="property.type" [propSchema]="property.schema" [propKey]="" [propValue]="property.value"-->
+ <dynamic-property
+ [selectedPropertyId]="selectedPropertyId"
+ [canBeDeclared]="true"
+ [property]="property"
+ [expandedChildId]="property.expandedChildPropertyId"
+ [propChildren]="property.flattenedChildren | filterChildProperties : property.expandedChildPropertyId"
+ (valueChanged)="propValueChanged(property);"
+ (expandChild)="property.updateExpandedChildPropertyId($event)"
+ (clickOnPropertyRow)="onClickPropertyInnerRow($event, instanceName)"
+ (checkProperty)="propertyChecked(property, $event)"
+ >
+ </dynamic-property>
+
+ </div>
+ </div>
+<!--
+ <div *ngIf="!property.isSimpleType && !property.isDeclared" class="table-cell valueCol child-property-container">
+
+ <div class="child-property-row" *ngFor="let flatProperty of property.flattenedChildren | filterChildProperties : property.expandedChildPropertyId"
+ [ngClass]="{'selected': selectedPropertyId === flatProperty.uniqueId}" (click)="clickOnPropertyRow(flatProperty, instanceName, $event)" [@fadeIn]="''">
+ <derived-property [propertyObj]="flatProperty" [propertyNameSearchText]="propertyNameSearchText" [expanded]="flatProperty.propertiesName == property.expandedChildPropertyId"
+ (selectProperty)="propertySelected(property, $event, flatProperty.propertiesName)" (valueChanged)="propValueChanged(property)" (expandChild)="property.updateExpandedChildPropertyId($event)"></derived-property>
+ </div>
+
+ </div>
+ <!--
+
+
+ <property [propertyObj]="flatProperty"
+ [expanded]="flatProperty.parentName == property.expandedChildPropertyId"
+ (valueChanged)="propValueChanged($event)"
+ (selectChildProperty)="onChildPropertySelected($event)"></property>
+ <list-property *ngIf="property.type == 'list'"
+ [property]="property"
+ [selectedPropertyId]="selectedPropertyId"
+ (valueChanged)="propValueChanged($event)"
+ (selectChildProperty)="onChildPropertySelected($event)"></list-property>
+ <map-property *ngIf="property.type == 'map'"
+ [property]="property"
+ [selectedPropertyId]="selectedPropertyId"
+ (valueChanged)="propValueChanged($event)"
+ (selectChildProperty)="onChildPropertySelected($event)"></map-property>
+ <properties-value-inner-table *ngIf="property.isDataType"
+ [property]="property"
+ [selectedPropertyId]="selectedPropertyId"
+ (selectChildProperty)="onChildPropertySelected($event)"
+ (valueChanged)="propValueChanged($event)"></properties-value-inner-table>
+ -->
+
+
+
+ </div>
+
+ </div>
+</div>
+<!--
+<div class="properties-table" style="display:block">
+ <div class="table-header">
+ <div class="table-cell col1">Property Name</div>
+ <div class="table-cell col2">Type</div>
+ <div class="table-cell col3">ES</div>
+ <div class="table-cell valueCol">Value</div>
+ </div>
+ <div class="table-body">
+ <div class="no-data" *ngIf="!properties || !properties.length">No data to display</div>
+ <div class="table-row" *ngFor="let property of properties" (click)="clickOnPropertyRow(property)">
+ <div class="table-cell col1"><checkbox [label]="property.name" [(checked)]="property.isSelected" (checkedChange)="propSelected(property)" [disabled]="property.isDisabled"></checkbox></div>
+ <div class="table-cell col2">{{property.type | contentAfterLastDot}}</div>
+ <div class="table-cell col3">{{property.schema && property.schema.property && property.schema.property.type ? (property.schema.property.type | contentAfterLastDot) : ''}}</div>
+ <div class="table-cell valueCol" [class.inner-table-container]="property.childrenProperties || !property.isSimpleType">
+ <!--<input class="value-input" *ngIf="property.isSimpleType"
+ type="property.derivedFromSimpleTypeName || property.type"
+ [(ngModel)]="property.value"
+ (change)="putDefaultValueInEmptyProperty(property);propValueChanged(property);"
+ pattern="validationUtils.getValidationPattern(property.type)"/>
+ <dynamic-element
+ class="value-input"
+ *ngIf="property.isSimpleType"
+ [(value)]="property.value"
+ (valueChange)="putDefaultValueInEmptyProperty(property);propValueChanged(property);"
+ [type]="property.derivedFromSimpleTypeName || property.type"
+ [name]="property.name">
+ </dynamic-element>
+ <list-property *ngIf="property.type == 'list'"
+ [property]="property"
+ [selectedPropertyId]="selectedPropertyId"
+ (valueChanged)="propValueChanged($event)"
+ (selectChildProperty)="onChildPropertySelected($event)"></list-property>
+ <map-property *ngIf="property.type == 'map'"
+ [property]="property"
+ [selectedPropertyId]="selectedPropertyId"
+ (valueChanged)="propValueChanged($event)"
+ (selectChildProperty)="onChildPropertySelected($event)"></map-property>
+ <properties-value-inner-table *ngIf="property.isDataType"
+ [property]="property"
+ [selectedPropertyId]="selectedPropertyId"
+ (selectChildProperty)="onChildPropertySelected($event)"
+ (valueChanged)="propValueChanged($event)"></properties-value-inner-table>
+ </div>
+
+ </div>
+ </div>
+</div> -->
+
+
+
diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less
new file mode 100644
index 0000000000..de080dfdc9
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less
@@ -0,0 +1,188 @@
+
+@import './../../../../assets/styles/variables.less';
+
+:host /deep/ input { width:100%;}
+
+.properties-table {
+ display:flex;
+ flex-direction:column;
+ flex: 1;
+ height:100%;
+ text-align:left;
+
+
+ .child-property-container {
+ display:flex;
+ flex-direction:column;
+
+ &.table-cell {
+ padding:0;
+ }
+
+ .child-property-row {
+ border-bottom: #d2d2d2 solid 1px;
+ &:last-child {
+ border-bottom:none;
+ }
+ }
+ }
+
+
+ derived-property, dynamic-property {
+ width:100%;
+ }
+
+ /deep/ dynamic-property dynamic-property .dynamic-property-row {
+ border-top:solid #d2d2d2 1px;
+ }
+
+ /deep/ dynamic-property dynamic-property:first-of-type .dynamic-property-row:not(.with-top-border) {
+ border-top: none;
+ }
+
+ properties-value-inner-table {
+ width: 100%;
+ }
+
+ .table-header {
+ font-weight:bold;
+ border-top: #d2d2d2 solid 1px;
+ background-color: #eaeaea;
+
+ .valueCol {
+ justify-content: flex-start;
+ padding: 5px;
+ }
+ }
+ .table-header, .table-row {
+ display: flex;
+ flex-direction:row;
+ flex: 0 0 auto;
+ }
+
+ .table-body {
+ display:flex;
+ flex-direction: column;
+ overflow-y:auto;
+ flex: 1;
+
+ .no-data {
+ border: #d2d2d2 solid 1px;
+ border-top:none;
+ text-align: center;
+ height: 100%;
+ padding: 20px;
+ }
+ /deep/.selected{
+ background-color: #e6f6fb;
+ color: #009fdb;
+ }
+ }
+
+ .table-rows-header {
+ font-size:16px;
+ flex:1;
+ border: #d2d2d2 solid 1px;
+ border-top:none;
+ padding: 5px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ background-color: @tlv_color_v;
+ }
+
+ .table-row {
+ &:hover {
+ background-color:#f8f8f8; cursor:pointer;
+ }
+
+ &:last-child {
+ flex: 1 0 auto;
+ }
+ .selected-row {
+ background-color:#e6f6fb;
+ }
+ }
+ .table-cell {
+ font-size:13px;
+ flex:1;
+ border: #d2d2d2 solid 1px;
+ border-right:none;
+ border-top:none;
+ padding: 5px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+
+ &:last-child {
+ border-right:#d2d2d2 solid 1px;
+ }
+ &.col1 {
+ flex: 0 0 300px;
+ max-width:300px;
+
+ .property-description-icon {
+ float: right;
+ margin-top: 4px;
+ }
+ }
+ &.col2 {
+ flex: 0 0 150px;
+ max-width:150px;
+ }
+
+ &.col3 {
+ flex:0 0 120px;
+ max-width:120px;
+ }
+
+ &.valueCol {
+ flex: 1 0 auto;
+ min-width: 350px;
+ display: flex;
+ justify-content: flex-end;
+ padding: 0px;
+
+ .value-input {
+ flex: 1;
+ max-height: 24px;
+ border: none;
+ background-color: inherit;
+
+ &:focus, &:active {
+ border:none;
+ outline:none;
+ }
+ }
+
+ .delete-btn {
+ flex: 0 0 auto;
+ }
+
+ .delete-button-container {
+ max-height: 24px;
+ }
+
+ &.inner-table-container {
+ padding: 0px;
+
+ .delete-button-container {
+ padding: 3px 5px 0 0 ;
+ }
+ }
+ }
+
+ &.input-value-col {
+ padding: 5px;
+ }
+
+
+ }
+
+ .filtered {
+ /deep/ .checkbox-label-content{
+ background-color: yellow;
+ }
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts
new file mode 100644
index 0000000000..d5a9b40425
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts
@@ -0,0 +1,93 @@
+import { Component, Input, Output, EventEmitter, SimpleChanges, ViewChild, ElementRef } from "@angular/core";
+import { trigger, state, style, transition, animate} from '@angular/core';
+import {PropertyFEModel, DerivedFEProperty, DerivedPropertyType, InstanceFePropertiesMap} from "app/models";
+import {PropertiesService} from "../../services/properties.service";
+// import { GroupByPipe } from 'app/ng2/pipes/groupBy.pipe';
+//import {PropertiesValueInnerTableComponent} from "./properties-table/properties-value-inner-table/properties-value-inner-table";
+import { DynamicElementComponent } from 'app/ng2/components/dynamic-element/dynamic-element.component';
+import { KeysPipe } from 'app/ng2/pipes/keys.pipe';
+
+@Component({
+ selector: 'properties-table',
+ templateUrl: './properties-table.component.html',
+ styleUrls: ['./properties-table.component.less'],
+ animations: [trigger('fadeIn', [transition(':enter', [style({ opacity: '0' }), animate('.3s ease-out', style({ opacity: '1' }))]) ])]
+})
+export class PropertiesTableComponent {
+
+ @Input() fePropertiesMap: InstanceFePropertiesMap;
+ @Input() selectedPropertyId: string;
+ @Input() displayDeleteButton: boolean;
+ @Input() propertyNameSearchText:string;
+
+ @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>();
+ @Output() selectPropertyRow: EventEmitter<PropertyRowSelectedEvent> = new EventEmitter<PropertyRowSelectedEvent>();
+ @Output() updateCheckedPropertyCount: EventEmitter<boolean> = new EventEmitter<boolean>();
+ //@Output() selectInstanceRow: EventEmitter<string> = new EventEmitter<string>();
+
+ feInstancesNames: Array<string>;
+
+ constructor ( private propertiesService:PropertiesService ){
+ }
+
+ /**
+ * Update feInstancesNames when fePropertiesMap: InstanceFePropertiesMap change (after getting response from server)
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['fePropertiesMap']) {
+ if (changes['fePropertiesMap'].currentValue) {
+ let keysPipe = new KeysPipe();
+ let fiteredArr = keysPipe.transform(changes['fePropertiesMap'].currentValue,[]);
+ this.feInstancesNames = fiteredArr;
+ }
+ }
+ }
+
+ propValueChanged = (property) => {
+ !property.isDeclared && this.valueChanged.emit(property);
+ };
+
+ // Click on main row (row of propertyFEModel)
+ onClickPropertyRow = (property:PropertyFEModel, instanceName:string, event?) => {
+ //event && event.stopPropagation();
+ let propertyRowSelectedEvent:PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName);
+ this.selectPropertyRow.emit(propertyRowSelectedEvent);
+ };
+
+ // Click on inner row (row of DerivedFEProperty)
+ onClickPropertyInnerRow = (property:DerivedFEProperty, instanceName:string) => {
+ let propertyRowSelectedEvent:PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName);
+ this.selectPropertyRow.emit(propertyRowSelectedEvent);
+ }
+
+ propertyChecked = (prop: PropertyFEModel, childPropName?: string) => {
+ let isChecked: boolean = (!childPropName)? prop.isSelected : prop.flattenedChildren.find(prop => prop.propertiesName == childPropName).isSelected;
+
+ console.log(isChecked, childPropName, prop);
+ if (!isChecked) {
+ this.propertiesService.undoDisableRelatedProperties(prop, childPropName);
+ } else {
+ this.propertiesService.disableRelatedProperties(prop, childPropName);
+ }
+ this.updateCheckedPropertyCount.emit(isChecked);
+ }
+
+ putDefaultValueInEmptyProperty = (property:PropertyFEModel):void => {
+ property.value = property.value || property.defaultValue;
+ }
+
+ // clickOnInstanceRow = (instanceName:string) =>{
+ // this.selectInstanceRow.emit(instanceName);
+ // };
+
+}
+
+export class PropertyRowSelectedEvent {
+ propertyModel:PropertyFEModel | DerivedFEProperty;
+ instanceName:string;
+ constructor ( propertyModel:PropertyFEModel | DerivedFEProperty, instanceName:string ){
+ this.propertyModel = propertyModel;
+ this.instanceName = instanceName;
+ }
+}
+
diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.html b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.html
new file mode 100644
index 0000000000..61555cac50
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.html
@@ -0,0 +1,41 @@
+
+<template ngFor let-childProp [ngForOf]="property.childrenProperties" let-i="index">
+ <div class="table-inner-row" (click)="onChildPropertySelected(childProp)" [ngClass]="{'selected': selectedPropertyId === childProp.treeNodeId}">
+ <div class="table-cell" [ngClass]="{'filtered':childProp.name === propertyNameSearchText}">
+ <checkbox [label]="childProp.name" [(checked)]="childProp.isSelected" [disabled]="property.isDisabled"></checkbox>
+ </div>
+ <div class="table-cell prop-value">
+ <input class="value-input" *ngIf="childProp.isSimpleType"
+ [(ngModel)]="property.valueObjectRef[childProp.name]"
+ (change)="putDefaultValueInEmptyChildProperty(childProp);propValueChanged();"
+ type="childProp.derivedFromSimpleTypeName || childProp.type"/>
+ <span class="datatype-text" *ngIf="childProp.isDataType">{{ childProp.type | contentAfterLastDot }}</span>
+ <span *ngIf="!childProp.isSimpleType" (click)="property.updateExpandedChildPropertyId(childProp.treeNodeId)">V</span>
+ </div>
+ </div>
+ <div class="table-inner-row" *ngIf="childProp.type === 'list' && property.expandedChildPropertyId === childProp.treeNodeId">
+ <list-property [property]="childProp"
+ [selectedPropertyId]="selectedPropertyId"
+ [propertyNameSearchText]="propertyNameSearchText"
+ (valueChanged)="propValueChanged()"
+ (selectChildProperty)="onChildPropertySelected($event)"></list-property>
+
+ </div>
+ <div class="table-inner-row" *ngIf="childProp.type === 'map' && property.expandedChildPropertyId == childProp.treeNodeId">
+ <map-property [property]="childProp"
+ [selectedPropertyId]="selectedPropertyId"
+ [propertyNameSearchText]="propertyNameSearchText"
+ (valueChanged)="propValueChanged()"
+ (selectChildProperty)="onChildPropertySelected($event)"></map-property>
+
+ </div>
+ <div class="table-inner-row" *ngIf="childProp.isDataType && property.expandedChildPropertyId == childProp.treeNodeId">
+ <properties-value-inner-table [property]="childProp"
+ [selectedPropertyId]="selectedPropertyId"
+ [propertyNameSearchText]="propertyNameSearchText"
+ (selectChildProperty)="onChildPropertySelected($event)"
+ (valueChanged)="propValueChanged()"></properties-value-inner-table>
+ </div>
+</template>
+
+
diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.less b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.less
new file mode 100644
index 0000000000..1b7f6d4cd1
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.less
@@ -0,0 +1,71 @@
+table { width:100%;}
+tr {border-bottom: #d2d2d2 solid 1px;}
+tr:last-child { border-bottom:none;}
+td { border:none; padding:5px;}
+td:first-child { border-right:#d2d2d2 solid 1px;}
+
+.prop-value{
+ span {
+ position: absolute;
+ top: 5px;
+ right: 2px;
+
+ &.delete-span {
+ right:20px;
+ }
+
+ &.datatype-text {
+ position:static;
+ }
+
+ }
+}
+
+.add-data-row {
+ padding:5px;
+ text-align:right;
+ border-bottom: #d2d2d2 solid 1px;
+
+ &:last-child {
+ border-bottom:none;
+ }
+}
+.table-inner-row {
+ display:flex;
+ flex-direction:row;
+ border-bottom: #d2d2d2 solid 1px;
+ flex: 0 0 100%;
+ position:relative;
+
+ &:last-child {
+ border-bottom:none;
+ }
+
+ .table-cell {
+ flex: 0 0 50%;
+ padding:5px;
+ position:relative;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &:first-child {
+ border-right:#d2d2d2 solid 1px;
+ overflow:hidden;
+ }
+
+
+ }
+
+ .table-inner-container, .inner-table-container {
+ flex: 0 0 100%;
+ }
+}
+/deep/ map-property, /deep/ properties-value-inner-table, /deep/ list-property{
+ width:100%;
+}
+
+.filtered {
+ /deep/ .checkbox-label-content{
+ background-color: yellow;
+ }
+}
diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.ts b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.ts
new file mode 100644
index 0000000000..7d0b219ffe
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.ts
@@ -0,0 +1,37 @@
+/**
+ * Created by rc2122 on 4/20/2017.
+ */
+import {Component, Input, Output, EventEmitter} from "@angular/core";
+import {PropertyFEModel} from "app/models";
+import {PropertiesService} from "../../../services/properties.service";
+
+@Component({
+ selector: 'properties-value-inner-table',
+ templateUrl: './properties-value-inner-table.component.html',
+ styleUrls: ['./properties-value-inner-table.component.less']
+})
+export class PropertiesValueInnerTableComponent {
+
+ @Input() property: PropertyFEModel;
+ @Input() selectedPropertyId: string;
+ @Input() propertyNameSearchText:string;
+
+ @Output() selectChildProperty: EventEmitter<any> = new EventEmitter<PropertyFEModel>();
+ @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>();
+
+ constructor ( private propertiesService:PropertiesService){
+ }
+
+
+ onChildPropertySelected = (property) => {
+ this.selectChildProperty.emit(property);
+ };
+
+ propValueChanged = () => {
+ this.valueChanged.emit(this.property);
+ };
+
+ putDefaultValueInEmptyChildProperty = (childProp:PropertyFEModel):void => {
+ this.property.valueObjectRef[childProp.name] = this.property.valueObjectRef[childProp.name] || childProp.defaultValue;
+ }
+}
diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html
new file mode 100644
index 0000000000..1fbf45e39f
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html
@@ -0,0 +1,12 @@
+<div class="tooltip {{ placement }}"
+ [style.top]="top + 'px'"
+ [style.left]="left + 'px'"
+ [class.in]="isIn"
+ [class.fade]="isFade"
+ role="tooltip">
+ <div class="tooltip-arrow"></div>
+ <div class="tooltip-inner">
+ <ng-content></ng-content>
+ {{ content }}
+ </div>
+</div>
diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less
new file mode 100644
index 0000000000..1ff496f840
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less
@@ -0,0 +1,11 @@
+.tooltip-inner {
+ word-wrap: break-word;
+ max-width: 300px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+ border-bottom-color: #000 !important;
+}
+
+
+
diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts
new file mode 100644
index 0000000000..6e3e8065bb
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts
@@ -0,0 +1,195 @@
+import {Component, AfterViewInit, Input, ElementRef, ChangeDetectorRef} from "@angular/core";
+
+@Component
+({
+ selector: "tooltip-content",
+ templateUrl: "./tooltip-content.component.html",
+ styleUrls: ["./tooltip-content.component.less"]
+})
+
+export class TooltipContentComponent implements AfterViewInit {
+
+ // -------------------------------------------------------------------------
+ // Inputs / Outputs
+ // -------------------------------------------------------------------------
+
+ @Input() hostElement: HTMLElement;
+ @Input() content: string;
+ @Input() placement: "top"|"bottom"|"left"|"right" = "bottom";
+ @Input() animation: boolean = true;
+
+ // -------------------------------------------------------------------------
+ // Properties
+ // -------------------------------------------------------------------------
+
+ top: number = -100000;
+ left: number = -100000;
+ isIn: boolean = false;
+ isFade: boolean = false;
+
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(private element: ElementRef,
+ private cdr: ChangeDetectorRef) {
+ }
+
+ // -------------------------------------------------------------------------
+ // Lifecycle callbacks
+ // -------------------------------------------------------------------------
+
+ ngAfterViewInit(): void {
+ this.show();
+ this.cdr.detectChanges();
+ }
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ show(): void {
+ if(!this.hostElement) {
+ return;
+ }
+
+ const position = this.positionElement(this.hostElement, this.element.nativeElement.children[0], this.placement);
+ this.top = position.top;
+ this.left = position.left;
+ this.isIn = true;
+ if (this.animation) {
+ this.isFade = true;
+ }
+ }
+
+ hide(): void {
+ this.top = -100000;
+ this.left = -100000;
+ this.isIn = true;
+ if(this.animation) {
+ this.isFade = false;
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Private Methods
+ // -------------------------------------------------------------------------
+
+ private positionElement(hostElem: HTMLElement, targetElem: HTMLElement, positionStr: string, appendToBody: boolean = false): {top: number, left: number} {
+ let positionStrParts = positionStr.split("-");
+ let pos0 = positionStrParts[0];
+ let pos1 = positionStrParts[1] || "center";
+ let hostElemPosition = appendToBody ? this.offset(hostElem) : this.position(hostElem);
+ let targetElemWidth = targetElem.offsetWidth;
+ let targetElemHeight = targetElem.offsetHeight;
+ let shiftWidth: any = {
+ center(): number {
+ return hostElemPosition.left + hostElemPosition.width / 2 - targetElemWidth / 2;
+ },
+ left(): number {
+ return hostElemPosition.left;
+ },
+ right(): number {
+ return hostElemPosition.left + hostElemPosition.width;
+ }
+ };
+
+ let shiftHeight: any = {
+ center: function (): number {
+ return hostElemPosition.top + hostElemPosition.height / 2 - targetElemHeight / 2;
+ },
+ top: function (): number {
+ return hostElemPosition.top;
+ },
+ bottom: function (): number {
+ return hostElemPosition.top + hostElemPosition.height;
+ }
+ }
+
+ let targetElemPosition: {top: number, left: number};
+
+ switch (pos0) {
+ case "right":
+ targetElemPosition = {
+ top: shiftHeight[pos1](),
+ left: shiftWidth[pos0]()
+ };
+ break;
+
+ case "left":
+ targetElemPosition = {
+ top: shiftHeight[pos1](),
+ left: hostElemPosition.left - targetElemWidth
+ };
+ break;
+
+ case "bottom":
+ targetElemPosition = {
+ top: shiftHeight[pos0](),
+ left: shiftWidth[pos1]()
+ };
+ break;
+
+ default:
+ targetElemPosition = {
+ top: hostElemPosition.top - targetElemHeight,
+ left: shiftWidth[pos1]()
+ };
+ break;
+ }
+
+ return targetElemPosition;
+ }
+
+
+ private position(nativeElem: HTMLElement): {width: number, height: number, top: number, left: number} {
+ let offsetParentCBR = {top: 0, left: 0};
+ const elemBCR = this.offset(nativeElem);
+ const offsetParentElem = this.parentOffsetElem(nativeElem);
+ if(offsetParentElem !== window.document) {
+ offsetParentCBR = this.offset(offsetParentElem);
+ offsetParentCBR.top += offsetParentElem.clientTop - offsetParentElem.scrollTop;
+ offsetParentCBR.left += offsetParentElem.clientLeft - offsetParentElem.scrollTop;
+ }
+
+ const boundingClientRect = nativeElem.getBoundingClientRect();
+
+ return {
+ width: boundingClientRect.width || nativeElem.offsetWidth,
+ height: boundingClientRect.height || nativeElem.offsetHeight,
+ top: elemBCR.top - offsetParentCBR.top,
+ left: elemBCR.left - offsetParentCBR.left
+ };
+ }
+
+ private offset(nativeElem:any): {width: number, height: number, top: number, left: number} {
+ const boundingClientRect = nativeElem.getBoundingClientRect();
+ return {
+ width: boundingClientRect.width || nativeElem.offsetWidth,
+ height: boundingClientRect.height || nativeElem.offsetHeight,
+ top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop),
+ left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft)
+ };
+ }
+
+ private getStyle(nativeElem: HTMLElement, cssProperty: string): string {
+ if(window.getComputedStyle) {
+ return (window.getComputedStyle(nativeElem) as any)[cssProperty];
+ }
+
+ return (nativeElem.style as any)[cssProperty];
+ }
+
+ private isStaticPositioned(nativeElem: HTMLElement): boolean {
+ return (this.getStyle(nativeElem, "position") || "static") === "static";
+ }
+
+ private parentOffsetElem(nativeElem: HTMLElement): any {
+ let offsetParent: any = nativeElem.offsetParent || window.document;
+ while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) {
+ offsetParent = offsetParent.offsetParent;
+ }
+
+ return offsetParent || window.document;
+ }
+}
diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts
new file mode 100644
index 0000000000..e98b69003e
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts
@@ -0,0 +1,81 @@
+import {
+ Directive, ComponentRef, ViewContainerRef, ComponentFactoryResolver, Input, HostListener
+} from "@angular/core";
+import {TooltipContentComponent} from "./tooltip-content.component";
+
+@Directive ({
+ selector: "[tooltip]"
+})
+export class TooltipComponent {
+
+ // -------------------------------------------------------------------------
+ // Properties
+ // -------------------------------------------------------------------------
+
+ private tooltip: ComponentRef<TooltipContentComponent>;
+ private visible: boolean;
+
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(private viewContainerRef: ViewContainerRef,
+ private resolver: ComponentFactoryResolver) {
+ }
+
+ // -------------------------------------------------------------------------
+ // Inputs / Outputs
+ // -------------------------------------------------------------------------
+
+ @Input("tooltip") content: string|TooltipContentComponent;
+ @Input() tooltipDisabled: boolean;
+ @Input() tooltipAnimation: boolean = true;
+ @Input() tooltipPlacement: "top"|"bottom"|"left"|"right" = "bottom";
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ @HostListener("mouseenter")
+ show(): void {
+ if(this.tooltipDisabled || this.visible || this.content === "") {
+ return;
+ }
+
+ this.visible = true;
+ if (typeof this.content === "string") {
+ const factory = this.resolver.resolveComponentFactory(TooltipContentComponent);
+ if (!this.visible) {
+ return;
+ }
+
+ this.tooltip = this.viewContainerRef.createComponent(factory);
+ this.tooltip.instance.hostElement = this.viewContainerRef.element.nativeElement;
+ this.tooltip.instance.content = this.content as string;
+ this.tooltip.instance.placement = this.tooltipPlacement;
+ this.tooltip.instance.animation = this.tooltipAnimation;
+ } else {
+ const tooltip = this.content as TooltipContentComponent;
+ tooltip.hostElement = this.viewContainerRef.element.nativeElement;
+ tooltip.placement = this.tooltipPlacement;
+ tooltip.animation = this.tooltipAnimation;
+ tooltip.show();
+ }
+ }
+
+ @HostListener("mouseleave")
+ hide(): void {
+ if (!this.visible) {
+ return;
+ }
+
+ this.visible = false;
+ if (this.tooltip) {
+ this.tooltip.destroy();
+ }
+ if (this.content instanceof TooltipContentComponent) {
+ (this.content as TooltipContentComponent).hide();
+ }
+ }
+}
+
diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts
new file mode 100644
index 0000000000..69976da6af
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts
@@ -0,0 +1,25 @@
+import {NgModule} from "@angular/core";
+import {TooltipContentComponent} from "./tooltip-content.component";
+import {TooltipComponent} from "./tooltip.component";
+import {CommonModule} from "@angular/common";
+
+@NgModule({
+ declarations: [
+ TooltipComponent,
+ TooltipContentComponent,
+ ],
+ imports: [
+ CommonModule
+ ],
+ exports: [
+ TooltipComponent,
+ TooltipContentComponent,
+ ],
+ entryComponents: [
+ TooltipContentComponent
+ ],
+ providers: []
+})
+export class TooltipModule {
+
+}