nm-edit-attributes
src/app/shared/widgets/edit-attributes/edit-attributes.component.ts
providers |
EditAttributeService
|
selector | nm-edit-attributes |
styleUrls | edit-attributes.component.scss |
templateUrl | ./edit-attributes.component.html |
constructor(_widgetframeService: WidgetframeService, _editAttributeService: EditAttributeService, _halService: HalService, _notificationService: CustomNotificationService, validationService: ValidationService, translateService: TranslateService)
|
|||||||||||||||||||||
Parameters :
|
Private attributeChanged | ||||||
attributeChanged(attribute: Attribute)
|
||||||
Parameters :
Returns :
void
|
Protected configureWidget | ||||||
configureWidget(configuration: WidgetConfig
|
||||||
Decorators : WidgetConfigure
|
||||||
Parameters :
Returns :
void
|
emitLookupLoaded | ||||
emitLookupLoaded(attribute: )
|
||||
Parameters :
Returns :
boolean
|
markSendUnchanged |
markSendUnchanged()
|
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
onLookupLoaded | ||||||
onLookupLoaded(data: , attribute: )
|
||||||
Parameters :
Returns :
void
|
resetWidget |
resetWidget()
|
Returns :
any
|
setBoxWidth | ||||
setBoxWidth(index: )
|
||||
Parameters :
Returns :
string
|
trackByFn | ||||||
trackByFn(index: , item: )
|
||||||
Parameters :
Returns :
any
|
Public trackByIdentifier | ||||||
trackByIdentifier(idx: , attribute: )
|
||||||
Parameters :
Returns :
any
|
Private validateAttributes |
validateAttributes()
|
Returns :
void
|
Public _editAttributeService |
_editAttributeService:
|
Type : EditAttributeService
|
Private _emitLookupLoaded |
_emitLookupLoaded:
|
Default value : false
|
Public _halService |
_halService:
|
Type : HalService
|
Public _id |
_id:
|
Type : string
|
Decorators : WidgetId
|
Public _notificationService |
_notificationService:
|
Type : CustomNotificationService
|
Public _widgetframeService |
_widgetframeService:
|
Type : WidgetframeService
|
Public action |
action:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Emits action. |
Public addFields |
addFields:
|
Default value : new Subject<Attribute[]>()
|
Decorators : WidgetInput
|
Takes a list of attributes as inputs and adds them to the list |
Public additionalDescriptionTooltipIcon |
additionalDescriptionTooltipIcon:
|
Type : string
|
attributeRows |
attributeRows:
|
Type : QueryList<EditAttributeComponent>
|
Decorators : ViewChildren
|
Public attributes |
attributes:
|
Type : Attribute[]
|
Public attributesInput |
attributesInput:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
List of attributes to be displayed. it's another alternatives for the uri. |
Public boxWidth |
boxWidth:
|
Type : string
|
Public changedAttributes |
changedAttributes:
|
Default value : <any>{}
|
Public changedAttributesOutput |
changedAttributesOutput:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Emits changed attributes. |
Public cols |
cols:
|
Type : any[]
|
Public configuration |
configuration:
|
Type : WidgetConfig<EditAttributesConfiguration>
|
Decorators : WidgetConfiguration
|
Public copyContentButton |
copyContentButton:
|
Type : boolean
|
Default value : false
|
Public displayValueAssets |
displayValueAssets:
|
Type : boolean
|
Default value : true
|
Public editLayout |
editLayout:
|
Type : string
|
Default value : "list"
|
Private emitLookupLoadedAttributes |
emitLookupLoadedAttributes:
|
Type : any[]
|
Public inline |
inline:
|
Type : boolean
|
Default value : false
|
Public inputLink |
inputLink:
|
Type : string
|
Public lastChangedAttributeOutput |
lastChangedAttributeOutput:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Emits last changed attribute. |
Public lookupLoadedOutput |
lookupLoadedOutput:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Emits when lookup field is loaded. |
Public productLevel |
productLevel:
|
Type : string
|
Default value : ""
|
Public productNo |
productNo:
|
Public productNoForEdit |
productNoForEdit:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Product No. to be updated after changes done on attributes. |
Public reload |
reload:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Reload channel. |
Public removeFields |
removeFields:
|
Default value : new Subject<string[]>()
|
Decorators : WidgetInput
|
Takes a list of strings as input and will remove the attributes with that identifier if found |
Public resetWidgets |
resetWidgets:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Reset widget data. |
Public selectedDataLocale |
selectedDataLocale:
|
Default value : new ReplaySubject<any>(1)
|
Decorators : WidgetInput
|
Selected data locale channel triggeres when locale changed. |
Public selectedDataLocaleString |
selectedDataLocaleString:
|
Public showDescriptionTooltips |
showDescriptionTooltips:
|
Type : boolean
|
Public showValidation |
showValidation:
|
Type : boolean
|
Public sub |
sub:
|
Public targets |
targets:
|
Default value : new ReplaySubject<any>(1)
|
Decorators : WidgetInput
|
Public targetsInput |
targetsInput:
|
Type : []
|
Default value : []
|
Public unsubscribe |
unsubscribe:
|
Default value : NgUnsubscribe.create()
|
Public uri |
uri:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Uri to load attributes. |
Public valid |
valid:
|
Default value : new Subject<boolean>()
|
Decorators : WidgetOutput
|
Public withBorder |
withBorder:
|
Type : boolean
|
Default value : true
|
import {
combineLatest as observableCombineLatest,
Subject,
ReplaySubject,
} from "rxjs";
import {
map,
mergeMap,
takeUntil,
onErrorResumeNext,
distinctUntilChanged,
filter,
} from "rxjs/operators";
import { Component, OnDestroy, QueryList, ViewChildren } from "@angular/core";
import { WidgetframeService } from "../widgetframe/widgetframe.service";
import { getOrDefault, WidgetConfig } from "../widget.configuration";
import {
WidgetComponent,
WidgetConfiguration,
WidgetConfigure,
WidgetId,
WidgetInput,
WidgetOutput,
} from "../widget.metadata";
import { NgUnsubscribe } from "../../ng-unsubscribe";
import * as uriTemplates_ from "uri-templates";
import { Attribute } from "../../components/edit-attribute/attribute";
import { EditAttributeService } from "../../components/edit-attribute/edit-attribute.service";
import { HalService } from "../../components/hal/index";
import { CustomNotificationService } from "../../components/notification/customnotification.service";
import { TranslateService } from "@ngx-translate/core";
import { flatCopy } from "../../components/util/util.service";
import { BaseConfiguration } from "../widgetframe/widgetframe.component";
import { ValidationService } from "../../components/validation/validation.service";
import { EditAttributeComponent } from "../../components/edit-attribute";
const uriTemplates = uriTemplates_;
export interface EditAttributesConfiguration extends BaseConfiguration {
/**
* Enables/Disables inline mode. @default(false)
*/
inline?: boolean;
/**
* Enables/Disables image (asset) for lookup options. @default(true)
*/
"display-value-assets"?: boolean;
/**
* Emits when lookup loaded. @default(false)
*/
emitLookupLoaded?: boolean;
/**
* Shows/Hides description tooltips on attribute. @default(false)
* attribute required object key 'descriptionTooltip' and content type of string
*/
showDescriptionTooltips?: boolean;
/**
* Changes additional description tooltip icons. @default(info)
*/
additionalDescriptionTooltipIcon?: string;
/**
* List of lookup attributes identifiers to be compared when the lookup attribute is loaded .
*/
emitLookupLoadedAttributes?: any[];
/**
* Shows/Hides copy content button. It is used to copy field data.
* To show this button : value should be true and attribute isn't ('BOOLEAN' ,'META' ,'COMPOSITION_AMOUNT' ,'COMPOSITION_PERCENT').
* @default(false)
*/
copyContentButton?: boolean;
/**
* List of attribute identifiers that need to be marked as unchanged.
*/
"send-unchanged"?: string[];
/**
* Edit layout. its possible values are : ['list' , 'textarea']. @default('list')
*/
"edit-layout"?: string;
/**
* Action name to subscribe on.
*/
"action-name"?: string;
/**
* Context. Used by content-management-app to spasifay which context is executed.
* Its possible values :['' , 'content'] @default('')
*/
context?: string;
/**
* Enables/Disables filter dropdown for attribute.
*/
filter?: boolean;
/**
* Enables/Disables displaying the validation error indicator, if the value is not valid
*/
"show-validation"?: boolean;
}
@WidgetComponent("nm-edit-attributes")
@Component({
selector: "nm-edit-attributes",
templateUrl: "./edit-attributes.component.html",
styleUrls: ["./edit-attributes.component.scss"],
providers: [EditAttributeService],
})
export class EditAttributesWidgetComponent implements OnDestroy {
public cols: any[];
public attributes: Attribute[];
public changedAttributes = <any>{};
public inputLink: string;
@ViewChildren(EditAttributeComponent)
attributeRows: QueryList<EditAttributeComponent>;
@WidgetConfiguration()
public configuration: WidgetConfig<EditAttributesConfiguration>;
/**
* Product No. to be updated after changes done on attributes.
*/
@WidgetInput()
public productNoForEdit = new Subject<any>();
/**
* Reload channel.
*/
@WidgetInput()
public reload = new Subject<any>();
/**
* Selected data locale channel triggeres when locale changed.
*/
@WidgetInput()
public selectedDataLocale = new ReplaySubject<any>(1);
/**
* Emits changed attributes.
*/
@WidgetOutput("changedAttributesOutput")
public changedAttributesOutput = new Subject<any>();
/**
* Emits last changed attribute.
*/
@WidgetOutput("lastChangedAttributeOutput")
public lastChangedAttributeOutput = new Subject<any>();
/**
* Emits when lookup field is loaded.
*/
@WidgetOutput("lookupLoadedOutput")
public lookupLoadedOutput = new Subject<any>();
/**
* Emits action.
*/
@WidgetOutput("action")
public action = new Subject<any>();
@WidgetOutput("valid")
public valid = new Subject<boolean>();
@WidgetInput()
public targets = new ReplaySubject<any>(1);
/**
* Selected product level. it's displayed as a header for widget in case we aren't in inline mode.
*/
@WidgetInput()
public selectedProductLevel = new Subject<any>();
/**
* Reset widget data.
*/
@WidgetInput()
public resetWidgets = new Subject<any>();
/**
* Uri to load attributes.
*/
@WidgetInput()
public uri = new Subject<any>();
/**
* List of attributes to be displayed. it's another alternatives for the uri.
*/
@WidgetInput()
public attributesInput = new Subject<any>();
@WidgetId()
public _id: string;
/**
* Takes a list of strings as input and will remove the attributes with that identifier if found
*/
@WidgetInput()
public removeFields = new Subject<string[]>();
/**
* Takes a list of attributes as inputs and adds them to the list
*/
@WidgetInput()
public addFields = new Subject<Attribute[]>();
/**
* This will search for attributes with the existing identifier and replace the value with the one from the input.
* This will allow to keep the order and only change the value.
* If you want to modifiy the value of meta-attribute use parent@child as identifier
*/
@WidgetInput()
public value = new Subject<Attribute[]>();
public unsubscribe = NgUnsubscribe.create();
public sub;
public editLayout: string = "list";
public boxWidth: string;
public productNo;
public selectedDataLocaleString;
public productLevel: string = "";
public targetsInput = [];
public inline: boolean = false;
public withBorder: boolean = true;
public copyContentButton: boolean = false;
public displayValueAssets: boolean = true;
private _emitLookupLoaded = false;
private emitLookupLoadedAttributes: any[];
public showDescriptionTooltips: boolean;
public additionalDescriptionTooltipIcon: string;
public showValidation: boolean;
constructor(
public _widgetframeService: WidgetframeService,
public _editAttributeService: EditAttributeService,
public _halService: HalService,
public _notificationService: CustomNotificationService,
private validationService: ValidationService,
private translateService: TranslateService
) {
this.cols = [
{ field: "description", header: "tab.head.att.name" },
{ field: "value", header: "" },
];
}
@WidgetConfigure()
protected configureWidget(
configuration: WidgetConfig<EditAttributesConfiguration>
) {
this.inline =
this.configuration.configuration["inline"] !== undefined
? this.configuration.configuration["inline"]
: false;
this.displayValueAssets =
this.configuration.configuration["display-value-assets"] !== undefined
? this.configuration.configuration["display-value-assets"]
: true;
this._emitLookupLoaded = getOrDefault(
this.configuration.configuration["emitLookupLoaded"],
false
);
this.showDescriptionTooltips = getOrDefault(
this.configuration.configuration.showDescriptionTooltips,
false
);
this.additionalDescriptionTooltipIcon = getOrDefault(
this.configuration.configuration.additionalDescriptionTooltipIcon,
"info_outline"
);
this.showValidation = getOrDefault(
this.configuration.configuration["show-validation"],
false
);
this.withBorder = getOrDefault(
this.configuration.configuration.withBorder,
true
);
this.emitLookupLoadedAttributes =
this.configuration.configuration["emitLookupLoadedAttributes"];
this.copyContentButton = getOrDefault(
this.configuration.configuration["copyContentButton"],
false
);
this.changedAttributes.values = [];
this.changedAttributes.targets = [];
// let href = configuration._links["attributes"]["href"];
const editLayout = configuration.configuration["edit-layout"];
if (editLayout) {
this.editLayout = editLayout;
}
// let template = uriTemplates(href);
this.productNoForEdit
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => (this.productNo = data));
this.selectedDataLocale
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
this.changedAttributesOutput.next(null);
this.selectedDataLocaleString = data;
});
this.selectedProductLevel
.pipe(takeUntil(this.unsubscribe))
.subscribe((productLevel) => {
this.productLevel = productLevel;
});
this.resetWidgets.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
this.attributes = [];
this.changedAttributes.values = [];
this.changedAttributes.targets = [];
this.changedAttributesOutput.next(null);
this.uri.next(" ");
});
this.targets.pipe(takeUntil(this.unsubscribe)).subscribe((targetsInput) => {
this.targetsInput = targetsInput.slice(0);
});
this._editAttributeService
.getAttributeChangedEvent()
.pipe(takeUntil(this.unsubscribe))
.subscribe((attribute) => {
this.attributeChanged(attribute);
//}
});
this._halService
.getActionEvents()
.pipe(
filter(
(event) => event.name === configuration.configuration["action-name"]
),
map((event) => (<any>event).response),
takeUntil(this.unsubscribe)
)
.subscribe((resp) => {
this.changedAttributes = <any>{};
this.changedAttributes.values = [];
this.changedAttributes.targets = [];
this.changedAttributesOutput.next(null);
if (resp.message !== undefined) {
if (resp.level === "ERROR") {
}
if (resp.level === "SUCSESS") {
this.changedAttributes = <any>{};
this.changedAttributes.values = [];
this.changedAttributesOutput.next(null);
}
}
});
let source = observableCombineLatest(
this.reload.asObservable().pipe(distinctUntilChanged()),
this.selectedDataLocale.asObservable().pipe(distinctUntilChanged()),
this.uri.asObservable(),
function (reloadEvent, selectedDataLocale, uri) {
let template = uriTemplates(uri);
let uriParams = {};
uriParams["data-locale"] = selectedDataLocale;
uriParams["context"] = configuration.configuration["context"];
uriParams["locale"] = reloadEvent;
return template.fill(uriParams);
}
).pipe(
takeUntil(this.unsubscribe),
mergeMap((href) =>
this._widgetframeService
.getData(href)
.pipe(onErrorResumeNext(this.resetWidget()))
),
map((res) => <any>res)
);
source.subscribe(
(data) => {
this.attributes = data["values"];
this.validateAttributes();
if (data.hasOwnProperty("_actions")) {
let template = uriTemplates(data["_actions"].save.href);
let uriParams = {};
uriParams["productNo"] = this.productNo;
data["_actions"].save.href = template.fill(uriParams);
this.action.next(data["_actions"].save);
}
if (this.configuration.configuration["send-unchanged"]) {
this.markSendUnchanged();
}
},
(error) => {
console.log(error);
this.attributes = null;
this.valid.next(true);
}
);
// alternative to uri input
this.attributesInput
.pipe(takeUntil(this.unsubscribe))
.subscribe((attribute) => {
this.attributes = attribute;
this.validateAttributes();
if (this.configuration.configuration["send-unchanged"]) {
this.markSendUnchanged();
}
});
this.removeFields.pipe(takeUntil(this.unsubscribe)).subscribe((fields) => {
this.attributes = this.attributes.filter(
(entry) => fields.indexOf(entry.identifier) === -1
);
this.validateAttributes();
});
this.addFields.pipe(takeUntil(this.unsubscribe)).subscribe((fields) => {
this.attributes.push(...fields);
this.validateAttributes();
});
this.value.pipe(takeUntil(this.unsubscribe)).subscribe((fields) => {
if (!Array.isArray(fields)) {
console.error("Input for value must be an array ", fields);
return;
}
fields.forEach((field) => {
//We are supposed to update a meta attribute
if (field.identifier.indexOf("@") !== -1) {
const parentName = field.identifier.substring(
0,
field.identifier.indexOf("@")
);
const childName = field.identifier.substring(
field.identifier.indexOf("@") + 1
);
const parentIndex = this.attributes.findIndex(
(entry) => entry.identifier === parentName
);
if (parentIndex === -1) {
console.warn(
`Cant find attribute with identifier ${parentName}`,
this.attributes
);
return;
}
const parentAttribute = this.attributes[parentIndex];
if (
!parentAttribute._embedded ||
!parentAttribute._embedded.attributes
) {
console.warn(
`Cant find any children in attribute`,
parentAttribute
);
return;
}
const child = parentAttribute._embedded.attributes.find(
(entry) => entry.identifier === childName
);
child.source = field.source;
this.attributes[parentIndex] = flatCopy(parentAttribute);
this.attributeChanged(this.attributes[parentIndex]);
return;
}
const attributeIndex = this.attributes.findIndex(
(entry) => entry.identifier === field.identifier
);
if (attributeIndex === -1) {
console.warn(
`Cant find attribute ${field.identifier}`,
this.attributes
);
return;
}
this.attributes[attributeIndex].source = field.source;
//Create a flatcopy of the attribute to trigger change detection of edit-attribute
this.attributes[attributeIndex] = flatCopy(
this.attributes[attributeIndex]
);
this.attributeChanged(this.attributes[attributeIndex]);
});
});
this.changedAttributesOutput
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
this.validateAttributes();
});
}
private attributeChanged(attribute: Attribute) {
let valid = true;
if (attribute.type === "COMPOSITION_PERCENT") {
let total: number = 0;
for (var source of attribute.source) {
total += parseInt(source.amount);
}
if (total != 100) {
valid = false;
this._notificationService.error(
this.translateService.instant("ERROR"),
this.translateService.instant("composition.percent.error.message")
);
}
}
//if (valid) {
let duplicate = this.changedAttributes.values.filter(
(item) => item.identifier == attribute.identifier
);
if (duplicate.length === 1) {
if (attribute.type === "META") {
attribute._embedded.attributes.forEach((metaAttrDef, index) => {
let duplicateMetaAttrDef = duplicate[0].attributes[index];
if (duplicateMetaAttrDef.identifier !== metaAttrDef.identifier) {
duplicateMetaAttrDef = duplicate[0].attributes.find(
(dupMetaAttrDef) =>
dupMetaAttrDef.identifier === metaAttrDef.identifier
);
}
duplicateMetaAttrDef.source = metaAttrDef.source;
});
} else {
duplicate[0].source = attribute.source;
}
} else {
this.changedAttributes.values.push(attribute);
}
if (this.targetsInput.length > 0) {
this.changedAttributes.targets = this.targetsInput;
} else {
this.changedAttributes.targets = [];
this.changedAttributes.targets.push({
level: "product",
identifier: this.productNo,
});
}
this.changedAttributes["locale"] = this.selectedDataLocaleString;
this.lastChangedAttributeOutput.next(attribute);
this.changedAttributesOutput.next(this.changedAttributes);
}
resetWidget() {
return null;
}
ngOnDestroy() {
this.unsubscribe.destroy();
}
setBoxWidth(index) {
return 98 / index + "%";
}
trackByFn(index, item) {
return item.identifiers; // or item.id
}
markSendUnchanged() {
const sendUnchanged = this.configuration.configuration["send-unchanged"];
let attributesToSendUnchanged = this.attributes.filter(function (
attribute
) {
return sendUnchanged.indexOf(attribute.identifier) >= 0;
});
for (const attribute of attributesToSendUnchanged) {
attribute.sendUnchanged = true;
}
}
emitLookupLoaded(attribute) {
if (!this._emitLookupLoaded) {
return false;
}
if (!this.emitLookupLoadedAttributes) {
return true;
}
return (
this.emitLookupLoadedAttributes.findIndex(
(v) => v === attribute.identifier
) !== -1
);
}
onLookupLoaded(data, attribute) {
this.lookupLoadedOutput.next({ data, attribute });
}
private validateAttributes() {
if (!this.attributes) {
this.valid.next(true);
return;
}
this.validationService
.validateBulkEdit(this.attributes, null)
.subscribe((errors) => {
this.valid.next(errors.size == 0);
this.attributeRows.forEach((row) => {
row._changeDetectorRef.markForCheck();
});
});
}
public trackByIdentifier(idx, attribute) {
return attribute.identifier;
}
}
<nm-widgetframe
*ngIf="!inline"
[header]="configuration.configuration['header']"
[withBorder]="withBorder"
[configuration]="configuration"
widgetId="{{ _id }}"
>
<div slot="title" class="nm-widgetframe__title">
{{ configuration.configuration["title"] | translate }}
<span *ngIf="productLevel.length > 0"
> ({{ productLevel | translate }})</span
>
<nm-help-icon
*ngIf="configuration.configuration['infoText']"
[info-text]="configuration.configuration['infoText'] | translate"
[info-title]="configuration.configuration['title'] | translate"
[info-placement]="'left'"
[wiki-link]="configuration.configuration['wikiLink']"
></nm-help-icon>
</div>
<div
slot="content"
class="nm-widgetframe__content"
*ngIf="attributes !== null"
>
<div class="nm-attribute-list" *ngIf="editLayout === 'list'">
<div *ngFor="let attribute of attributes; trackBy: trackByIdentifier">
<div class="nm-attribute-list-elements">
<div class="nm-attribute-list-description">
<nm-ellipsis [content]="attribute.description"></nm-ellipsis>
</div>
<div
class="nm-attribute-list-filter"
*ngIf="configuration.configuration['filter']"
>
<nm-edit-attribute-filter
[attribute]="attribute"
[editAttributeService]="_editAttributeService"
></nm-edit-attribute-filter>
</div>
<div class="nm-attribute-list-value">
<nm-edit-attribute
[attribute]="attribute"
[emitLookupLoaded]="emitLookupLoaded(attribute)"
(lookupLoaded)="onLookupLoaded($event, attribute)"
[copyContentButton]="copyContentButton"
[editLayout]="editLayout"
[editAttributeService]="_editAttributeService"
[displayValueAssets]="displayValueAssets"
[additionalDescriptionTooltipIcon]="
additionalDescriptionTooltipIcon
"
[showDescriptionTooltips]="showDescriptionTooltips"
[showValidation]="showValidation"
></nm-edit-attribute>
</div>
</div>
</div>
</div>
<div
class="nm-attribute-list textarea-layout"
*ngIf="editLayout === 'textarea'"
>
<div *ngFor="let attribute of attributes; trackBy: trackByIdentifier">
<nm-edit-attribute
[attribute]="attribute"
[floatingLabel]="attribute.description"
[emitLookupLoaded]="emitLookupLoaded(attribute)"
(lookupLoaded)="onLookupLoaded($event, attribute)"
[editAttributeService]="_editAttributeService"
[editLayout]="editLayout"
[boxWidth]="boxWidth"
[showValidation]="showValidation"
></nm-edit-attribute>
</div>
</div>
</div>
</nm-widgetframe>
<div *ngIf="inline">
<div slot="title" class="nm-widgetframe__title">
{{ configuration.configuration["title"] | translate }}
</div>
<div
slot="content"
class="nm-widgetframe__content"
*ngIf="attributes !== null"
>
<div class="nm-attribute-list" *ngIf="editLayout === 'list'">
<div *ngFor="let attribute of attributes; trackBy: trackByIdentifier">
<div class="nm-attribute-list-elements">
<div class="nm-attribute-list-description">
<nm-ellipsis [content]="attribute.description"></nm-ellipsis>
</div>
<div
class="nm-attribute-list-filter"
*ngIf="configuration.configuration['filter']"
>
<nm-edit-attribute-filter
[attribute]="attribute"
[editAttributeService]="_editAttributeService"
></nm-edit-attribute-filter>
</div>
<div class="nm-attribute-list-value">
<nm-edit-attribute
[attribute]="attribute"
[emitLookupLoaded]="emitLookupLoaded(attribute)"
(lookupLoaded)="onLookupLoaded($event, attribute)"
[copyContentButton]="false"
[editLayout]="editLayout"
[editAttributeService]="_editAttributeService"
[displayValueAssets]="displayValueAssets"
[additionalDescriptionTooltipIcon]="
additionalDescriptionTooltipIcon
"
[showDescriptionTooltips]="showDescriptionTooltips"
[showValidation]="showValidation"
></nm-edit-attribute>
</div>
</div>
</div>
</div>
<div
class="nm-attribute-list textarea-layout"
*ngIf="editLayout === 'textarea'"
>
<div *ngFor="let attribute of attributes; trackBy: trackByIdentifier">
<nm-edit-attribute
[attribute]="attribute"
[emitLookupLoaded]="emitLookupLoaded(attribute)"
(lookupLoaded)="onLookupLoaded($event, attribute)"
[editAttributeService]="_editAttributeService"
[editLayout]="editLayout"
[boxWidth]="boxWidth"
[showValidation]="showValidation"
></nm-edit-attribute>
</div>
</div>
</div>
</div>