File

src/app/shared/widgets/edit-attributes/edit-attributes.component.ts

Extends

BaseConfiguration

Index

Properties

Properties

action-name
action-name: string
Type : string
Optional

Action name to subscribe on.

additionalDescriptionTooltipIcon
additionalDescriptionTooltipIcon: string
Type : string
Optional

Changes additional description tooltip icons. @default(info)

context
context: string
Type : string
Optional

Context. Used by content-management-app to spasifay which context is executed. Its possible values :['' , 'content'] @default('')

copyContentButton
copyContentButton: boolean
Default value : (false)
Type : boolean
Optional

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').

display-value-assets
display-value-assets: boolean
Type : boolean
Optional

Enables/Disables image (asset) for lookup options. @default(true)

edit-layout
edit-layout: string
Type : string
Optional

Edit layout. its possible values are : ['list' , 'textarea']. @default('list')

emitLookupLoaded
emitLookupLoaded: boolean
Type : boolean
Optional

Emits when lookup loaded. @default(false)

emitLookupLoadedAttributes
emitLookupLoadedAttributes: any[]
Type : any[]
Optional

List of lookup attributes identifiers to be compared when the lookup attribute is loaded .

filter
filter: boolean
Type : boolean
Optional

Enables/Disables filter dropdown for attribute.

inline
inline: boolean
Type : boolean
Optional

Enables/Disables inline mode. @default(false)

send-unchanged
send-unchanged: string[]
Type : string[]
Optional

List of attribute identifiers that need to be marked as unchanged.

show-validation
show-validation: boolean
Type : boolean
Optional

Enables/Disables displaying the validation error indicator, if the value is not valid

showDescriptionTooltips
showDescriptionTooltips: boolean
Type : boolean
Optional

Shows/Hides description tooltips on attribute. @default(false) attribute required object key 'descriptionTooltip' and content type of string

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;
  }
}

results matching ""

    No results matching ""