@WidgetComponent

nm-search-advanced

File

src/app/shared/widgets/search/advanced/search-advanced.component.ts

Implements

OnInit OnChanges AfterViewInit

Metadata

providers EditAttributeService LookupService
selector nm-search-advanced
styleUrls search-advanced.component.scss
templateUrl ./search-advanced.component.html

Index

Widget inputs
Widget outputs
Properties
Methods
Inputs
Outputs

Constructor

constructor(localstorageService: LocalStorageService, widgetframeService: WidgetframeService, dialogService: DialogService, translateService: TranslateService, datetimeAdapter: DateTimeAdapter, _editAttributeService: EditAttributeService, lookupService: LookupService, cdr: ChangeDetectorRef, _scrollService: ScrollService)
Parameters :
Name Type Optional
localstorageService LocalStorageService no
widgetframeService WidgetframeService no
dialogService DialogService no
translateService TranslateService no
datetimeAdapter DateTimeAdapter<any> no
_editAttributeService EditAttributeService no
lookupService LookupService no
cdr ChangeDetectorRef no
_scrollService ScrollService no

Inputs

attributeData

Used to update missing/existing attributes.

Type: ReplaySubject

Default value: new ReplaySubject<any>(1)

attributeTypesToAddMultiple

Enables adding an item several times in search attributes dialog.

Type: string[]

attributeUrl

Url for fetching attributes.

dataInput

Used to transfer search form fields to dataOutput channel.

Type: Subject

Default value: new Subject()

dialogWithActionMenu

Enables/disables action menu for setting the default attributes

Type: boolean

Default value: false

dynamicHeight

Enables/disables dynamicHeight calculation.

Type: boolean

Default value: true

dynamicHeightAdditionalHeight

Viewport height in px that is not covered by attribute-container.

Type: string

Default value: "592px"

enableRoleBasedSearchTemplates

enable to create role-based search templates

Type: boolean

Default value: false

fields

Pre-defined static fields.

Type: FormWidgetField[]

fieldsTemplate

Used to update search form fields according to selected search favorite template.

Type: Subject

Default value: new Subject()

itemHeight

Sets height for a single attribute field.

Type: string

Default value: "44px"

links

Urls for fetching needed data. i.e: lookup values, search modes.

listHeight

Sets height for attribute fields list.

Type: string

Default value: "340px"

listsearch

Enables/disables list search mode.

Type: boolean

Default value: false

locale

used for loading lookups.

lookups

Pre-defined static lookup fields.

prefix

Local storage prefix for widget wrapping the component.

Default value: "search"

reloadLookups

Reloads lookup fields.

Type: ReplaySubject

Default value: new ReplaySubject<any>(1)

reset

Triggers reset of search filters.

Type: Subject

Default value: new Subject()

resetFavorites

Resets favorites value.

Type: Subject

Default value: new Subject()

searchFavorites

Enables/disables search favorites.

Type: boolean

searchModeChanged

Gets changes of search mode

Type: ReplaySubject

Default value: new ReplaySubject<any>(1)

searchTemplatesUri

Used to get search favorites templates.

Default value: "/api/core/search-templates"

selectFilterParams

Used to set attribute scopes in search attributes dialog.

Type: SelectFilterParams

Outputs

attributes

Emits changed attributes.

$event type: ReplaySubject
dataOutput

Emits search form fields to initialize search favorites dialog.

$event type: Subject
expandToggle

used for toggle-expanded-event

$event type: Subject
listvalueOutput

List value output in list search mode.

$event type: ReplaySubject
search

Emits form values.

$event type: Subject
templateOutput

Emits search template parameter.

$event type: ReplaySubject

Methods

Public attributeSupportsRange
attributeSupportsRange(attribute: )
Parameters :
Name Optional
attribute no
Returns : boolean
Private buildFieldsAndAttributes
buildFieldsAndAttributes()
Returns : void
Private checkIsAttributeEmpty
checkIsAttributeEmpty(attribute: Attribute)
Parameters :
Name Type Optional
attribute Attribute no
Returns : boolean
editAttribute
editAttribute(attribute: Attribute)
Parameters :
Name Type Optional
attribute Attribute no
Returns : void
editField
editField(field: FormWidgetField)
Parameters :
Name Type Optional
field FormWidgetField no
Returns : void
Public formcontrolFor
formcontrolFor(field: FormWidgetField)
Parameters :
Name Type Optional
field FormWidgetField no
Returns : any
Public formcontrolNameFor
formcontrolNameFor(field: FormWidgetField)
Parameters :
Name Type Optional
field FormWidgetField no
Returns : string
Public getHeight
getHeight()
Returns : any
getLookups
getLookups(key: )
Parameters :
Name Optional
key no
Returns : any[]
Public getTooltip
getTooltip(attribute: Attribute)
Parameters :
Name Type Optional
attribute Attribute no
Returns : string
Public getTooltipValue
getTooltipValue(field: FormWidgetField)
Parameters :
Name Type Optional
field FormWidgetField no
Returns : any
Public getTooltipValueForAttribute
getTooltipValueForAttribute(attribute: Attribute)
Parameters :
Name Type Optional
attribute Attribute no
Returns : any
Public includedInTooltip
includedInTooltip(field: FormWidgetField)
Parameters :
Name Type Optional
field FormWidgetField no
Returns : any
Public isFieldVisible
isFieldVisible(field: FormWidgetField)
Parameters :
Name Type Optional
field FormWidgetField no
Returns : any
Private loadLookups
loadLookups(locale: )
Parameters :
Name Optional
locale no
Returns : void
Private mapToMultiLookup
mapToMultiLookup(attribute: )
Parameters :
Name Optional
attribute no
Returns : void
ngAfterViewInit
ngAfterViewInit()
Returns : void
ngOnChanges
ngOnChanges(changes: SimpleChanges)
Parameters :
Name Type Optional
changes SimpleChanges no
Returns : void
ngOnInit
ngOnInit()
Returns : void
Public onInputEvent
onInputEvent(event: FormEventPayload)
Parameters :
Name Type Optional
event FormEventPayload no
Returns : void
Public openAttributeSelection
openAttributeSelection()
Returns : void
Private rangeAllowed
rangeAllowed(field: FormWidgetField)
Parameters :
Name Type Optional
field FormWidgetField no
Returns : boolean
rangeChange
rangeChange(event: any, field: FormWidgetField)
Parameters :
Name Type Optional
event any no
field FormWidgetField no
Returns : void
Private recoverChangedAttributes
recoverChangedAttributes()
Returns : void
Private recoverFormValue
recoverFormValue()
Returns : void
Private recoverFromData
recoverFromData(data: Attribute[], attributes: Attribute[])
Parameters :
Name Type Optional
data Attribute[] no
attributes Attribute[] no
Returns : void
Public save
save()
Returns : void
Private setFormValue
setFormValue(value: )
Parameters :
Name Optional
value no
Returns : void
Public showLabel
showLabel(attribute: )
Parameters :
Name Optional
attribute no
Returns : boolean
Public submit
submit()
Returns : void
templateChanged
templateChanged(template: any)
Parameters :
Name Type Optional
template any no
Returns : void
toggleExpanded
toggleExpanded()
Returns : void
Private updateIgxForContainerHeight
updateIgxForContainerHeight()
Returns : void
Private updateLookups
updateLookups(keys: string[], locale: )
Parameters :
Name Type Optional
keys string[] no
locale no
Returns : void

