aboutsummaryrefslogtreecommitdiffstats
path: root/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute
diff options
context:
space:
mode:
Diffstat (limited to 'catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute')
-rw-r--r--catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.html102
-rw-r--r--catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.less82
-rw-r--r--catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.ts232
3 files changed, 416 insertions, 0 deletions
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.html b/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.html
new file mode 100644
index 0000000000..7f271af4e1
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.html
@@ -0,0 +1,102 @@
+<!--
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ -->
+
+<div *ngIf="!attribute.hidden" class="dynamic-property-row nested-level-{{nestedLevel}}" [@fadeIn]
+ [ngClass]="{'selected': selectedAttributeId && selectedAttributeId === attribute.propertiesName, 'readonly': attribute.isDisabled ||attribute.isDeclared}"
+ [class.with-top-border]="attribute.isChildOfListOrMap"
+ (click)="onClickPropertyRow(attribute, $event)">
+ <!-- LEFT CELL -->
+ <ng-container *ngIf="!isAttributeFEModel">
+ <div class="table-cell" *ngIf="canBeDeclared" [ngClass]="{'filtered':attribute.name === attributeNameSearchText}" [class.round-checkbox]="attribute.isDeclared"> <!-- simple children of complex type [@checkEffect]="property.isDeclared"-->
+ <checkbox *ngIf="hasDeclareOption" [(checked)]="attribute.isSelected" [disabled]="attribute.isDisabled ||attribute.isDeclared || readonly" (checkedChange)="checkAttribute.emit(attribute.propertiesName)" ></checkbox>
+ <div class="inner-cell-div" tooltip="{{attribute.name}}"><span>{{attribute.name}}</span></div>
+ </div>
+ <div class="table-cell" *ngIf="!canBeDeclared && !attribute.isChildOfListOrMap">
+ <div class="inner-cell-div" tooltip="{{attribute.name}}"><span>{{attribute.name}}</span></div>
+ </div> <!-- simple children of complex type within map or list -->
+ <div class="table-cell map-entry" *ngIf="attribute.isChildOfListOrMap && attribType == derivedAttributeType.MAP"><!-- map left cell -->
+ <dynamic-element #mapKeyInput
+ class="value-input"
+ pattern="validationUtils.getValidationPattern(string)"
+ [value]="attribute.mapKey"
+ type="string"
+ [name]="attribute.name"
+ (elementChanged)="mapKeyChanged.emit($event.value)"
+ [readonly]="readonly"
+ [testId]="'prop-key-' + attributeTestsId"
+ ></dynamic-element>
+ </div>
+ </ng-container>
+ <!-- RIGHT CELL OR FULL WIDTH CELL-->
+ <ng-container *ngIf="attribType == derivedAttributeType.SIMPLE || attribute.isDeclared || (attribute.isChildOfListOrMap && attribType == derivedAttributeType.MAP && attribute.schema.property.isSimpleType)">
+ <div class="table-cell">
+ <dynamic-element class="value-input"
+ pattern="validationUtils.getValidationPattern(property.type)"
+ [value]="attribute.isDeclared ? attribute.value : attribute.valueObj"
+ [type]="attribute.isDeclared ? 'string' : attribute.type"
+ [name]="attribute.name"
+ [path]="attribute.propertiesName"
+ (elementChanged)="onElementChanged($event)"
+ [readonly]="readonly || attribute.isDeclared || attribute.isDisabled"
+ [testId]="'prop-' + attributeTestsId"
+ [declared] = "attribute.isDeclared"
+ [constraints] = "constraints"
+ ></dynamic-element>
+ </div>
+ </ng-container>
+ <ng-container *ngIf="!isAttributeFEModel && attribType != derivedAttributeType.SIMPLE && !attribute.isDeclared"> <!-- right cell for complex elements, or list complex -->
+ <div class="table-cell" *ngIf="attribType == derivedAttributeType.COMPLEX">{{attribute.type | contentAfterLastDot }}</div>
+ <div class="table-cell" *ngIf="attribType == derivedAttributeType.MAP && !attribute.schema.property.isSimpleType">{{attribute.schema.property.type | contentAfterLastDot }}</div>
+ </ng-container>
+ <ng-container *ngIf="isAttributeFEModel && (attribType == derivedAttributeType.LIST || attribType == derivedAttributeType.MAP) && !attribute.isDeclared"><!-- empty, full-width table cell - for PropertyFEModel of type list or map -->
+ <div class="table-cell empty"></div>
+ </ng-container>
+ <!-- ICONS: add, delete, and expand -->
+ <ng-container *ngIf="!attribute.isDeclared">
+ <a *ngIf="(attribType == derivedAttributeType.LIST || attribType == derivedAttributeType.MAP) && !attribute.isChildOfListOrMap" class="property-icon add-item" (click)="createNewChildProperty();" [ngClass]="{'disabled':readonly || preventInsertItem(attribute)}" [attr.data-tests-id]="'add-to-list-' + attributeTestsId">Add value to list</a>
+ <span *ngIf="attribute.isChildOfListOrMap" (click)="deleteItem.emit(attribute);" class="property-icon sprite-new delete-item-icon" [ngClass]="{'disabled':readonly}" [attr.data-tests-id]="'delete-from-list-' + attributeTestsId"></span>
+ <span *ngIf="!isAttributeFEModel && (attribType == derivedAttributeType.COMPLEX || ((attribType == derivedAttributeType.LIST || attribType == derivedAttributeType.MAP) && hasChildren))" (click)="expandChildById(attribPath)" class="property-icon sprite-new round-expand-icon" [class.open]="expandedChildId.indexOf(attribPath) == 0" [attr.data-tests-id]="'expand-' + attributeTestsId" ></span>
+ </ng-container>
+
+</div>
+<!-- FLAT CHILDREN -->
+<div class="flat-children-container" *ngIf="isAttributeFEModel && !attribute.isDeclared">
+ <ng-container *ngFor="let prop of attribute.flattenedChildren | filterChildAttributes: expandedChildId; trackBy:prop?.propertiesName">
+ <dynamic-property
+ [selectedAttributeId]="selectedAttributeId"
+ [hasDeclareOption]="hasDeclareOption"
+ [canBeDeclared]="hasDeclareOption && prop.canBeDeclared"
+ [attribute]="prop"
+ [rootAttribute]="rootAttribute || attribute"
+ [expandedChildId]="expandedChildId"
+ [attributeNameSearchText]="attributeNameSearchText"
+ [readonly]="readonly"
+ [hasChildren]="getHasChildren(prop)"
+ (propertyChanged)="childValueChanged(prop)"
+ (mapKeyChanged)="updateChildKeyInParent(prop, $event)"
+ (expandChild)="expandChildById($event)"
+ (deleteItem)="deleteListOrMapItem($event)"
+ (clickOnAttributeRow)="onClickPropertyRow($event)"
+ (checkAttribute)="checkedChange($event)"
+ (addChildAttribsToParent)="addChildProps($event, prop.propertiesName)"
+ >
+ </dynamic-property>
+ </ng-container>
+</div>
diff --git a/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.less b/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.less
new file mode 100644
index 0000000000..fd572b0f2d
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.less
@@ -0,0 +1,82 @@
+@import '../../../../../../assets/styles/variables.less';
+.flat-children-container {
+ .dynamic-property-row {
+ /*create nested left border classes for up to 10 levels of nesting*/
+ .nested-border-loop(@i) when (@i > 0) {
+ @size: (@i - 1) *2;
+ &.nested-level-@{i} .table-cell:first-child {
+ border-left: ~"solid @{size}px #009fdb";
+ }
+ .nested-border-loop(@i - 1)
+ }
+ .nested-border-loop(10);
+ }
+ dynamic-property {
+ &:first-child .dynamic-property-row.with-top-border {
+ border-top:solid 1px #d2d2d2;
+ }
+ &:not(:last-child) .dynamic-property-row {
+ border-bottom:solid 1px #d2d2d2;
+ }
+ }
+}
+.dynamic-property-row {
+ display:flex;
+ flex-direction:row;
+ align-items: stretch;
+
+ &.readonly{
+ background-color: @tlv_color_t;
+ cursor: auto;
+ }
+ //for the case that the parent is disabled but the child is enabled
+ &:not(.readonly){
+ background-color: @main_color_p;
+ }
+
+ .table-cell {
+ flex: 1;
+ padding:9px;
+ justify-content: center;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &:first-child {
+ flex: 0 0 50%;
+ border-right:#d2d2d2 solid 1px;
+ &:only-of-type {
+ flex: 1 1 100%;
+ border-right:none;
+ }
+ }
+ &.empty {
+ height:40px;
+ }
+ }
+ .property-icon {
+ flex: 0 0 auto;
+ margin-right:10px;
+ align-self:center;
+ cursor:pointer;
+ }
+}
+
+.filtered {
+ /deep/ .checkbox-label-content{
+ background-color: yellow;
+ }
+}
+.inner-cell-div{
+ max-width: 100%;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ display: inline;
+ padding-left: 8px;
+}
+.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/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.ts b/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.ts
new file mode 100644
index 0000000000..39faac9283
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/attributes-table/dynamic-attribute/dynamic-attribute.component.ts
@@ -0,0 +1,232 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import * as _ from "lodash";
+import {Component, EventEmitter, Input, Output, ViewChild} from "@angular/core";
+import {PROPERTY_TYPES} from 'app/utils';
+import {DataTypeService} from "../../../../services/data-type.service";
+import {animate, style, transition, trigger} from '@angular/animations';
+import {IUiElementChangeEvent} from "../../../ui/form-components/ui-element-base.component";
+import {DynamicElementComponent} from "../../../ui/dynamic-element/dynamic-element.component";
+import {DerivedAttributeType} from "../../../../../models/attributes-outputs/attribute-be-model";
+import {AttributeFEModel} from "app/models/attributes-outputs/attribute-fe-model";
+import {DerivedFEAttribute} from "../../../../../models/attributes-outputs/derived-fe-attribute";
+import {AttributesUtils} from "../../../../pages/attributes-outputs/services/attributes.utils";
+
+@Component({
+ selector: 'dynamic-property',
+ templateUrl: './dynamic-attribute.component.html',
+ styleUrls: ['./dynamic-attribute.component.less'],
+ animations: [trigger('fadeIn', [transition(':enter', [style({opacity: '0'}), animate('.7s ease-out', style({opacity: '1'}))])])]
+})
+export class DynamicAttributeComponent {
+
+ derivedAttributeType = DerivedAttributeType;
+ attribType: DerivedAttributeType;
+ attribPath: string;
+ isAttributeFEModel: boolean;
+ nestedLevel: number;
+ attributeTestsId: string;
+ constraints: string[];
+
+ @Input() canBeDeclared: boolean;
+ @Input() attribute: AttributeFEModel | DerivedFEAttribute;
+ @Input() expandedChildId: string;
+ @Input() selectedAttributeId: string;
+ @Input() attributeNameSearchText: string;
+ @Input() readonly: boolean;
+ @Input() hasChildren: boolean;
+ @Input() hasDeclareOption: boolean;
+ @Input() rootAttribute: AttributeFEModel;
+
+ @Output('attributeChanged') emitter: EventEmitter<void> = new EventEmitter<void>();
+ @Output() expandChild: EventEmitter<string> = new EventEmitter<string>();
+ @Output() checkAttribute: EventEmitter<string> = new EventEmitter<string>();
+ @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>();
+ @Output() clickOnAttributeRow: EventEmitter<AttributeFEModel | DerivedFEAttribute> = new EventEmitter<AttributeFEModel | DerivedFEAttribute>();
+ @Output() mapKeyChanged: EventEmitter<string> = new EventEmitter<string>();
+ @Output() addChildAttribsToParent: EventEmitter<Array<DerivedFEAttribute>> = new EventEmitter<Array<DerivedFEAttribute>>();
+
+ @ViewChild('mapKeyInput') public mapKeyInput: DynamicElementComponent;
+
+ constructor(private attributesUtils: AttributesUtils, private dataTypeService: DataTypeService) {
+ }
+
+ ngOnInit() {
+ this.isAttributeFEModel = this.attribute instanceof AttributeFEModel;
+ this.attribType = this.attribute.derivedDataType;
+ this.attribPath = (this.attribute instanceof AttributeFEModel) ? this.attribute.name : this.attribute.attributesName;
+ this.nestedLevel = (this.attribute.attributesName.match(/#/g) || []).length;
+ this.rootAttribute = (this.rootAttribute) ? this.rootAttribute : <AttributeFEModel>this.attribute;
+ this.attributeTestsId = this.getAttributeTestsId();
+
+ this.initConstraintsValues();
+
+ }
+
+ initConstraintsValues() {
+ let primitiveProperties = ['string', 'integer', 'float', 'boolean'];
+
+ if (this.attribute.constraints) {
+ this.constraints = this.attribute.constraints[0].validValues
+ }
+
+ //Complex Type
+ else if (primitiveProperties.indexOf(this.rootAttribute.type) == -1 && primitiveProperties.indexOf(this.attribute.type) >= 0) {
+ this.constraints = this.dataTypeService.getConstraintsByParentTypeAndUniqueID(this.rootAttribute.type, this.attribute.name);
+ } else {
+ this.constraints = null;
+ }
+
+ }
+
+ onClickPropertyRow = (property, event) => {
+ // Because DynamicAttributeComponent is recursive second time the event is fire event.stopPropagation = undefined
+ event && event.stopPropagation && event.stopPropagation();
+ this.clickOnAttributeRow.emit(property);
+ }
+
+ expandChildById = (id: string) => {
+ this.expandedChildId = id;
+ this.expandChild.emit(id);
+ }
+
+ checkedChange = (propName: string) => {
+ this.checkAttribute.emit(propName);
+ }
+
+ getHasChildren = (property: DerivedFEAttribute): boolean => {// enter to this function only from base property (AttributeFEModel) and check for child property if it has children
+ return _.filter((<AttributeFEModel>this.attribute).flattenedChildren, (prop: DerivedFEAttribute) => {
+ return _.startsWith(prop.attributesName + '#', property.attributesName);
+ }).length > 1;
+ }
+
+ getAttributeTestsId = () => {
+ return [this.rootAttribute.name].concat(this.rootAttribute.getParentNamesArray(this.attribute.attributesName, [], true)).join('.');
+ };
+
+ onElementChanged = (event: IUiElementChangeEvent) => {
+ this.attribute.updateValueObj(event.value, event.isValid);
+ this.emitter.emit();
+ };
+
+ createNewChildProperty = (): void => {
+
+ let newProps: Array<DerivedFEAttribute> = this.attributesUtils.createListOrMapChildren(this.attribute, "", null);
+ this.attributesUtils.assignFlattenedChildrenValues(this.attribute.valueObj, [newProps[0]], this.attribute.attributesName);
+ if (this.attribute instanceof AttributeFEModel) {
+ this.addChildProps(newProps, this.attribute.name);
+ } else {
+ this.addChildAttribsToParent.emit(newProps);
+ }
+ }
+
+ addChildProps = (newProps: Array<DerivedFEAttribute>, childPropName: string) => {
+
+ if (this.attribute instanceof AttributeFEModel) {
+ let insertIndex: number = this.attribute.getIndexOfChild(childPropName) + this.attribute.getCountOfChildren(childPropName); //insert after parent prop and existing children
+ this.attribute.flattenedChildren.splice(insertIndex, 0, ...newProps); //using ES6 spread operator
+ this.expandChildById(newProps[0].attributesName);
+
+ this.updateMapKeyValueOnMainParent(newProps);
+ this.emitter.emit();
+ }
+ }
+
+ updateMapKeyValueOnMainParent(childrenProps: Array<DerivedFEAttribute>) {
+ if (this.attribute instanceof AttributeFEModel) {
+ const attributeFEModel: AttributeFEModel = <AttributeFEModel>this.attribute;
+ //Update only if all this attributeFEModel parents has key name
+ if (attributeFEModel.getParentNamesArray(childrenProps[0].attributesName, []).indexOf('') === -1) {
+ angular.forEach(childrenProps, (prop: DerivedFEAttribute): void => { //Update parent AttributeFEModel with value for each child, including nested props
+ attributeFEModel.childPropUpdated(prop);
+ if (prop.isChildOfListOrMap && prop.mapKey !== undefined) {
+ attributeFEModel.childPropMapKeyUpdated(prop, prop.mapKey, true);
+ }
+ }, this);
+ //grab the cumulative value for the new item from parent AttributeFEModel and assign that value to DerivedFEProp[0] (which is the list or map parent with UUID of the set we just added)
+ let parentNames = (<AttributeFEModel>attributeFEModel).getParentNamesArray(childrenProps[0].attributesName, []);
+ childrenProps[0].valueObj = _.get(attributeFEModel.valueObj, parentNames.join('.'), null);
+ }
+ }
+ }
+
+ childValueChanged = (property: DerivedFEAttribute) => { //value of child property changed
+
+ if (this.attribute instanceof AttributeFEModel) { // will always be the case
+ if (this.attribute.getParentNamesArray(property.attributesName, []).indexOf('') === -1) {//If one of the parents is empty key -don't save
+ this.attribute.childPropUpdated(property);
+ this.emitter.emit();
+ }
+ }
+ }
+
+ deleteListOrMapItem = (item: DerivedFEAttribute) => {
+ if (this.attribute instanceof AttributeFEModel) {
+ this.removeValueFromParent(item);
+ this.attribute.flattenedChildren.splice(this.attribute.getIndexOfChild(item.attributesName), this.attribute.getCountOfChildren(item.attributesName));
+ this.expandChildById(item.attributesName);
+ }
+ }
+
+ removeValueFromParent = (item: DerivedFEAttribute) => {
+ if (this.attribute instanceof AttributeFEModel) {
+ let itemParent = (item.parentName == this.attribute.name)
+ ? this.attribute : this.attribute.flattenedChildren.find(prop => prop.attributesName == item.parentName);
+ if (!itemParent) {
+ return;
+ }
+
+ if (item.derivedDataType == DerivedAttributeType.MAP) {
+ const oldKey = item.getActualMapKey();
+ delete itemParent.valueObj[oldKey];
+ if (itemParent instanceof AttributeFEModel) {
+ delete itemParent.valueObjValidation[oldKey];
+ itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
+ }
+ this.attribute.childPropMapKeyUpdated(item, null); // remove map key
+ } else {
+ const itemIndex: number = this.attribute.flattenedChildren.filter(prop => prop.parentName == item.parentName).map(prop => prop.attributesName).indexOf(item.attributesName);
+ itemParent.valueObj.splice(itemIndex, 1);
+ if (itemParent instanceof AttributeFEModel) {
+ itemParent.valueObjValidation.splice(itemIndex, 1);
+ itemParent.valueObjIsValid = itemParent.calculateValueObjIsValid();
+ }
+ }
+ if (itemParent instanceof AttributeFEModel) { //direct child
+ this.emitter.emit();
+ } else { //nested child - need to update parent prop by getting flattened name (recurse through parents and replace map/list keys, etc)
+ this.childValueChanged(itemParent);
+ }
+ }
+ }
+
+ updateChildKeyInParent(childProp: DerivedFEAttribute, newMapKey: string) {
+ if (this.attribute instanceof AttributeFEModel) {
+ this.attribute.childPropMapKeyUpdated(childProp, newMapKey);
+ this.emitter.emit();
+ }
+ }
+
+ preventInsertItem = (property: DerivedFEAttribute): boolean => {
+ return property.type == PROPERTY_TYPES.MAP && Object.keys(property.valueObj).indexOf('') > -1;
+ }
+
+}