File

src/app/shared/widgets/buy/search-attributes/search-attributes.component.ts

Index

Properties

Properties

allowRange
allowRange: boolean
Type : boolean
Optional

Enables/disables attribute values range, from and to. @default(false)

attributeUrl
attributeUrl: String
Type : String
Optional

used to get attributes in case of editShowAttributes enabled.

canEditShownAttributes
canEditShownAttributes: boolean
Type : boolean
Optional

Enables/Disables selecting attributes SelectAttributesDialog. @default(false)

localstorage-attribute-search
localstorage-attribute-search: String
Type : String
Optional

Local storage key for edited attributes and their values.

localstorage-content-visible
localstorage-content-visible: String
Type : String
Optional

Local storage key for search attributes visibility state.

multiLookupsAllowed
multiLookupsAllowed: boolean
Type : boolean
Optional

Enables/disables multilookup option. @default(false)

translateAttributeDescriptions
translateAttributeDescriptions: boolean
Type : boolean
Optional

Enables/Disables showing attributes description. @default(false)

import {
  combineLatest as observableCombineLatest,
  Subject,
  ReplaySubject,
} from "rxjs";

import {
  onErrorResumeNext,
  takeUntil,
  mergeMap,
  distinctUntilChanged,
  map,
  flatMap,
} from "rxjs/operators";
import { Component, OnDestroy } from "@angular/core";
import { WidgetframeService } from "../../widgetframe/widgetframe.service";
import {
  getOrDefault,
  throwIfUndefined,
  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 {
  DeletionMode,
  Scope,
} from "../../../components/local-storage/local-storage-constants";
import {
  LocalStorageEntry,
  LocalStorageService,
} from "../../../components/local-storage/local-storage.service";
import { SelectAttributesDialogComponent } from "../../../components/dialog/selectAttributesDialog.component";
import { AppdataStore } from "../../../components/appdata/appdata.store";
import { deepCopy } from "../../../components/util/util.service";
import { FormControl } from "@angular/forms";
import { DialogService } from "../../../components/dialog";

const uriTemplates = uriTemplates_;

export interface SearchAttributesWidgetConfiguration {
  /**
   * Enables/Disables selecting attributes SelectAttributesDialog. @default(false)
   */
  canEditShownAttributes?: boolean;
  /**
   * Local storage key for edited attributes and their values.
   */
  "localstorage-attribute-search"?: String;
  /**
   * Local storage key for search attributes visibility state.
   */
  "localstorage-content-visible"?: String;
  /**
   * Enables/disables attribute values range, from and to. @default(false)
   */
  allowRange?: boolean;
  /**
   * Enables/Disables showing attributes description. @default(false)
   */
  translateAttributeDescriptions?: boolean;
  /**
   * Enables/disables multilookup option. @default(false)
   */
  multiLookupsAllowed?: boolean;
  /**
   * used to get attributes in case of editShowAttributes enabled.
   */
  attributeUrl?: String;
}

declare var jQuery: any;

@WidgetComponent("nm-search-attributes")
@Component({
  selector: "nm-search-attributes",
  templateUrl: "./search-attributes.component.html",
  styleUrls: ["./search-attributes.component.scss"],
  providers: [EditAttributeService],
})
export class SearchAttributesWidgetComponent implements OnDestroy {
  public cols: any[];
  public attributes: Attribute[];
  public attributesorg: Attribute[];
  public changedAttributes = <any>{};
  public localstorageAttributesEntry: LocalStorageEntry;
  public localStorageVisibiltyEntry: LocalStorageEntry;

  public canEditShownAttributes = false;

  public contentVisible: boolean = false;
  public allowRange;
  public isCollapsible: boolean = false;

  public translateAttributeDescriptions = false;
  public currentLocale: string;
  public inputLink: string;

  /**
   *  By default, attributes of type 'MULTI_LOOKUP' are converted to type 'LOOKUP', for compatibility with some
   *  search functions. Setting this value to true will allow multiple values to be selected for these attributes.
   */
  public multiLookupsAllowed: boolean;

  @WidgetConfiguration()
  public configuration: WidgetConfig<SearchAttributesWidgetConfiguration>;

  /**
   * Reloads attributes for a given locale, and attributes url.
   */
  @WidgetInput()
  public reload = new Subject<any>();

  /**
   * Emits an array of all edited attributes and their values.
   */
  @WidgetOutput("changedAttributesOutput")
  public changedAttributesOutput = new ReplaySubject<any>();

  /**
   * Emits the attribute that is being edited.
   */
  @WidgetOutput("changedValueByUser")
  public changedValueByUser = new Subject<any>();

  /**
   * Resets attributes and local storage values.
   */
  @WidgetInput()
  public reset = new Subject<any>();

  /**
   * Will call given uri and select the attributes from the address.
   */
  @WidgetInput("uri")
  public uri = new ReplaySubject<any>(1);

  /**
   * Input to set the uri that is used for the editShownAttributes.
   */
  @WidgetInput("attributeUrl")
  public attributeUrl = new Subject();

  /**
   * Input of preset array of attribute identifiers, used to get attributes.
   */
  @WidgetInput()
  public data = new Subject();

  @WidgetId()
  public _id: string;

  public unsubscribe = NgUnsubscribe.create();
  private attributesLocalstorageEntry: LocalStorageEntry;

  constructor(
    public _widgetframeService: WidgetframeService,
    public _editAttributeService: EditAttributeService,
    public _halService: HalService,
    private localStorageService: LocalStorageService,
    public _notificationService: CustomNotificationService,
    private appStore: AppdataStore,
    private dialogService: DialogService
  ) {}

  private selectAttributeUrl;

  @WidgetConfigure()
  protected configureWidget(configuration: WidgetConfig) {
    this.changedAttributes = [];
    this.localStorageVisibiltyEntry = this.localStorageService.getLocalStorageEntry(
      configuration.configuration["localstorage-content-visible"],
      Scope.GLOBAL,
      DeletionMode.LOGIN
    );
    this.localstorageAttributesEntry = this.localStorageService.getLocalStorageEntry(
      configuration.configuration["localstorage-attribute-search"],
      Scope.GLOBAL,
      DeletionMode.LOGIN
    );
    this.selectAttributeUrl = configuration.configuration.attributeUrl;

    this.canEditShownAttributes = getOrDefault(
      configuration.configuration.editShownAttributes,
      false
    );
    if (this.canEditShownAttributes) {
      this.attributesLocalstorageEntry = this.localStorageService.getLocalStorageEntry(
        this._id,
        Scope.GLOBAL,
        DeletionMode.RESET
      );
      if (this.attributesLocalstorageEntry.exists()) {
        this.attributes = JSON.parse(this.attributesLocalstorageEntry.value);
        this.recoverChangedAttributes();
      }
    }
    this.allowRange = getOrDefault(
      configuration.configuration["allowRange"],
      false
    );

    this.translateAttributeDescriptions =
      configuration.configuration.translateAttributeDescriptions || false;
    if (this.localStorageVisibiltyEntry.exists()) {
      this.contentVisible = this.localStorageVisibiltyEntry.value === "true";
    }

    this.multiLookupsAllowed = getOrDefault(
      configuration.configuration.multiLookupsAllowed,
      false
    );

    this.data.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
      const missingAttributes = [];
      let foundAttributes = [];
      Object.keys(data).forEach((key) => {
        const attribute = this.attributes.find((att) => att.identifier == key);
        if (attribute) {
          if (attribute.source && attribute.source[0]) {
            attribute.source[0] = {};
          }
          foundAttributes.push(attribute);
        } else {
          missingAttributes.push(key);
        }
      });
      if (missingAttributes.length !== 0) {
        this.appStore
          .getAppdata()
          .pipe(
            map((data) => data.ipim._links.attributes.href),
            map(
              (data) =>
                data +
                '?filter=identifier:in:"' +
                missingAttributes.join(",") +
                '"'
            ),
            flatMap((data) => this._widgetframeService.getData(data)),
            map((data) => data._embedded.attributes)
          )
          .subscribe((entries) => {
            foundAttributes = foundAttributes.concat(entries);
            this.recoverFromData(data, foundAttributes);
          });
      } else {
        this.recoverFromData(data, foundAttributes);
      }
    });

    this.reset.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
      this.changedAttributes = [];
      this.changedAttributesOutput.next(this.changedAttributes);

      if (this.localstorageAttributesEntry.exists()) {
        this.localstorageAttributesEntry.clear();
      }

      if (this.attributesorg) {
        this.attributes = jQuery.extend(true, [], this.attributesorg);
      } else {
        if (this.attributes) {
          this.attributes.forEach((attribute) => {
            if (attribute.source && attribute.source[0]) {
              attribute.source[0] = {};
            }
          });
          this.attributes = jQuery.extend(true, [], this.attributes);
        }
      }
    });

    this._editAttributeService
      .getAttributeChangedEvent()
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((attribute) => {
        const dashIdx = attribute.identifier.indexOf("-");
        // The original attribute that was changed, needed to save the from / to
        let originalAttribute;
        let from;
        if (dashIdx !== -1) {
          const rootAttributeName =
            dashIdx === -1 ? null : attribute.identifier.substring(0, dashIdx);
          const att = this.attributes.find(
            (att) => att.identifier === rootAttributeName
          );
          const sub = attribute.identifier.substring(dashIdx + 1);
          originalAttribute = attribute;
          attribute = att;

          from = sub === "from";
        }

        let dublicateIndex = this.changedAttributes.findIndex(
          (item) => item.identifier == attribute.identifier
        );
        if (dublicateIndex !== -1) {
          this.changedAttributes.splice(dublicateIndex, 1);
          const isEmpty = this.checkIsAttributeEmpty(attribute);
          // The attribute and all of its fake range child attributes are not empty, we can readd it to the changed attributes
          if (!isEmpty) {
            this.changedAttributes.push(attribute);
          }
        } else {
          // THere is no duplicate, we are save to add this to the changed attributes
          this.changedAttributes.push(attribute);
        }

        this.localstorageAttributesEntry.value = JSON.stringify(
          this.changedAttributes
        );
        this.changedAttributesOutput.next(this.changedAttributes);
        this.changedValueByUser.next(attribute);
      });

    this.attributeUrl
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => (this.selectAttributeUrl = data));

    let source = observableCombineLatest(
      this.uri.asObservable(),
      this.reload.asObservable().pipe(distinctUntilChanged()),
      function (uri, reloadEvent) {
        let template = uriTemplates(uri);
        let uriParams = {};
        // uriParams["context"] = configuration.configuration["context"];
        uriParams["locale"] = reloadEvent;
        this.currentLocale = reloadEvent;
        return template.fill(uriParams);
      }
    ).pipe(
      takeUntil(this.unsubscribe),
      mergeMap((href) =>
        this._widgetframeService
          .getData(href)
          .pipe(onErrorResumeNext(this.resetWidget()))
      )
    );

    source.subscribe(
      (data) => {
        for (const attribute of <any>data["_embedded"]["attributes"]) {
          if (!this.multiLookupsAllowed && attribute.type === "MULTI_LOOKUP") {
            attribute.type = "LOOKUP";
          }
        }
        this.attributes = jQuery.extend(
          true,
          [],
          data["_embedded"]["attributes"]
        );
        this.attributesorg = jQuery.extend(
          true,
          [],
          data["_embedded"]["attributes"]
        );

        this.recoverChangedAttributes();
      },
      (error) => {
        console.log(error);
        this.attributes = null;
      }
    );

    this.reload.asObservable().subscribe((data) => {
      this.currentLocale = data;
    });
  }

  private recoverFromData(data, attributes: Attribute[]) {
    this.attributes = [];
    this.changedAttributes = [];

    Object.keys(data).forEach((key) => {
      // We need to deep copy the object, so that the angular change detection will still work while in OnPush (we need to replace the reference)
      const attribute = deepCopy(
        attributes.find((attribute) => attribute.identifier == key)
      );
      attribute.source = [{ value: data[key] }];
      this.attributes.push(attribute);
      this.changedAttributes.push(attribute);
    });
    this.changedAttributesOutput.next(this.changedAttributes);
    if (this.attributesLocalstorageEntry) {
      this.attributesLocalstorageEntry.value = JSON.stringify(this.attributes);
    }
    this.localstorageAttributesEntry.value = JSON.stringify(
      this.changedAttributes
    );
  }

  private checkIsAttributeEmpty(attribute: Attribute): boolean {
    const isRootEmpty =
      !attribute.source[0].value ||
      attribute.source[0].value === "" ||
      attribute.source.length === 0;
    // The root is not empty, it cant be empty
    if (!isRootEmpty) {
      return false;
    }
    // The root is empty and it has no range attributes, it must be empty
    if (!attribute.from) {
      return true;
    }
    //Check if the children are empty
    else
      return (
        this.checkIsAttributeEmpty(attribute.from) &&
        this.checkIsAttributeEmpty(attribute.to)
      );
  }

  private recoverChangedAttributes() {
    if (this.localstorageAttributesEntry.exists()) {
      this.changedAttributes = JSON.parse(
        this.localstorageAttributesEntry.value
      );
      for (const changedAttribute of this.changedAttributes) {
        let dublicate = this.attributes.filter(
          (item) => item.identifier == changedAttribute.identifier
        );
        if (dublicate.length === 1) {
          dublicate[0].source = changedAttribute.source;
          if (changedAttribute.from) {
            dublicate[0].from = changedAttribute.from;
            dublicate[0].to = changedAttribute.to;
            dublicate[0].displayRange = changedAttribute.displayRange;
          }
        }
      }
      this.changedAttributesOutput.next(this.changedAttributes);
    }
  }

  resetWidget() {
    return null;
  }

  ngOnDestroy() {
    this.unsubscribe.destroy();
  }

  toogleContentVisibility() {
    this.contentVisible = !this.contentVisible;

    this.localStorageVisibiltyEntry.value = this.contentVisible.toString();
  }

  public editShownAttributes() {
    let dialogRef = this.dialogService.open(SelectAttributesDialogComponent, {
      minWidth: "900px",
      maxWidth: "900px",
      height: "755px",
    });
    dialogRef.componentInstance.preselectedAttributes = this.attributes || [];
    dialogRef.componentInstance.currentLocale = this.currentLocale;
    dialogRef.componentInstance.attributeUrl = throwIfUndefined(
      this.selectAttributeUrl
    );
    dialogRef.componentInstance.infoText = "infoText.select.attributes";
    dialogRef.afterClosed().subscribe((data) => {
      if (data) {
        let changedAttributesChanged = false;
        var i = this.changedAttributes.length;
        // If we have a changed attribute that is no longer editable we need to remove it from the list of changedattributes and output the changed attributes
        while (i--) {
          const entry = this.changedAttributes[i];
          const attribute = data.find(
            (val) => val.identifier === entry.identifier
          );
          if (!attribute) {
            this.changedAttributes.splice(i, 1);
            changedAttributesChanged = true;
          }
        }
        if (changedAttributesChanged) {
          this.changedAttributesOutput.next(this.changedAttributes);
        }

        if (this.attributes) {
          data.forEach((entry) => {
            if (!this.multiLookupsAllowed && entry.type === "MULTI_LOOKUP") {
              entry.type = "LOOKUP";
            }
            const value = this.attributes.find(
              (att) => att.identifier === entry.identifier
            );
            if (value) {
              entry.source = value.source;
            }
          });
        }
        this.attributes = data;
        this.attributesLocalstorageEntry.value = JSON.stringify(data);
      }
    });
  }

  showLabel(attribute) {
    return !(this.allowRange && this.attributeSupportsRange(attribute));
  }

  private attributeSupportsRange(attribute) {
    return attribute.type !== "BOOLEAN";
  }

  public rangeChange(event, attribute: Attribute) {
    attribute.displayRange = event.checked;
    if (attribute.from) {
      //Make sure that the attribute and the change attribute instance are === so that changes to displayRange will be copied to the changed attribute (needed for tooltip)
    } else if (event.checked) {
      // This will deep copy the source of the root to from and to! If this is a problem we need to cleanse the source before / while copping
      const fromClone: Attribute = JSON.parse(JSON.stringify(attribute));
      const toClone = JSON.parse(JSON.stringify(attribute));
      fromClone.identifier = fromClone.identifier + "-from";
      toClone.identifier = toClone.identifier + "-to";
      attribute.from = fromClone;
      attribute.to = toClone;
    }
    // Emit change, since changing the slider changes the search
    this.changedAttributesOutput.next(this.changedAttributes);
  }

  public getChangedAttributesForHeader() {
    const result = [];
    this.changedAttributes.forEach((value) => {
      if (value.displayRange) {
        let from;
        let to;
        if (value.from.source[0] && value.from.source[0].value) {
          from = value.from.source[0].value;
        }
        if (value.to.source[0] && value.to.source[0].value) {
          to = value.to.source[0].value;
        }
        let description = from ? from : "?";
        if (to) {
          description += " - " + to;
        }
        const fakeValue = {
          description: value.description,
          source: [
            {
              description,
            },
          ],
        };
        result.push(fakeValue);
      } else {
        result.push(value);
      }
    });
    return result;
  }
}

results matching ""

    No results matching ""