Properties

Public _editAttributeService
_editAttributeService: EditAttributeService
Type : EditAttributeService
Public attributes
attributes: Attribute[]
Type : Attribute[]
Default value : []
Private attributesEntry
attributesEntry: LocalStorageEntry
Type : LocalStorageEntry
Public changedAttributes
changedAttributes: []
Type : []
Default value : []
Private changedAttributesEntry
changedAttributesEntry: LocalStorageEntry
Type : LocalStorageEntry
Public containerHeight
containerHeight: string
Type : string
Public containerHeightPx
containerHeightPx: string
Type : string
Public currentLocale
currentLocale:
Public expanded
expanded: boolean
Type : boolean
Default value : true
Private expandedEntry
expandedEntry: LocalStorageEntry
Type : LocalStorageEntry
Private favoritesChanged
favoritesChanged: boolean
Type : boolean
Default value : false
Public fieldsAndAttributes
fieldsAndAttributes: []
Type : []
Default value : []
Public fieldsVisible
fieldsVisible: object
Type : object
Default value : {}
Private fieldsVisibleEntry
fieldsVisibleEntry: LocalStorageEntry
Type : LocalStorageEntry
Public fieldsVisibleFavorite
fieldsVisibleFavorite:
Private fieldsVisibleFavoriteEntry
fieldsVisibleFavoriteEntry: LocalStorageEntry
Type : LocalStorageEntry
Private forceEmit
forceEmit:
Default value : new Subject<void>()
Public form
form: FormGroup
Type : FormGroup
Private formValueEntry
formValueEntry: LocalStorageEntry
Type : LocalStorageEntry
Public formValues
formValues: object
Type : object
Default value : {}
Public hasSystemAttributes
hasSystemAttributes: boolean
Type : boolean
Default value : false
Private ignoreChanges
ignoreChanges: boolean
Type : boolean
Default value : false
Private localeChannel
localeChannel: BehaviorSubject<any>
Type : BehaviorSubject<any>
Default value : new BehaviorSubject<any>( "de-DE" )
Public lookups
lookups: object
Type : object
Default value : {}
Public rangeModes
rangeModes: object
Type : object
Default value : {}
Private rangeModesEntry
rangeModesEntry: LocalStorageEntry
Type : LocalStorageEntry
Public uniqueID
uniqueID: string
Type : string
Default value : "identifier"
Private unsubscribe
unsubscribe:
Default value : NgUnsubscribe.create()

Accessors

locale
setlocale(value: )

used for loading lookups.

Parameters :
Name Optional
value no
Returns : void
import {
  AfterViewInit,
  ChangeDetectorRef,
  Component,
  HostListener,
  Input,
  OnChanges,
  OnInit,
  Output,
  SimpleChanges,
} from "@angular/core";
import { EditAttributeService } from "../../../components/edit-attribute/edit-attribute.service";

import {
  DeletionMode,
  Scope,
} from "../../../components/local-storage/local-storage-constants";
import {
  LocalStorageEntry,
  LocalStorageService,
} from "../../../components/local-storage/local-storage.service";
import { FormControl, FormGroup } from "@angular/forms";
import { debounceTime, filter, map, take, takeUntil } from "rxjs/operators";
import { NgUnsubscribe } from "../../../ng-unsubscribe";
import {
  angularWidgetBridgeInput,
  calcHeight,
  createUID,
  deepCopy,
  notNullOrEmpty,
} from "../../../components/util/util.service";
import { WidgetframeService } from "../../widgetframe/widgetframe.service";
import { MatDialog } from "@angular/material/dialog";
import { SearchAttributeSelectionDialog } from "../attribute-selection/search-attribute-selection.component";
import * as _ from "lodash";
import { TranslateService } from "@ngx-translate/core";
import { BehaviorSubject, combineLatest, ReplaySubject, Subject } from "rxjs";
import { DateTimeAdapter } from "ng-pick-datetime";
import { SearchAttributeConfigurationDialog } from "../attribute-configuration/search-attribute-configuration.component";
import * as uriTemplates_ from "uri-templates";
import { Attribute } from "../../../components/edit-attribute/attribute";
import { SelectFilterParams } from "../../interfaces/list.interfaces";
import { LookupService } from "../../../components/lookup/lookup.service";
import { ScrollService } from "../../../components/scroll";
import { DialogService } from "../../../components/dialog";

const uriTemplates = uriTemplates_;

export class FormWidgetField {
  identifier: string;
  description: string;
  type: string;
  lookupSource?: string;
  /**
   * `true` enables the range selection, `false` disables it
   *
   * @deprecated use {@link range} instead
   */
  allowRange?: boolean;

  /**
   * modifies the range selection behaviour
   * `always` restricts the field to always have a range selection,
   * `choose` allows the user to switch between the modes,
   * while `never` only allows for single value search
   */
  range: "never" | "choose" | "always";
  defaultVisible?: boolean;
  eventField?: boolean;
  default?: string;
  basic?: boolean;
  formatDate?: boolean;
  min?: number | null;
  max?: number | null;
  autofocus?: boolean;
}

export class FormLookup {
  label: string;
  collectionName: string;
  valueField: string;
}

export class FormEventPayload {
  field: string;
  payload: any;
}

const RANGE_ATTRIBUTES = [
  "PLAIN_STRING",
  "INTEGER_NUMBER",
  "DATE",
  "DATE_TIME",
  "PERCENTAGE",
  "DECIMAL_NUMBER",
];

