nm-search-attributes
src/app/shared/widgets/buy/search-attributes/search-attributes.component.ts
providers |
EditAttributeService
|
selector | nm-search-attributes |
styleUrls | search-attributes.component.scss |
templateUrl | ./search-attributes.component.html |
constructor(_widgetframeService: WidgetframeService, _editAttributeService: EditAttributeService, _halService: HalService, localStorageService: LocalStorageService, _notificationService: CustomNotificationService, appStore: AppdataStore, dialogService: DialogService)
|
||||||||||||||||||||||||
Parameters :
|
Private attributeSupportsRange | ||||
attributeSupportsRange(attribute: )
|
||||
Parameters :
Returns :
boolean
|
Private checkIsAttributeEmpty | ||||||
checkIsAttributeEmpty(attribute: Attribute)
|
||||||
Parameters :
Returns :
boolean
|
Protected configureWidget | ||||||
configureWidget(configuration: WidgetConfig)
|
||||||
Decorators : WidgetConfigure
|
||||||
Parameters :
Returns :
void
|
Public editShownAttributes |
editShownAttributes()
|
Returns :
void
|
Public getChangedAttributesForHeader |
getChangedAttributesForHeader()
|
Returns :
{}
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
Public rangeChange | |||||||||
rangeChange(event: , attribute: Attribute)
|
|||||||||
Parameters :
Returns :
void
|
Private recoverChangedAttributes |
recoverChangedAttributes()
|
Returns :
void
|
Private recoverFromData | |||||||||
recoverFromData(data: , attributes: Attribute[])
|
|||||||||
Parameters :
Returns :
void
|
resetWidget |
resetWidget()
|
Returns :
any
|
showLabel | ||||
showLabel(attribute: )
|
||||
Parameters :
Returns :
boolean
|
toogleContentVisibility |
toogleContentVisibility()
|
Returns :
void
|
Public _editAttributeService |
_editAttributeService:
|
Type : EditAttributeService
|
Public _halService |
_halService:
|
Type : HalService
|
Public _id |
_id:
|
Type : string
|
Decorators : WidgetId
|
Public _notificationService |
_notificationService:
|
Type : CustomNotificationService
|
Public _widgetframeService |
_widgetframeService:
|
Type : WidgetframeService
|
Public allowRange |
allowRange:
|
Public attributes |
attributes:
|
Type : Attribute[]
|
Private attributesLocalstorageEntry |
attributesLocalstorageEntry:
|
Type : LocalStorageEntry
|
Public attributesorg |
attributesorg:
|
Type : Attribute[]
|
Public attributeUrl |
attributeUrl:
|
Default value : new Subject()
|
Decorators : WidgetInput
|
Input to set the uri that is used for the editShownAttributes. |
Public canEditShownAttributes |
canEditShownAttributes:
|
Default value : false
|
Public changedAttributes |
changedAttributes:
|
Default value : <any>{}
|
Public changedAttributesOutput |
changedAttributesOutput:
|
Default value : new ReplaySubject<any>()
|
Decorators : WidgetOutput
|
Emits an array of all edited attributes and their values. |
Public changedValueByUser |
changedValueByUser:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Emits the attribute that is being edited. |
Public cols |
cols:
|
Type : any[]
|
Public configuration |
configuration:
|
Type : WidgetConfig<SearchAttributesWidgetConfiguration>
|
Decorators : WidgetConfiguration
|
Public contentVisible |
contentVisible:
|
Type : boolean
|
Default value : false
|
Public currentLocale |
currentLocale:
|
Type : string
|
Public data |
data:
|
Default value : new Subject()
|
Decorators : WidgetInput
|
Input of preset array of attribute identifiers, used to get attributes. |
Public inputLink |
inputLink:
|
Type : string
|
Public isCollapsible |
isCollapsible:
|
Type : boolean
|
Default value : false
|
Public localstorageAttributesEntry |
localstorageAttributesEntry:
|
Type : LocalStorageEntry
|
Public localStorageVisibiltyEntry |
localStorageVisibiltyEntry:
|
Type : LocalStorageEntry
|
Public multiLookupsAllowed |
multiLookupsAllowed:
|
Type : boolean
|
By default, attributes of type 'MULTI_LOOKUP' are converted to type 'LOOKUP', for compatibility with some search functions. Setting this value to true will allow multiple values to be selected for these attributes. |
Public reload |
reload:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Reloads attributes for a given locale, and attributes url. |
Public reset |
reset:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Resets attributes and local storage values. |
Private selectAttributeUrl |
selectAttributeUrl:
|
Public translateAttributeDescriptions |
translateAttributeDescriptions:
|
Default value : false
|
Public unsubscribe |
unsubscribe:
|
Default value : NgUnsubscribe.create()
|
Public uri |
uri:
|
Default value : new ReplaySubject<any>(1)
|
Decorators : WidgetInput
|
Will call given uri and select the attributes from the address. |
import {
combineLatest as observableCombineLatest,
Subject,
ReplaySubject,
} from "rxjs";
import {
onErrorResumeNext,
takeUntil,
mergeMap,
distinctUntilChanged,
map,
flatMap,
} from "rxjs/operators";
import { Component, OnDestroy } from "@angular/core";
import { WidgetframeService } from "../../widgetframe/widgetframe.service";
import {
getOrDefault,
throwIfUndefined,
WidgetConfig,
} from "../../widget.configuration";
import {
WidgetComponent,
WidgetConfiguration,
WidgetConfigure,
WidgetId,
WidgetInput,
WidgetOutput,
} from "../../widget.metadata";
import { NgUnsubscribe } from "../../../ng-unsubscribe";
import * as uriTemplates_ from "uri-templates";
import { Attribute } from "../../../components/edit-attribute/attribute";
import { EditAttributeService } from "../../../components/edit-attribute/edit-attribute.service";
import { HalService } from "../../../components/hal/index";
import { CustomNotificationService } from "../../../components/notification/customnotification.service";
import {
DeletionMode,
Scope,
} from "../../../components/local-storage/local-storage-constants";
import {
LocalStorageEntry,
LocalStorageService,
} from "../../../components/local-storage/local-storage.service";
import { SelectAttributesDialogComponent } from "../../../components/dialog/selectAttributesDialog.component";
import { AppdataStore } from "../../../components/appdata/appdata.store";
import { deepCopy } from "../../../components/util/util.service";
import { FormControl } from "@angular/forms";
import { DialogService } from "../../../components/dialog";
const uriTemplates = uriTemplates_;
export interface SearchAttributesWidgetConfiguration {
/**
* Enables/Disables selecting attributes SelectAttributesDialog. @default(false)
*/
canEditShownAttributes?: boolean;
/**
* Local storage key for edited attributes and their values.
*/
"localstorage-attribute-search"?: String;
/**
* Local storage key for search attributes visibility state.
*/
"localstorage-content-visible"?: String;
/**
* Enables/disables attribute values range, from and to. @default(false)
*/
allowRange?: boolean;
/**
* Enables/Disables showing attributes description. @default(false)
*/
translateAttributeDescriptions?: boolean;
/**
* Enables/disables multilookup option. @default(false)
*/
multiLookupsAllowed?: boolean;
/**
* used to get attributes in case of editShowAttributes enabled.
*/
attributeUrl?: String;
}
declare var jQuery: any;
@WidgetComponent("nm-search-attributes")
@Component({
selector: "nm-search-attributes",
templateUrl: "./search-attributes.component.html",
styleUrls: ["./search-attributes.component.scss"],
providers: [EditAttributeService],
})
export class SearchAttributesWidgetComponent implements OnDestroy {
public cols: any[];
public attributes: Attribute[];
public attributesorg: Attribute[];
public changedAttributes = <any>{};
public localstorageAttributesEntry: LocalStorageEntry;
public localStorageVisibiltyEntry: LocalStorageEntry;
public canEditShownAttributes = false;
public contentVisible: boolean = false;
public allowRange;
public isCollapsible: boolean = false;
public translateAttributeDescriptions = false;
public currentLocale: string;
public inputLink: string;
/**
* By default, attributes of type 'MULTI_LOOKUP' are converted to type 'LOOKUP', for compatibility with some
* search functions. Setting this value to true will allow multiple values to be selected for these attributes.
*/
public multiLookupsAllowed: boolean;
@WidgetConfiguration()
public configuration: WidgetConfig<SearchAttributesWidgetConfiguration>;
/**
* Reloads attributes for a given locale, and attributes url.
*/
@WidgetInput()
public reload = new Subject<any>();
/**
* Emits an array of all edited attributes and their values.
*/
@WidgetOutput("changedAttributesOutput")
public changedAttributesOutput = new ReplaySubject<any>();
/**
* Emits the attribute that is being edited.
*/
@WidgetOutput("changedValueByUser")
public changedValueByUser = new Subject<any>();
/**
* Resets attributes and local storage values.
*/
@WidgetInput()
public reset = new Subject<any>();
/**
* Will call given uri and select the attributes from the address.
*/
@WidgetInput("uri")
public uri = new ReplaySubject<any>(1);
/**
* Input to set the uri that is used for the editShownAttributes.
*/
@WidgetInput("attributeUrl")
public attributeUrl = new Subject();
/**
* Input of preset array of attribute identifiers, used to get attributes.
*/
@WidgetInput()
public data = new Subject();
@WidgetId()
public _id: string;
public unsubscribe = NgUnsubscribe.create();
private attributesLocalstorageEntry: LocalStorageEntry;
constructor(
public _widgetframeService: WidgetframeService,
public _editAttributeService: EditAttributeService,
public _halService: HalService,
private localStorageService: LocalStorageService,
public _notificationService: CustomNotificationService,
private appStore: AppdataStore,
private dialogService: DialogService
) {}
private selectAttributeUrl;
@WidgetConfigure()
protected configureWidget(configuration: WidgetConfig) {
this.changedAttributes = [];
this.localStorageVisibiltyEntry = this.localStorageService.getLocalStorageEntry(
configuration.configuration["localstorage-content-visible"],
Scope.GLOBAL,
DeletionMode.LOGIN
);
this.localstorageAttributesEntry = this.localStorageService.getLocalStorageEntry(
configuration.configuration["localstorage-attribute-search"],
Scope.GLOBAL,
DeletionMode.LOGIN
);
this.selectAttributeUrl = configuration.configuration.attributeUrl;
this.canEditShownAttributes = getOrDefault(
configuration.configuration.editShownAttributes,
false
);
if (this.canEditShownAttributes) {
this.attributesLocalstorageEntry = this.localStorageService.getLocalStorageEntry(
this._id,
Scope.GLOBAL,
DeletionMode.RESET
);
if (this.attributesLocalstorageEntry.exists()) {
this.attributes = JSON.parse(this.attributesLocalstorageEntry.value);
this.recoverChangedAttributes();
}
}
this.allowRange = getOrDefault(
configuration.configuration["allowRange"],
false
);
this.translateAttributeDescriptions =
configuration.configuration.translateAttributeDescriptions || false;
if (this.localStorageVisibiltyEntry.exists()) {
this.contentVisible = this.localStorageVisibiltyEntry.value === "true";
}
this.multiLookupsAllowed = getOrDefault(
configuration.configuration.multiLookupsAllowed,
false
);
this.data.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
const missingAttributes = [];
let foundAttributes = [];
Object.keys(data).forEach((key) => {
const attribute = this.attributes.find((att) => att.identifier == key);
if (attribute) {
if (attribute.source && attribute.source[0]) {
attribute.source[0] = {};
}
foundAttributes.push(attribute);
} else {
missingAttributes.push(key);
}
});
if (missingAttributes.length !== 0) {
this.appStore
.getAppdata()
.pipe(
map((data) => data.ipim._links.attributes.href),
map(
(data) =>
data +
'?filter=identifier:in:"' +
missingAttributes.join(",") +
'"'
),
flatMap((data) => this._widgetframeService.getData(data)),
map((data) => data._embedded.attributes)
)
.subscribe((entries) => {
foundAttributes = foundAttributes.concat(entries);
this.recoverFromData(data, foundAttributes);
});
} else {
this.recoverFromData(data, foundAttributes);
}
});
this.reset.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
this.changedAttributes = [];
this.changedAttributesOutput.next(this.changedAttributes);
if (this.localstorageAttributesEntry.exists()) {
this.localstorageAttributesEntry.clear();
}
if (this.attributesorg) {
this.attributes = jQuery.extend(true, [], this.attributesorg);
} else {
if (this.attributes) {
this.attributes.forEach((attribute) => {
if (attribute.source && attribute.source[0]) {
attribute.source[0] = {};
}
});
this.attributes = jQuery.extend(true, [], this.attributes);
}
}
});
this._editAttributeService
.getAttributeChangedEvent()
.pipe(takeUntil(this.unsubscribe))
.subscribe((attribute) => {
const dashIdx = attribute.identifier.indexOf("-");
// The original attribute that was changed, needed to save the from / to
let originalAttribute;
let from;
if (dashIdx !== -1) {
const rootAttributeName =
dashIdx === -1 ? null : attribute.identifier.substring(0, dashIdx);
const att = this.attributes.find(
(att) => att.identifier === rootAttributeName
);
const sub = attribute.identifier.substring(dashIdx + 1);
originalAttribute = attribute;
attribute = att;
from = sub === "from";
}
let dublicateIndex = this.changedAttributes.findIndex(
(item) => item.identifier == attribute.identifier
);
if (dublicateIndex !== -1) {
this.changedAttributes.splice(dublicateIndex, 1);
const isEmpty = this.checkIsAttributeEmpty(attribute);
// The attribute and all of its fake range child attributes are not empty, we can readd it to the changed attributes
if (!isEmpty) {
this.changedAttributes.push(attribute);
}
} else {
// THere is no duplicate, we are save to add this to the changed attributes
this.changedAttributes.push(attribute);
}
this.localstorageAttributesEntry.value = JSON.stringify(
this.changedAttributes
);
this.changedAttributesOutput.next(this.changedAttributes);
this.changedValueByUser.next(attribute);
});
this.attributeUrl
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => (this.selectAttributeUrl = data));
let source = observableCombineLatest(
this.uri.asObservable(),
this.reload.asObservable().pipe(distinctUntilChanged()),
function (uri, reloadEvent) {
let template = uriTemplates(uri);
let uriParams = {};
// uriParams["context"] = configuration.configuration["context"];
uriParams["locale"] = reloadEvent;
this.currentLocale = reloadEvent;
return template.fill(uriParams);
}
).pipe(
takeUntil(this.unsubscribe),
mergeMap((href) =>
this._widgetframeService
.getData(href)
.pipe(onErrorResumeNext(this.resetWidget()))
)
);
source.subscribe(
(data) => {
for (const attribute of <any>data["_embedded"]["attributes"]) {
if (!this.multiLookupsAllowed && attribute.type === "MULTI_LOOKUP") {
attribute.type = "LOOKUP";
}
}
this.attributes = jQuery.extend(
true,
[],
data["_embedded"]["attributes"]
);
this.attributesorg = jQuery.extend(
true,
[],
data["_embedded"]["attributes"]
);
this.recoverChangedAttributes();
},
(error) => {
console.log(error);
this.attributes = null;
}
);
this.reload.asObservable().subscribe((data) => {
this.currentLocale = data;
});
}
private recoverFromData(data, attributes: Attribute[]) {
this.attributes = [];
this.changedAttributes = [];
Object.keys(data).forEach((key) => {
// We need to deep copy the object, so that the angular change detection will still work while in OnPush (we need to replace the reference)
const attribute = deepCopy(
attributes.find((attribute) => attribute.identifier == key)
);
attribute.source = [{ value: data[key] }];
this.attributes.push(attribute);
this.changedAttributes.push(attribute);
});
this.changedAttributesOutput.next(this.changedAttributes);
if (this.attributesLocalstorageEntry) {
this.attributesLocalstorageEntry.value = JSON.stringify(this.attributes);
}
this.localstorageAttributesEntry.value = JSON.stringify(
this.changedAttributes
);
}
private checkIsAttributeEmpty(attribute: Attribute): boolean {
const isRootEmpty =
!attribute.source[0].value ||
attribute.source[0].value === "" ||
attribute.source.length === 0;
// The root is not empty, it cant be empty
if (!isRootEmpty) {
return false;
}
// The root is empty and it has no range attributes, it must be empty
if (!attribute.from) {
return true;
}
//Check if the children are empty
else
return (
this.checkIsAttributeEmpty(attribute.from) &&
this.checkIsAttributeEmpty(attribute.to)
);
}
private recoverChangedAttributes() {
if (this.localstorageAttributesEntry.exists()) {
this.changedAttributes = JSON.parse(
this.localstorageAttributesEntry.value
);
for (const changedAttribute of this.changedAttributes) {
let dublicate = this.attributes.filter(
(item) => item.identifier == changedAttribute.identifier
);
if (dublicate.length === 1) {
dublicate[0].source = changedAttribute.source;
if (changedAttribute.from) {
dublicate[0].from = changedAttribute.from;
dublicate[0].to = changedAttribute.to;
dublicate[0].displayRange = changedAttribute.displayRange;
}
}
}
this.changedAttributesOutput.next(this.changedAttributes);
}
}
resetWidget() {
return null;
}
ngOnDestroy() {
this.unsubscribe.destroy();
}
toogleContentVisibility() {
this.contentVisible = !this.contentVisible;
this.localStorageVisibiltyEntry.value = this.contentVisible.toString();
}
public editShownAttributes() {
let dialogRef = this.dialogService.open(SelectAttributesDialogComponent, {
minWidth: "900px",
maxWidth: "900px",
height: "755px",
});
dialogRef.componentInstance.preselectedAttributes = this.attributes || [];
dialogRef.componentInstance.currentLocale = this.currentLocale;
dialogRef.componentInstance.attributeUrl = throwIfUndefined(
this.selectAttributeUrl
);
dialogRef.componentInstance.infoText = "infoText.select.attributes";
dialogRef.afterClosed().subscribe((data) => {
if (data) {
let changedAttributesChanged = false;
var i = this.changedAttributes.length;
// If we have a changed attribute that is no longer editable we need to remove it from the list of changedattributes and output the changed attributes
while (i--) {
const entry = this.changedAttributes[i];
const attribute = data.find(
(val) => val.identifier === entry.identifier
);
if (!attribute) {
this.changedAttributes.splice(i, 1);
changedAttributesChanged = true;
}
}
if (changedAttributesChanged) {
this.changedAttributesOutput.next(this.changedAttributes);
}
if (this.attributes) {
data.forEach((entry) => {
if (!this.multiLookupsAllowed && entry.type === "MULTI_LOOKUP") {
entry.type = "LOOKUP";
}
const value = this.attributes.find(
(att) => att.identifier === entry.identifier
);
if (value) {
entry.source = value.source;
}
});
}
this.attributes = data;
this.attributesLocalstorageEntry.value = JSON.stringify(data);
}
});
}
showLabel(attribute) {
return !(this.allowRange && this.attributeSupportsRange(attribute));
}
private attributeSupportsRange(attribute) {
return attribute.type !== "BOOLEAN";
}
public rangeChange(event, attribute: Attribute) {
attribute.displayRange = event.checked;
if (attribute.from) {
//Make sure that the attribute and the change attribute instance are === so that changes to displayRange will be copied to the changed attribute (needed for tooltip)
} else if (event.checked) {
// This will deep copy the source of the root to from and to! If this is a problem we need to cleanse the source before / while copping
const fromClone: Attribute = JSON.parse(JSON.stringify(attribute));
const toClone = JSON.parse(JSON.stringify(attribute));
fromClone.identifier = fromClone.identifier + "-from";
toClone.identifier = toClone.identifier + "-to";
attribute.from = fromClone;
attribute.to = toClone;
}
// Emit change, since changing the slider changes the search
this.changedAttributesOutput.next(this.changedAttributes);
}
public getChangedAttributesForHeader() {
const result = [];
this.changedAttributes.forEach((value) => {
if (value.displayRange) {
let from;
let to;
if (value.from.source[0] && value.from.source[0].value) {
from = value.from.source[0].value;
}
if (value.to.source[0] && value.to.source[0].value) {
to = value.to.source[0].value;
}
let description = from ? from : "?";
if (to) {
description += " - " + to;
}
const fakeValue = {
description: value.description,
source: [
{
description,
},
],
};
result.push(fakeValue);
} else {
result.push(value);
}
});
return result;
}
}
<ng-template #popTemplateChangedAttributes>
<div class="nm-attribute-list-tooltip">
<div
class="nm-attribute-list-elements"
*ngFor="let attribute of getChangedAttributesForHeader()"
>
<div class="nm-attribute-list-description">
<nm-ellipsis [content]="attribute.description"></nm-ellipsis>
</div>
<div class="nm-attribute-list-value">
{{
attribute.source[0].description
? attribute.source[0].description
: attribute.source[0].value
}}
</div>
</div>
</div>
</ng-template>
<div
slot="title"
class="nm-widgetframe__title"
*ngIf="canEditShownAttributes || (attributes && attributes.length > 0)"
>
<mat-checkbox
class="nm-widgetframe__title-inline-container"
[checked]="contentVisible"
color="primary"
(change)="toogleContentVisibility()"
>{{ "label.advanced.search" | translate }}</mat-checkbox
>
<mat-icon
color="primary"
[hidden]="!(this.changedAttributes.length > 0)"
class="nm-widgetframe__title-icon nm-widgetframe__title-toggle"
[popover]="popTemplateChangedAttributes"
popoverTitle="{{ 'active.attribute.filters' | translate }}"
placement="bottom"
container="body"
triggers="mouseenter:mouseleave"
>filter_list</mat-icon
>
</div>
<div
slot="content"
class="nm-widgetframe__content"
[class.hidden]="!contentVisible"
>
<button
mat-mini-fab
color="primary"
class="nm-edit-shown-attributes"
(click)="editShownAttributes()"
*ngIf="canEditShownAttributes"
>
<mat-icon>add</mat-icon>
</button>
<p-dataGrid
[value]="attributes"
[rows]="1000"
[paginator]="false"
emptyMessage=" "
>
<ng-template let-attribute pTemplate="body">
<div class="nm-attribute-list-elements">
<ng-container *ngIf="showLabel(attribute); else noLabel">
<div
class="nm-attribute-list-description"
*ngIf="showLabel(attribute)"
>
<nm-ellipsis
[content]="
translateAttributeDescriptions
? (attribute.description | translate)
: attribute.description
"
></nm-ellipsis>
</div>
<div class="nm-attribute-list-value">
<nm-edit-attribute
[attribute]="attribute"
[copyContentButton]="false"
[onlyInputsForTexts]="true"
[editAttributeService]="_editAttributeService"
[floatingLabel]="false"
>
</nm-edit-attribute>
</div>
</ng-container>
<ng-template #noLabel>
<div class="range-wrapper">
<ng-container *ngIf="attribute.displayRange; else noRange">
<div class="range-wrapper-left">
<nm-edit-attribute
[attribute]="attribute.from"
[copyContentButton]="false"
[onlyInputsForTexts]="true"
[loadOptionsLazy]="true"
[displayValueAssets]="true"
[editAttributeService]="_editAttributeService"
[floatingLabel]="
('from' | translate) +
' ' +
(translateAttributeDescriptions
? (attribute.description | translate)
: attribute.description)
"
>
</nm-edit-attribute>
</div>
<div class="range-wrapper-right">
<nm-edit-attribute
[attribute]="attribute.to"
[copyContentButton]="false"
[onlyInputsForTexts]="true"
[loadOptionsLazy]="true"
[displayValueAssets]="true"
[editAttributeService]="_editAttributeService"
[floatingLabel]="
('to' | translate) +
' ' +
(translateAttributeDescriptions
? (attribute.description | translate)
: attribute.description)
"
>
</nm-edit-attribute>
</div>
</ng-container>
<ng-template #noRange>
<nm-edit-attribute
[attribute]="attribute"
[copyContentButton]="false"
[onlyInputsForTexts]="true"
[loadOptionsLazy]="true"
[displayValueAssets]="true"
[editAttributeService]="_editAttributeService"
[floatingLabel]="
translateAttributeDescriptions
? (attribute.description | translate)
: attribute.description
"
>
</nm-edit-attribute>
</ng-template>
</div>
<mat-slide-toggle
[checked]="attribute.displayRange"
(change)="rangeChange($event, attribute)"
></mat-slide-toggle>
</ng-template>
</div>
</ng-template>
</p-dataGrid>
</div>