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