@Component({
  selector: "nm-search-advanced",
  templateUrl: "./search-advanced.component.html",
  styleUrls: ["./search-advanced.component.scss"],
  providers: [EditAttributeService, LookupService],
})
export class SearchAdvancedComponent
  implements OnInit, OnChanges, AfterViewInit
{
  /**
   * Url for fetching attributes.
   */
  @Input() public attributeUrl;

  /**
   * Local storage prefix for widget wrapping the component.
   */
  @Input() public prefix = "search";

  /**
   * Pre-defined static fields.
   */
  @Input() public fields: FormWidgetField[];

  /**
   * Urls for fetching needed data. i.e: lookup values, search modes.
   */
  @Input() public links;

  /**
   * Used to set attribute scopes in search attributes dialog.
   */
  @Input() public selectFilterParams: SelectFilterParams;

  /**
   * Triggers reset of search filters.
   */
  @Input() public reset = new Subject();

  /**
   * Pre-defined static lookup fields.
   */
  @Input("lookups") public lookupsInput;

  /**
   * Used to get search favorites templates.
   */
  @Input() public searchTemplatesUri = "/api/core/search-templates";

  /**
   * Used to update search form fields according to selected search favorite template.
   */
  @Input() public fieldsTemplate = new Subject();

  /**
   * Used to transfer search form fields to dataOutput channel.
   */
  @Input() public dataInput = new Subject();

  /**
   * Enables/disables search favorites.
   */
  @Input() public searchFavorites: boolean;

  /**
   * Enables adding an item several times in search attributes dialog.
   */
  @Input() public attributeTypesToAddMultiple: string[];

  /**
   * Emits search form fields to initialize search favorites dialog.
   */
  @Output() public dataOutput = new Subject();

  /**
   * Emits changed attributes.
   */
  @Output("attributes") public attributesOutput = new ReplaySubject<any>(1);

  /**
   * Emits form values.
   */
  @Output("search") public change = new Subject();

  /**
   * Resets favorites value.
   */
  @Input() public resetFavorites = new Subject();

  /**
   * Used to update missing/existing attributes.
   */
  @Input("attributeData") public attributeData = new ReplaySubject<any>(1);

  /**
   * Reloads lookup fields.
   */
  @Input() public reloadLookups = new ReplaySubject<any>(1);

  /**
   * Sets height for attribute fields list.
   */
  @Input() public listHeight: string = "340px";

  /**
   * Sets height for a single attribute field.
   */
  @Input() public itemHeight: string = "44px";

  /**
   * Enables/disables list search mode.
   */
  @Input() public listsearch: boolean = false;

  /**
   * Gets changes of search mode
   */
  @Input() public searchModeChanged = new ReplaySubject<any>(1);

  /**
   * Enables/disables action menu for setting the default attributes
   */
  @Input() public dialogWithActionMenu: boolean = false;

  /**
   * List value output in list search mode.
   */
  @Output() public listvalueOutput = new ReplaySubject<any>(1);

  /**
   * Emits search template parameter.
   */
  @Output() public templateOutput = new ReplaySubject<any>(1);

  /**
   * used for toggle-expanded-event
   */
  @Output("expandToggle")
  public expandedToggle = new Subject();

  // Array that combines fields and attributes so we can iterate them in an igxFor
  public fieldsAndAttributes = [];

  private localeChannel: BehaviorSubject<any> = new BehaviorSubject<any>(
    "de-DE"
  );

  /**
   * used for loading lookups.
   */
  @Input("locale")
  public set locale(value) {
    angularWidgetBridgeInput(value, this.localeChannel, this.unsubscribe);
  }

  /**
   * Enables/disables dynamicHeight calculation.
   */
  @Input() public dynamicHeight: boolean = true;

  /**
   * Viewport height in px that is not covered by attribute-container.
   */
  @Input() public dynamicHeightAdditionalHeight: string = "592px";

  /**
   * enable to create role-based search templates
   */
  @Input() public enableRoleBasedSearchTemplates: boolean = false;

  public expanded: boolean = true;
  public hasSystemAttributes: boolean = false;
  public changedAttributes = [];
  public form: FormGroup;
  public formValues = {};
  public lookups = {};
  public currentLocale;
  public attributes: Attribute[] = [];
  public rangeModes = {};
  public fieldsVisible = {};
  public fieldsVisibleFavorite;
  public uniqueID: string = "identifier";

  private unsubscribe = NgUnsubscribe.create();
  private forceEmit = new Subject<void>();
  private ignoreChanges: boolean = false;

  private expandedEntry: LocalStorageEntry;
  private fieldsVisibleEntry: LocalStorageEntry;
  private fieldsVisibleFavoriteEntry: LocalStorageEntry;
  private attributesEntry: LocalStorageEntry;
  private changedAttributesEntry: LocalStorageEntry;
  private rangeModesEntry: LocalStorageEntry;
  private formValueEntry: LocalStorageEntry;
  private favoritesChanged: boolean = false;

  public containerHeight: string;
  public containerHeightPx: string;

  constructor(
    private localstorageService: LocalStorageService,
    private widgetframeService: WidgetframeService,
    private dialogService: DialogService,
    private translateService: TranslateService,
    private datetimeAdapter: DateTimeAdapter<any>,
    public _editAttributeService: EditAttributeService,
    private lookupService: LookupService,
    private cdr: ChangeDetectorRef,
    private _scrollService: ScrollService
  ) {
    this.expandedEntry = localstorageService.getLocalStorageEntry(
      this.prefix + "search-advanced-expanded",
      Scope.GLOBAL,
      DeletionMode.RESET
    );
    this.fieldsVisibleEntry = localstorageService.getLocalStorageEntry(
      this.prefix + "search-advanced-fields-visible",
      Scope.GLOBAL,
      DeletionMode.RESET
    );
    this.fieldsVisibleFavoriteEntry = localstorageService.getLocalStorageEntry(
      this.prefix + "search-advanced-fields-visible-favorite",
      Scope.GLOBAL,
      DeletionMode.RESET
    );
    this.attributesEntry = localstorageService.getLocalStorageEntry(
      this.prefix + "search-advanced-attributes",
      Scope.GLOBAL,
      DeletionMode.RESET
    );
    this.changedAttributesEntry = localstorageService.getLocalStorageEntry(
      this.prefix + "search-advanced-changed-attributes",
      Scope.GLOBAL,
      DeletionMode.RESET
    );
    this.rangeModesEntry = localstorageService.getLocalStorageEntry(
      this.prefix + "search-advanced-range-modes",
      Scope.GLOBAL,
      DeletionMode.RESET
    );
    this.formValueEntry = localstorageService.getLocalStorageEntry(
      this.prefix + "search-advanced-form-value",
      Scope.GLOBAL,
      DeletionMode.RESET
    );

    this._scrollService
      .getChangeViewPortSize()
      .pipe(takeUntil(this.unsubscribe))
      .subscribe(() =>
        requestAnimationFrame(() => this.updateIgxForContainerHeight())
      );
  }

  private rangeAllowed(field: FormWidgetField): boolean {
    return field.range === "always" || field.range === "choose";
  }

  private buildFieldsAndAttributes() {
    this.fieldsAndAttributes = [];
    this.fieldsAndAttributes.push(...this.attributes);
    this.fieldsAndAttributes.push(
      ...this.fields.filter((field) => {
        return (
          this.isFieldVisible(field) &&
          !field.basic &&
          field.identifier !== "searchMode"
        );
      })
    );
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.fields) {
      this.fields.forEach((field) => {
        (field as any).isField = true;

        if (!field.hasOwnProperty("range")) {
          if (field.hasOwnProperty("allowRange")) {
            field.range = field.allowRange ? "choose" : "never";
            console.warn(
              "'allowRange=true|false' is deprecated, use 'range=never|choose|always' instead "
            );
          } else {
            field.range = "never";
          }
        }
      });
    }
  }

  ngOnInit(): void {
    if (this.attributeTypesToAddMultiple) {
      this.uniqueID = "uid";
    }

    this.dataInput
      .pipe(debounceTime(1000), takeUntil(this.unsubscribe))
      .subscribe((value: any) => {
        value.visibleFields = Object.keys(this.fieldsVisible).filter(
          (key) => this.fieldsVisible[key]
        );
        this.dataOutput.next(value);
      });

    if (this.attributesEntry.exists()) {
      this.attributes = JSON.parse(this.attributesEntry.value);
      this.recoverChangedAttributes();
    }

    if (this.rangeModesEntry.exists()) {
      this.rangeModes = JSON.parse(this.rangeModesEntry.value);
    }

    this.attributeData.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
      const missingAttributes = [];
      let foundAttributes = [];
      data.forEach((dto) => {
        const attribute = this.attributes.find(
          (att) => att.identifier == dto.identifier
        );
        if (attribute) {
          if (attribute.source && attribute.source[0]) {
            attribute.source[0] = {};
          }
          this.mapToMultiLookup(attribute);
          foundAttributes.push(attribute);
        } else {
          missingAttributes.push(dto.identifier);
        }
      });
      if (missingAttributes.length !== 0) {
        // TODO : , map(data => data + '?filter=identifier:in:"' + missingAttributes.join(',') + '"'),
        this.widgetframeService
          .getData(this.attributeUrl)
          .pipe(map((data) => data["_embedded"]["attributes"]))
          .subscribe((attributes) => {
            attributes.forEach((attribute) => {
              this.mapToMultiLookup(attribute);
            });
            foundAttributes = foundAttributes.concat(attributes);
            this.recoverFromData(data, foundAttributes);
          });
      } else {
        this.recoverFromData(data, foundAttributes);
      }
    });

    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 && att.uid === attribute.uid
          );
          const sub = attribute.identifier.substring(dashIdx + 1);
          originalAttribute = attribute;
          attribute = att;

          from = sub === "from";
        }

        let dublicateIndex = this.changedAttributes.findIndex(
          (item) => item[this.uniqueID] == attribute[this.uniqueID]
        );

        if (dublicateIndex !== -1) {
          this.changedAttributes.splice(dublicateIndex, 1);
        }
        this.changedAttributes.push(attribute);

        this.changedAttributesEntry.value = JSON.stringify(
          this.changedAttributes
        );
        this.resetFavorites.next();
        this.attributesOutput.next(this.changedAttributes);
      });

    this.localeChannel
      .asObservable()
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((locale) => {
        if (locale) {
          this.loadLookups(locale);

          if (this.attributes && this.attributes.length > 0) {
            // reload the attribute descriptions
            this.widgetframeService
              .getData(
                this.attributeUrl +
                  '&filter=identifier:in:"' +
                  this.attributes.map((attr) => attr.identifier).join(",") +
                  '"'
              )
              .pipe(map((data) => data["_embedded"]["attributes"]))
              .subscribe((attributes) => {
                attributes.forEach((backendAttribute) => {
                  var attribute = this.attributes.find(
                    (frontendAttribute) =>
                      frontendAttribute.identifier ===
                      backendAttribute.identifier
                  );
                  if (attribute) {
                    attribute.description = backendAttribute.description;
                  }
                  attribute = this.changedAttributes.find(
                    (frontendAttribute) =>
                      frontendAttribute.identifier ===
                      backendAttribute.identifier
                  );
                  if (attribute) {
                    attribute.description = backendAttribute.description;
                  }
                });
                // update the local storage
                this.changedAttributesEntry.value = JSON.stringify(
                  this.changedAttributes
                );
                this.attributesEntry.value = JSON.stringify(this.attributes);
              });
          }
        }
      });

    this.reloadLookups
      .asObservable()
      .pipe(takeUntil(this.unsubscribe), debounceTime(1))
      .subscribe((lookups) => {
        this.updateLookups(lookups, this.localeChannel.value);
      });

    let controls = {};
    this.fields.forEach((field) => {
      // overwrite loaded state from local storage
      if (field.range === "always") {
        this.rangeModes[field.identifier] = true;
      }

      if (this.rangeAllowed(field)) {
        controls[field.identifier + "From"] = new FormControl("");
        controls[field.identifier + "Until"] = new FormControl("");
      } else {
        controls[field.identifier] = new FormControl(field.default);
      }
    });

    this.form = new FormGroup(controls);

    combineLatest(this.form.valueChanges, this.forceEmit.asObservable())
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((values) => {
        if (!this.ignoreChanges) {
          if (!this.favoritesChanged) {
            this.resetFavorites.next();
          }

          this.favoritesChanged = false;
          let data = values[0];
          this.formValueEntry.value = JSON.stringify(data);
          this.formValues = data;
          this.hasSystemAttributes = false;
          data = deepCopy(data);
          const visitedFields = new Set();
          this.fields
            .filter((entry) => !entry.basic)
            .forEach((field) => {
              if (
                this.formValues[field.identifier] &&
                field.type === "lookup"
              ) {
                let lookups =
                  this.lookups[this.currentLocale][field.lookupSource];
                for (const lookup of lookups) {
                  if (this.formValues[field.identifier] === lookup.value) {
                    this.formValues[field.identifier] = lookup.label;
                  }
                }
              }
              visitedFields.add(field.identifier);
              visitedFields.add(field.identifier + "Until");
              visitedFields.add(field.identifier + "From");

              if (!this.isFieldVisible(field)) {
                delete data[field.identifier];
                delete data[field.identifier + "Until"];
                delete data[field.identifier + "From"];
              }
              // If we are not in range mode dont send the range until field
              // but duplicate the from field to allow searching for absolute values
              if (this.rangeAllowed(field)) {
                if (!this.rangeModes[field.identifier]) {
                  data[field.identifier] = data[field.identifier + "From"];
                  delete data[field.identifier + "Until"];
                }
              }

              if (
                this.isFieldVisible(field) &&
                field.identifier.indexOf("articleNo") === -1 &&
                field.identifier !== "description" &&
                field.identifier !== "searchModes"
              ) {
                if (data[field.identifier]) {
                  this.hasSystemAttributes = true;
                } else if (this.rangeAllowed(field)) {
                  if (
                    data[field.identifier + "From"] ||
                    data[field.identifier + "From"]
                  ) {
                    this.hasSystemAttributes = true;
                  }
                }
              }
            });
          Object.keys(data)
            .filter((key) => !visitedFields.has(key))
            .forEach((key) => {
              delete data[key];
            });
          this.change.next(data);
        }
      });

    this.forceEmit.next();
    if (this.fieldsVisibleEntry.exists()) {
      this.fieldsVisible = JSON.parse(this.fieldsVisibleEntry.value);
    } else {
      this.fields.forEach((field) => {
        if (field.defaultVisible === false) {
          this.fieldsVisible[field.identifier] = false;
        } else {
          this.fieldsVisible[field.identifier] = true;
        }
      });
    }
    if (this.fieldsVisibleFavoriteEntry.exists()) {
      this.fieldsVisibleFavorite = JSON.parse(
        this.fieldsVisibleFavoriteEntry.value
      );
    } else {
      this.fieldsVisibleFavorite = this.fields.filter(
        (entry) => entry.defaultVisible !== false
      );
    }

    this.reset.pipe(takeUntil(this.unsubscribe)).subscribe(() => {
      this.formValueEntry.value = "{}";
      const formValue = {};
      this.ignoreChanges = true;
      this.fields.forEach((field) => {
        if (field.default) {
          formValue[field.identifier] = field.default;
        } else {
          if (notNullOrEmpty(field.identifier + "From")) {
            formValue[field.identifier + "From"] = null;
          }
          if (notNullOrEmpty(field.identifier + "Until")) {
            formValue[field.identifier + "Until"] = null;
          }
          formValue[field.identifier] = null;
        }
        this.setFormValue(formValue);
      });

      if (this.attributesEntry.exists()) {
        this.attributes = JSON.parse(this.attributesEntry.value);
        this.recoverChangedAttributes();
        this.attributes.forEach((attribute) => {
          if (attribute.source && attribute.source.length) {
            attribute.source = [{}];
            this._editAttributeService.setTriggerSourceRefreshEvent(attribute);
          }

          if (attribute.from) {
            if (attribute.from.source && attribute.from.source.length) {
              attribute.from.source[0].value = null;
            }
            this._editAttributeService.setTriggerSourceRefreshEvent(
              attribute.from
            );
          }

          if (attribute.to) {
            if (attribute.to.source && attribute.to.source.length) {
              attribute.to.source[0].value = null;
            }
            this._editAttributeService.setTriggerSourceRefreshEvent(
              attribute.to
            );
          }
        });
        this.attributesEntry.value = JSON.stringify(this.attributes);
      }

      this.changedAttributes = [];
      this.changedAttributesEntry.clear();
      this.attributesOutput.next(this.changedAttributes);
      this.ignoreChanges = false;
      this.forceEmit.next();
      this.buildFieldsAndAttributes();
    });

    this.fieldsTemplate.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
      //this.resentFavoritesChanged = true; TODO
      const visibleFieldsIdentifiers = data["visibleFields"];
      this.formValueEntry.value = JSON.stringify(data);
      this.formValues = data;
      this.fieldsVisible = {};
      this.fields.forEach((field) => {
        if (this.rangeAllowed(field)) {
          if (
            notNullOrEmpty(data[field.identifier + "From"]) ||
            notNullOrEmpty(data[field.identifier + "Until"]) ||
            (visibleFieldsIdentifiers &&
              visibleFieldsIdentifiers.includes(field.identifier))
          ) {
            this.fieldsVisible[field.identifier] = true;
            if (notNullOrEmpty(data[field.identifier + "Until"])) {
              this.rangeModes[field.identifier] = true;
            } else {
              this.rangeModes[field.identifier] = false;
            }
          } else {
            this.fieldsVisible[field.identifier] = false;

            if (field.basic) {
              this.fieldsVisible[field.identifier] = true;
            }
          }
        } else {
          if (
            data[field.identifier] ||
            field.basic ||
            (visibleFieldsIdentifiers &&
              visibleFieldsIdentifiers.includes(field.identifier))
          ) {
            this.fieldsVisible[field.identifier] = true;
          } else {
            this.fieldsVisible[field.identifier] = false;
          }
        }
      });
      this.fieldsVisibleEntry.value = JSON.stringify(this.fieldsVisible);
      this.setFormValue(data);
      this.buildFieldsAndAttributes();
    });

    this.recoverFormValue();
    this.buildFieldsAndAttributes();

    this.containerHeightPx = this.listHeight;
    this.containerHeight = this.getHeight();

    this.searchModeChanged
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((isListSearch) => {
        if (!isListSearch) {
          requestAnimationFrame(() => this.updateIgxForContainerHeight());
        }
      });
  }

  toggleExpanded() {
    this.expandedToggle.next(this.expanded);
  }

  public formcontrolNameFor(field: FormWidgetField) {
    if (this.rangeAllowed(field)) {
      return field.identifier + "From";
    }
    return field.identifier;
  }

  public formcontrolFor(field: FormWidgetField) {
    return this.form.controls[this.formcontrolNameFor(field)];
  }

  public openAttributeSelection() {
    this.widgetframeService
      .getData(this.attributeUrl)
      .pipe(map((data) => data["_embedded"]["attributes"]))
      .subscribe((attributes) => {
        attributes.forEach((attribute) => {
          if (
            this.attributeTypesToAddMultiple &&
            this.attributeTypesToAddMultiple.indexOf(
              attribute.parentDescription
                ? attribute.parentDescription
                : attribute.type
            ) > -1
          ) {
            attribute.multipleAddable = true;
          }
        });

        let dialogRef = this.dialogService.open(
          SearchAttributeSelectionDialog,
          {
            minWidth: "900px",
            maxWidth: "900px",
            height: "755px",
          }
        );

        this.fields.forEach((field) => {
          (field as any).scope = "SYSTEM";
          (field as any).isField = true;
        });
        const fields = []
          .concat(this.fields)
          .filter((field) => !(field.basic || field.identifier == "searchMode"))
          .concat(attributes);
        dialogRef.componentInstance.fields = _.sortBy(fields, [
          (field) => {
            return String(this.translateService.instant(field.description));
          },
        ]);
        dialogRef.componentInstance.withDefaultMenuAction =
          this.dialogWithActionMenu;
        dialogRef.componentInstance.selectFilterParams =
          this.selectFilterParams;
        dialogRef.componentInstance.currentLocale = this.currentLocale;
        dialogRef.componentInstance.attributesVisible = this.attributes;
        dialogRef.componentInstance.fieldsVisible = deepCopy(
          this.fieldsVisible
        );
        dialogRef.componentInstance.resetDefault = this.fieldsVisibleFavorite;
        dialogRef.componentInstance.infoText =
          "visible-attribute-selection.edit.infoText";
        dialogRef.componentInstance.infoTitle =
          "visible-attribute-selection.edit.infoTitle";
        dialogRef.componentInstance.activateMultipleAddableMode =
          this.attributeTypesToAddMultiple &&
          this.attributeTypesToAddMultiple.length > 0;

        const sub =
          dialogRef.componentInstance.saveFavoritesObservable.subscribe(
            (data) => {
              this.fieldsVisibleFavorite = data;
              this.fieldsVisibleFavoriteEntry.value = JSON.stringify(data);
            }
          );

        dialogRef.afterClosed().subscribe((data) => {
          sub.unsubscribe();
          if (data) {
            this.fieldsVisible = data.fieldsVisible;
            this.fieldsVisibleEntry.value = JSON.stringify(data.fieldsVisible);

            const attributes = data.attributes;
            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 = attributes.find(
                (val) => val[this.uniqueID] === entry[this.uniqueID]
              );
              if (!attribute) {
                this.changedAttributes.splice(i, 1);
                changedAttributesChanged = true;
              }
            }
            if (changedAttributesChanged) {
              this.changedAttributesEntry.value = JSON.stringify(
                this.changedAttributes
              );
              this.resetFavorites.next();
              this.attributesOutput.next(this.changedAttributes);
            } else {
              this.attributesOutput.next(attributes);
            }
            attributes.forEach((attribute) => {
              this.mapToMultiLookup(attribute);
            });

            if (this.attributes) {
              attributes.forEach((entry) => {
                const value = this.attributes.find(
                  (att) => att[this.uniqueID] === entry[this.uniqueID]
                );
                if (value) {
                  entry.source = value.source;
                }
              });
            }
            this.attributes = attributes;
            this.attributesEntry.value = JSON.stringify(attributes);
            //Force a re emit, since the data that we are emitting should not contain invisible fields and we need to recompute that
            this.forceEmit.next();
            this.buildFieldsAndAttributes();
          }
        });
      });
  }

  editField(field: FormWidgetField) {
    let dialogRef = this.dialogService.open(
      SearchAttributeConfigurationDialog,
      {
        autoFocus: true,
        minWidth: "600px",
        maxWidth: "600px",
      }
    );
    dialogRef.componentInstance.field = field;
    dialogRef.componentInstance.form = this.form;
    dialogRef.componentInstance.fieldIsRange =
      this.rangeModes[field.identifier];
    dialogRef.componentInstance.lookups = this.lookups;
    dialogRef.componentInstance.currentLocale = this.currentLocale;
    dialogRef.componentInstance.formcontrolNameFor =
      this.formcontrolNameFor(field);

    dialogRef.componentInstance.onfieldRangeChange.subscribe((payload) => {
      let pl: any = payload;
      this.rangeChange(pl.event, pl.element);
    });

    dialogRef.afterClosed().subscribe((changedField) => {
      this.form.patchValue(this.form.value);
    });
    this.buildFieldsAndAttributes();
  }

  editAttribute(attribute: Attribute) {
    let dialogRef = this.dialogService.open(
      SearchAttributeConfigurationDialog,
      {
        minWidth: "600px",
        maxWidth: "600px",
      }
    );
    dialogRef.componentInstance.inputAttribute = attribute;

    dialogRef.componentInstance.infoText =
      "visible-attribute-selection.edit.infoText";
    dialogRef.componentInstance.infoTitle =
      "visible-attribute-selection.edit.infoTitle";
    dialogRef.componentInstance.editAttributeService =
      this._editAttributeService;

    dialogRef.componentInstance.showLabel = this.showLabel(attribute);
    dialogRef.componentInstance.attributeSupportsRange =
      this.attributeSupportsRange(attribute);

    dialogRef.afterClosed().subscribe((changedAttribute) => {
      if (changedAttribute) {
        if (!changedAttribute.displayRange) {
          delete changedAttribute.from;
          delete changedAttribute.to;
        }

        let attributeIndex = this.attributes.findIndex(
          (attribute) =>
            changedAttribute[this.uniqueID] === attribute[this.uniqueID]
        );
        this.attributes[attributeIndex] = changedAttribute;
        this._editAttributeService.setAttributeChangedEvent(changedAttribute);
        const atts = this.attributes;
        this.attributes = [];
        this.attributes.push(...atts);
        this.attributesOutput.next(this.changedAttributes);
        this.attributesEntry.value = JSON.stringify(this.attributes);
        this.changedAttributesEntry.value = JSON.stringify(
          this.changedAttributes
        );
        this.buildFieldsAndAttributes();
      }
    });
  }

  public getTooltipValue(field: FormWidgetField) {
    const value = this.formValues[field.identifier];
    if (field.type === "CATEGORY") {
      return value.categoryDescription;
    }
    if (field.type === "LOOKUP") {
      const lookups = this.getLookups(field.lookupSource);
      return this.translateService.instant(
        lookups.find((lookup) => lookup.value === value).label
      );
    }
    if (field.type === "MULTI_LOOKUP") {
      const lookups = this.getLookups(field.lookupSource);
      return value
        .map((entry) => lookups.find((lookup) => lookup.value === entry).label)
        .map((entry) => this.translateService.instant(entry))
        .join(" | ");
    }

    if (!this.rangeAllowed(field)) {
      return value;
    }

    let fromValue = this.formValues[field.identifier + "From"];
    let toValue = this.formValues[field.identifier + "Until"];

    if (field.type === "DATE") {
      if (fromValue) {
        this.datetimeAdapter.toIso8601(new Date(fromValue));

        fromValue = this.datetimeAdapter.format(new Date(fromValue), null);
      }
      if (toValue) {
        toValue = this.datetimeAdapter.format(new Date(toValue), null);
      }
    }

    if (toValue) {
      return `${fromValue} - ${toValue}`;
    }
    return fromValue;
  }

  public getTooltipValueForAttribute(attribute: Attribute) {
    return attribute.source
      .map((entry) =>
        entry.description && entry.description.length > 1
          ? entry.description
          : entry.value
      )
      .join(" | ");
  }

  public includedInTooltip(field: FormWidgetField) {
    if (
      !this.fieldsVisible[field.identifier] ||
      field.basic ||
      field.identifier === "searchMode"
    ) {
      return false;
    }
    if (!this.rangeAllowed(field)) {
      return this.formValues[field.identifier];
    }
    const fromValue = this.formValues[field.identifier + "From"];
    const toValue = this.formValues[field.identifier + "Until"];
    return fromValue || toValue;
  }

  rangeChange(event: any, field: FormWidgetField) {
    this.rangeModes[field.identifier] = event.checked;
    this.forceEmit.next();
    this.rangeModesEntry.value = JSON.stringify(this.rangeModes);
  }

  getLookups(key): any[] {
    if (!this.currentLocale) {
      return [];
    }
    return this.lookups[this.currentLocale][key];
  }

  private recoverFormValue() {
    if (this.formValueEntry.exists()) {
      this.setFormValue(JSON.parse(this.formValueEntry.value));
    }
  }

  private setFormValue(value) {
    this.form.patchValue(value);
  }

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

  private checkIsAttributeEmpty(attribute: Attribute): boolean {
    const isRootEmpty =
      attribute.source.length === 0 ||
      !attribute.source[0].value ||
      attribute.source[0].value === "";
    // 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 loadLookups(locale) {
    this.datetimeAdapter.setLocale(locale);
    this.currentLocale = locale;
    if (this.lookupsInput) {
      if (this.lookups[locale]) {
        return;
      }

      this.lookupService
        .getLookups(
          Object.keys(this.lookupsInput),
          this.lookupsInput,
          this.links,
          locale
        )
        .pipe(takeUntil(this.unsubscribe), take(1))
        .subscribe((lookups) => {
          this.lookups = Object.assign({}, this.lookups, { [locale]: lookups });
          this.cdr.markForCheck();
        });
    }
  }

  private updateLookups(keys: string[], locale) {
    this.datetimeAdapter.setLocale(locale);
    this.currentLocale = locale;
    if (this.lookupsInput) {
      this.lookupService
        .getLookups(keys, this.lookupsInput, this.links, locale)
        .pipe(takeUntil(this.unsubscribe), take(1))
        .subscribe((lookups) => {
          Object.keys(lookups).forEach((key) => {
            this.lookups[locale][key] = lookups[key];
          });

          this.lookups = Object.assign({}, this.lookups);
          this.cdr.markForCheck();
        });
    }
  }

  public isFieldVisible(field: FormWidgetField) {
    const value = this.fieldsVisible[field.identifier];
    if (value === undefined) {
      return true;
    }
    return value;
  }

  public showLabel(attribute) {
    return attribute.type == "BOOLEAN";
  }

  public attributeSupportsRange(attribute) {
    return RANGE_ATTRIBUTES.indexOf(attribute.type) !== -1;
  }

  public onInputEvent(event: FormEventPayload) {
    // this.event.next(event); TODO
  }

  public save() {
    // this.doSave.next(); TODO
  }

  public submit() {
    // this.doSubmit.next(this.listsearch); TODO
  }

  private recoverFromData(data: Attribute[], attributes: Attribute[]) {
    this.attributes = [];
    this.changedAttributes = [];
    data.forEach((entry) => {
      let attribute = deepCopy(
        attributes.find(
          (attribute) => attribute.identifier === entry.identifier
        )
      );

      if (entry.from) {
        attribute.from = entry.from;
        attribute.from.type = attribute.type;
        attribute.from.parentType = attribute.parentType;
        attribute.from.parentDescription = attribute.parentDescription;
        attribute.displayRange = true;
      }
      if (entry.to) {
        attribute.to = entry.to;
        attribute.to.type = attribute.type;
        attribute.to.parentType = attribute.parentType;
        attribute.to.parentDescription = attribute.parentDescription;
        attribute.displayRange = true;
      }

      attribute.source = entry.source;
      if (!attribute.uid) {
        attribute.uid = createUID();
      }

      this.attributes.push(attribute);
      this.changedAttributes.push(attribute);
    });

    this.attributesOutput.next(this.changedAttributes);
    this.attributesEntry.value = JSON.stringify(this.attributes);
    this.changedAttributesEntry.value = JSON.stringify(this.changedAttributes);
    this.buildFieldsAndAttributes();
  }

  public getTooltip(attribute: Attribute) {
    let tooltip = "";
    if (attribute.modifier === "NOT_EXISTS") {
      tooltip += "<div>";
      tooltip += this.translateService.instant("tooltip.no.exists");
      tooltip += "</div>";
    } else if (attribute.modifier === "OR") {
      tooltip += "<div>";
      tooltip += this.translateService.instant("tooltip.or");
      tooltip += "</div>";
    }

    if (attribute.locale && attribute.locale.length >= 1) {
      tooltip += "<div>";
      tooltip += this.translateService.instant("tooltip.search.for.locale");
      tooltip += "</div>";
    }

    if (attribute.maintenanceLevel && attribute.maintenanceLevel.length >= 1) {
      tooltip += "<div>";
      tooltip += this.translateService.instant(
        "tooltip.search.for.maintenance.level"
      );
      tooltip += "</div>";
    }
    return tooltip;
  }

  templateChanged(template: any) {
    this.favoritesChanged = true;

    if (template.type === "standard") {
      template = template.parameter;
      this.templateOutput.next(template);
      if (template.attributes) {
        const attributetemplate = template.attributes;
        delete template.attributes;
        this.attributeData.next(attributetemplate);
      } else {
        this.attributeData.next([]);
      }
      if (template.publication) {
        const category = template.category;
        const publication = template.publication;
        const paths = template.categoryPaths;
        template.category = {
          publication,
          category,
          paths,
        };
      } else {
        template.category = null;
      }
      this.fieldsTemplate.next(template);
      this.listsearch = false;
      this.listvalueOutput.next(null);
    } else {
      this.listvalueOutput.next(template.parameter);
    }
  }

  public getHeight() {
    return this.dynamicHeight
      ? calcHeight(this.dynamicHeightAdditionalHeight)
      : this.listHeight;
  }

  private mapToMultiLookup(attribute) {
    if (attribute._links && attribute._links["attribute-uoms"] != undefined) {
      delete attribute._links["attribute-uoms"];
    }

    const typeList = ["COMPOSITION_AMOUNT", "COMPOSITION_PERCENT", "LOOKUP"];
    if (typeList.includes(attribute.type)) {
      attribute.type = "MULTI_LOOKUP";
    }
  }

  ngAfterViewInit(): void {
    this.updateIgxForContainerHeight();
  }

  private updateIgxForContainerHeight() {
    const containerEl = document.querySelector("#attribute-container");
    if (!containerEl) {
      return;
    }
    const style = getComputedStyle(containerEl);
    this.containerHeightPx = style.height;
    this.cdr.markForCheck();
  }
}
<hr />
<ng-container *ngIf="!listsearch">
  <div class="nm-widgetframe__title-inline-container">
    <mat-checkbox
      class="nm-widgetframe__title-inline-container"
      color="primary"
      (change)="toggleExpanded()"
      [(ngModel)]="expanded"
      >{{ "label.advanced.search" | translate }}</mat-checkbox
    >
  </div>

  <div class="nm-widgetframe-sub-content" [class.hidden]="!expanded">
    <button
      mat-mini-fab
      color="primary"
      class="nm-advancedSearch__shownFieldsButton"
      (click)="openAttributeSelection()"
    >
      <mat-icon>add</mat-icon>
    </button>

    <ng-container *ngIf="searchFavorites">
      <ng-container *ngTemplateOutlet="searchFavoritesTpl"> </ng-container>
    </ng-container>

    <div
      id="attribute-container"
      class="attribute-container"
      [style.height]="containerHeight"
    >
      <ng-template
        igxFor
        let-item
        [igxForOf]="fieldsAndAttributes"
        igxForScrollOrientation="vertical"
        [igxForContainerSize]="containerHeightPx"
        [igxForItemSize]="itemHeight"
      >
        <ng-container *ngIf="!item.isField; else fieldRender">
          <ng-container
            *ngTemplateOutlet="attributeTemplate; context: { attribute: item }"
          ></ng-container>
        </ng-container>

        <ng-template #fieldRender>
          <ng-container
            *ngTemplateOutlet="fieldTemplate; context: { field: item }"
          ></ng-container>
        </ng-template>
      </ng-template>
    </div>
  </div>
</ng-container>

<ng-container *ngIf="listsearch">
  <ng-container *ngTemplateOutlet="searchFavoritesTpl"> </ng-container>
</ng-container>

<ng-template #popTemplateChangedValues>
  <div class="nm-attribute-list-tooltip">
    <div *ngFor="let field of fields">
      <div class="nm-attribute-list-elements" *ngIf="includedInTooltip(field)">
        <div class="nm-attribute-list-description">
          <nm-ellipsis [content]="field.description | translate"></nm-ellipsis>
        </div>
        <div class="nm-attribute-list-value">
          {{ getTooltipValue(field) }}
        </div>
      </div>
    </div>
    <br />

    <div *ngFor="let attribute of attributes">
      <div class="nm-attribute-list-elements" *ngIf="attribute.source[0].value">
        <div class="nm-attribute-list-description">
          <nm-ellipsis [content]="attribute.description"></nm-ellipsis>
        </div>
        <div class="nm-attribute-list-value">
          {{ getTooltipValueForAttribute(attribute) }}
        </div>
      </div>
    </div>
  </div>
</ng-template>

<ng-template #fieldTemplate let-field="field">
  <div class="form-row range-wrapper" [formGroup]="form">
    <div class="form-input">
      <ng-container *ngIf="rangeModes[field.identifier]; else noRange">
        <nm-search-input
          class="nm-advancedSearch__attributeRowRange --left"
          [field]="field"
          [lookups]="lookups"
          (event)="onInputEvent($event)"
          (enterPressed)="submit()"
          [locale]="currentLocale"
          [floatingLabel]="
            ('from' | translate) + ' ' + (field.description | translate)
          "
          [formControl]="form.controls[field.identifier + 'From']"
        ></nm-search-input>

        <nm-search-input
          class="nm-advancedSearch__attributeRowRange --right"
          [field]="field"
          [lookups]="lookups"
          (event)="onInputEvent($event)"
          (enterPressed)="submit()"
          [locale]="currentLocale"
          [floatingLabel]="
            ('to' | translate) + ' ' + (field.description | translate)
          "
          [formControl]="form.controls[field.identifier + 'Until']"
          [triggerAsChipList]="true"
        ></nm-search-input>
      </ng-container>
      <ng-template #noRange>
        <nm-search-input
          class="nm-advancedSearch__attributeRowSingle no-range"
          [field]="field"
          (event)="onInputEvent($event)"
          (enterPressed)="submit()"
          [lookups]="lookups"
          [floatingLabel]="field.description | translate"
          [locale]="currentLocale"
          [formControl]="formcontrolFor(field)"
          [triggerAsChipList]="true"
        ></nm-search-input>
      </ng-template>

      <button
        *ngIf="field.range === 'choose'"
        class="edit-button fade-in"
        mat-icon-button
        color="primary"
        aria-label="Search configuration"
      >
        <mat-icon
          (click)="editField(field)"
          [svgIcon]="'comment-search-outline'"
        ></mat-icon>
      </button>
    </div>
  </div>
</ng-template>

<ng-template #attributeTemplate let-attribute="attribute">
  <div class="nm-attribute-list-elements">
    <mat-icon
      *ngIf="getTooltip(attribute) as tooltip"
      color="primary"
      class="modifier-icon"
      [pTooltip]="tooltip"
      [escape]="false"
      [showDelay]="300"
      >info
    </mat-icon>

    <div
      class="range-wrapper"
      [ngClass]="{ modifierActive: attribute.modifier }"
    >
      <ng-container *ngIf="attribute.modifier !== 'NOT_EXISTS'; else notExists">
        <ng-container *ngIf="showLabel(attribute); else noLabel">
          <div
            class="nm-attribute-list-description"
            *ngIf="showLabel(attribute)"
          >
            <nm-ellipsis [content]="attribute.description"></nm-ellipsis>
          </div>
          <div class="nm-attribute-list-value">
            <nm-edit-attribute
              [attribute]="attribute"
              [copyContentButton]="false"
              [onlyInputsForTexts]="true"
              [triggerAsChipList]="true"
              [editAttributeService]="_editAttributeService"
              [floatingLabel]="false"
              showClearButton="{{ true }}"
            >
            </nm-edit-attribute>
          </div>
        </ng-container>
        <ng-template #noLabel>
          <ng-container *ngIf="attribute.displayRange; else noRange">
            <nm-edit-attribute
              class="range-wrapper-left"
              [attribute]="attribute.from"
              [copyContentButton]="false"
              [onlyInputsForTexts]="true"
              [loadOptionsLazy]="true"
              [displayValueAssets]="true"
              [editAttributeService]="_editAttributeService"
              [triggerAsChipList]="true"
              showClearButton="{{ true }}"
              [floatingLabel]="
                ('from' | translate) + ' ' + attribute.description
              "
            >
            </nm-edit-attribute>

            <nm-edit-attribute
              class="range-wrapper-right"
              [attribute]="attribute.to"
              [copyContentButton]="false"
              [onlyInputsForTexts]="true"
              [loadOptionsLazy]="true"
              [displayValueAssets]="true"
              [editAttributeService]="_editAttributeService"
              [triggerAsChipList]="true"
              showClearButton="{{ true }}"
              [floatingLabel]="('to' | translate) + ' ' + attribute.description"
            >
            </nm-edit-attribute>
          </ng-container>
          <ng-template #noRange>
            <nm-edit-attribute
              class="range-wrapper-single"
              [attribute]="attribute"
              [copyContentButton]="false"
              [onlyInputsForTexts]="true"
              [loadOptionsLazy]="true"
              [displayValueAssets]="true"
              [editAttributeService]="_editAttributeService"
              [triggerAsChipList]="true"
              showClearButton="{{ true }}"
              [floatingLabel]="attribute.description"
            >
            </nm-edit-attribute>
          </ng-template>
        </ng-template>
      </ng-container>

      <ng-template #notExists>
        <mat-form-field class="range-wrapper-no-value">
          <input
            matInput
            class="full-width-margin"
            [readonly]="true"
            [placeholder]="
              attribute.description +
              ' ' +
              ('placeholder.without.value' | translate)
            "
            [value]="attribute.tooltip || ''"
            [matTooltip]="attribute.tooltip || ''"
          />
        </mat-form-field>
      </ng-template>

      <button
        class="edit-button"
        mat-icon-button
        color="primary"
        (click)="editAttribute(attribute)"
        aria-label="Search configuration"
      >
        <mat-icon [svgIcon]="'comment-search-outline'"></mat-icon>
      </button>
    </div>
  </div>
</ng-template>

<ng-template #searchFavoritesTpl>
  <nm-search-favorites
    [reset]="resetFavorites"
    [searchTemplatesUri]="searchTemplatesUri"
    [data]="dataOutput"
    [currentLocale]="currentLocale"
    [listsearch]="listsearch"
    [enableRoleBasedSearchTemplates]="enableRoleBasedSearchTemplates"
    (template)="templateChanged($event)"
    (reloadLookups)="reloadLookups.next($event)"
  >
  </nm-search-favorites>
</ng-template>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""