nm-shop-category
src/app/shared/widgets/apps/my-shop-md/shop-category/shop-category.component.ts
changeDetection | ChangeDetectionStrategy.OnPush |
selector | nm-shop-category |
styleUrls | shop-category.component.scss |
templateUrl | ./shop-category.component.html |
Widget inputs |
Widget outputs |
Properties |
|
Methods |
|
constructor(categoryService: ShopCategoryService, appdataStore: AppdataStore, halService: HalService, dialog: MatDialog, _widgetframeService: WidgetframeService, _notificationService: CustomNotificationService, _progressbarService: ProgressbarService, zone: NgZone, dragAndDrop: DragAndDropService, localStorageService: LocalStorageService, _changeDetectorRef: ChangeDetectorRef)
|
||||||||||||||||||||||||||||||||||||
Parameters :
|
Protected configureWidget | ||||||
configureWidget(configuration: WidgetConfig)
|
||||||
Decorators : WidgetConfigure
|
||||||
Parameters :
Returns :
void
|
Private doReload | ||||
doReload(withReset: )
|
||||
Parameters :
Returns :
void
|
Private forceLoad | ||||||
forceLoad(data: any)
|
||||||
Parameters :
Returns :
Observable<any>
|
Private forceLoadChildren | |||||||||
forceLoadChildren(categories: any, path: )
|
|||||||||
Parameters :
Returns :
Observable<any>
|
getChildren | ||||||
getChildren(node: any)
|
||||||
Parameters :
Returns :
any
|
loadChildren | ||||
loadChildren(link: )
|
||||
Parameters :
Returns :
Observable<any>
|
markOffliine | ||||
markOffliine(node: )
|
||||
Parameters :
Returns :
void
|
ngOnInit |
ngOnInit()
|
Returns :
void
|
onActivatTreeNode | ||||
onActivatTreeNode(event: )
|
||||
Parameters :
Returns :
void
|
onUpdateData |
onUpdateData()
|
Returns :
void
|
refreshDragAndDrop |
refreshDragAndDrop()
|
Returns :
void
|
transformData | ||||||
transformData(data: any[])
|
||||||
Parameters :
Returns :
any[]
|
trim | ||||||
trim(arr: , key: )
|
||||||
Parameters :
Returns :
any
|
updateOutput | ||||
updateOutput(event: )
|
||||
Parameters :
Returns :
void
|
updateState | ||||||||
updateState(categories: , id: , isVisible: )
|
||||||||
Parameters :
Returns :
void
|
Public _id |
_id:
|
Type : string
|
Decorators : WidgetId
|
Public appdata |
appdata:
|
Type : Appdata
|
Public categories |
categories:
|
Type : any[]
|
Private clearChannel |
clearChannel:
|
Type : Subject<any>
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Private clearTextSearch |
clearTextSearch:
|
Type : Subject<any>
|
Decorators : WidgetOutput
|
Public colorListIdentifier |
colorListIdentifier:
|
Type : string
|
Public configuration |
configuration:
|
Type : WidgetConfig
|
Decorators : WidgetConfiguration
|
Private currentLevel |
currentLevel:
|
Type : number
|
Default value : 0
|
Private currentUri |
currentUri:
|
Type : any
|
customTemplateStringOptions |
customTemplateStringOptions:
|
Type : object
|
Default value : {
getChildren: this.getChildren.bind(this),
idField: "identifier",
//actionMapping
}
|
Public dataType |
dataType:
|
Type : string
|
Static DEFAULT_OUTPUT_TYPE |
DEFAULT_OUTPUT_TYPE:
|
Type : string
|
Default value : "uri"
|
Public dialog |
dialog:
|
Type : MatDialog
|
Public idForOffline |
idForOffline:
|
Public infotext |
infotext:
|
Type : string
|
Public inputLink |
inputLink:
|
Type : string
|
Public localstorageSelectedCategory |
localstorageSelectedCategory:
|
Type : string
|
Public localstorageSelectedCategoryEntry |
localstorageSelectedCategoryEntry:
|
Type : LocalStorageEntry
|
Public maintenanceLevel |
maintenanceLevel:
|
onDeactivateTreeNode |
onDeactivateTreeNode:
|
Default value : () => {
this.localstorageSelectedCategoryEntry.clear();
}
|
Public orderedResults |
orderedResults:
|
Type : any
|
Public outputType |
outputType:
|
Type : string
|
Private outputUri |
outputUri:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Public popupfilter |
popupfilter:
|
Type : any
|
Default value : {}
|
Private profile |
profile:
|
Type : Subject<any>
|
Decorators : WidgetOutput
|
Private programaticExpansion |
programaticExpansion:
|
Type : boolean
|
Default value : false
|
Private reloadChannel |
reloadChannel:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Private resetChannel |
resetChannel:
|
Type : Subject<any>
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Public rootCategoryLink |
rootCategoryLink:
|
Type : string
|
Public title |
title:
|
Type : string
|
tree |
tree:
|
Decorators : ViewChild
|
Private unsubscribe |
unsubscribe:
|
Default value : NgUnsubscribe.create()
|
Private uriParams |
uriParams:
|
Type : Subject<any>
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Public wikiLink |
wikiLink:
|
Type : string
|
import { of as observableOf, Subject, Observable } from "rxjs";
import {
mergeMap,
takeUntil,
filter,
switchMap,
mapTo,
map,
skip,
tap,
} from "rxjs/operators";
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
NgZone,
Output,
ViewChild,
} from "@angular/core";
import {
AppdataStore,
Appdata,
} from "../../../../components/appdata/appdata.store";
import { WidgetConfig } from "../../../widget.configuration";
import {
WidgetComponent,
WidgetConfiguration,
WidgetConfigure,
WidgetId,
WidgetInput,
WidgetOutput,
} from "../../../widget.metadata";
import { WidgetframeService } from "../../../widgetframe/widgetframe.service";
import { ShopCategory } from "./index";
import { ShopCategoryService } from "./shop-category.service";
import { NgUnsubscribe } from "../../../../ng-unsubscribe";
import { HalService } from "../../../../components/hal/hal.service";
import { MatDialog } from "@angular/material/dialog";
import { ShopCategoryDetailsPopupComponent } from "./shop-category-details-popup.component";
import { ShopCategoryPropertiesPopupComponent } from "./shop-category-properties-popup.component";
import { ProgressbarService } from "../../../../components/progressbar/progressbar.service";
import { IActionMapping, KEYS, TREE_ACTIONS } from "angular-tree-component";
import { CustomNotificationService } from "../../../../components/notification/customnotification.service";
import { ActionRequest } from "../../../../components/hal/actionRequest";
import { DragAndDropService } from "../../../../components/util/drag-and-drop.service";
import * as uriTemplates_ from "uri-templates";
import {
LocalStorageEntry,
LocalStorageService,
} from "../../../../components/local-storage/local-storage.service";
import {
DeletionMode,
Scope,
} from "../../../../components/local-storage/local-storage-constants";
const uriTemplates = uriTemplates_;
declare var $;
declare var jQuery: any;
const actionMapping: IActionMapping = {
mouse: {
contextMenu: (tree, node, $event) => {
$event.preventDefault();
},
dblClick: (tree, node, $event) => {
if (node.hasChildren) TREE_ACTIONS.TOGGLE_EXPANDED(tree, node, $event);
},
click: (tree, node, $event) => {
if (!node.hasChildren) {
$event.ctrlKey
? TREE_ACTIONS.TOGGLE_ACTIVE_MULTI(tree, node, $event)
: TREE_ACTIONS.TOGGLE_SELECTED(tree, node, $event);
}
},
},
keys: {
[KEYS.ENTER]: (tree, node, $event) => {
node.expandAll();
},
},
};
@WidgetComponent("nm-shop-category")
@Component({
selector: "nm-shop-category",
templateUrl: "./shop-category.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ["./shop-category.component.scss"],
})
export class ShopCategoryWidgetComponent {
private currentLevel: number = 0;
private programaticExpansion: boolean = false;
public inputLink: string;
public rootCategoryLink: string;
public dataType: string;
public localstorageSelectedCategory: string;
public localstorageSelectedCategoryEntry: LocalStorageEntry;
public categories: any[];
public appdata: Appdata;
public wikiLink: string;
public infotext: string;
public title: string;
public idForOffline;
public maintenanceLevel;
public orderedResults: any;
public popupfilter: any = {};
public outputType: string;
public colorListIdentifier: string;
private currentUri: any;
@Output()
public emitCategory = new EventEmitter<ShopCategory>();
@WidgetOutput("uri")
private outputUri = new Subject<any>();
@WidgetOutput("profile")
private profile: Subject<any>;
@WidgetInput("reload")
private reloadChannel = new Subject<any>();
@WidgetInput("reset")
private resetChannel: Subject<any> = new Subject<any>();
@WidgetInput("clearcategorysearch")
private clearChannel: Subject<any> = new Subject<any>();
@WidgetInput("uriParams")
private uriParams: Subject<any> = new Subject<any>();
@WidgetOutput("cleartextsearch")
private clearTextSearch: Subject<any>;
@WidgetConfiguration()
public configuration: WidgetConfig;
@WidgetId()
public _id: string;
@ViewChild("tree", { static: true })
tree;
private unsubscribe = NgUnsubscribe.create();
customTemplateStringOptions = {
getChildren: this.getChildren.bind(this),
idField: "identifier",
//actionMapping
};
public static DEFAULT_OUTPUT_TYPE: string = "uri";
constructor(
private categoryService: ShopCategoryService,
private appdataStore: AppdataStore,
private halService: HalService,
public dialog: MatDialog,
private _widgetframeService: WidgetframeService,
private _notificationService: CustomNotificationService,
private _progressbarService: ProgressbarService,
private zone: NgZone,
private dragAndDrop: DragAndDropService,
private localStorageService: LocalStorageService,
private _changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
this.halService
.getActionEvents()
.pipe(filter((event) => event.name === "my-shopmd-remove-dimensions"))
.subscribe((resp) => {
if (!!this.currentUri) {
this.outputUri.next(this.currentUri);
this._changeDetectorRef.markForCheck();
}
});
this.halService
.getActionEvents()
.pipe(filter((event) => event.name === "my-shopmd-save-properties"))
.subscribe((resp) => {
this._notificationService.fromJson(resp.response);
});
this.halService
.getActionEvents()
.pipe(
filter((event) => event.name === "details"),
map((event) => (<any>event).response)
)
.subscribe((resp) => {
this.dialog.closeAll();
let dialogRef = this.dialog.open(ShopCategoryDetailsPopupComponent);
dialogRef.componentInstance["link"] = resp.href;
dialogRef.componentInstance["popupfilter"] = this.popupfilter;
dialogRef.componentInstance["attachActionIdentifier"] =
"my-shopmd-attach-dimensions";
dialogRef.componentInstance[
"colorListIdentifier"
] = this.colorListIdentifier;
dialogRef.afterClosed().subscribe((response) => {
this._changeDetectorRef.markForCheck();
});
});
this.halService
.getActionEvents()
.pipe(
filter((event) => event.name === "properties"),
map((event) => (<any>event).response)
)
.subscribe((resp) => {
this.dialog.closeAll();
this.idForOffline = resp.payload.identifier;
let clickedNode = this.tree.treeModel.getNodeById(this.idForOffline);
let category = clickedNode.data.name;
let node = this.tree.treeModel.getNodeById(resp.payload.identifier);
let url = clickedNode.data._links.self.href;
this._widgetframeService.getData(url).subscribe((data) => {
let node = this.tree.treeModel.getNodeById(resp.payload.identifier);
let dialogRef = this.dialog.open(
ShopCategoryPropertiesPopupComponent
);
dialogRef.componentInstance["title"] = "Kategorieeigenschaften";
dialogRef.componentInstance["category"] = category;
dialogRef.componentInstance["data"] = data;
dialogRef.afterClosed().subscribe((isVisible) => {
if (typeof isVisible === "undefined" || isVisible == null) {
return;
}
this.updateState(this.categories, this.idForOffline, isVisible);
this._changeDetectorRef.markForCheck();
});
});
});
}
updateState(categories, id, isVisible) {
let node = this.tree.treeModel.getNodeById(id);
node.data.isOnline = isVisible;
}
markOffliine(node) {
node.data.isOnline = true;
}
getChildren(node: any) {
let unsubscribe = this.unsubscribe;
return new Promise((resolve, reject) => {
if (
node.data._links &&
node.data._links.children &&
node.data._links.children.href
) {
this.loadChildren(node.data._links.children.href)
.pipe(takeUntil(unsubscribe))
.subscribe(
(data) => {
resolve(this.transformData(data._embedded.categories));
this.refreshDragAndDrop();
},
(err) => reject(err)
);
} else {
resolve(null);
}
});
}
loadChildren(link): Observable<any> {
return this.categoryService.getCategories(link);
}
onUpdateData() {
if (
this.localstorageSelectedCategoryEntry.exists() &&
this.currentLevel === 0
) {
let path = JSON.parse(this.localstorageSelectedCategoryEntry.value);
let node = this.tree.treeModel.getNodeByPath(path);
if (node && node.isLeaf) {
node.setActiveAndVisible();
}
}
}
onActivatTreeNode(event) {
this.emitCategory.emit(event.node);
let category = event.node;
this.clearTextSearch.next(Date.now);
this.localstorageSelectedCategoryEntry.value = JSON.stringify(
category.path
);
if (category.data._links[this.dataType] !== undefined) {
this.outputUri.next(category.data._links[this.dataType].href);
this.profile.next("categorySearchResult");
} else {
this.outputUri.next(null);
}
this._changeDetectorRef.markForCheck();
}
/*
onActivatTreeNode = (event) => {
this.refreshDragAndDrop();
this.updateOutput(event);
}
*/
onDeactivateTreeNode = () => {
this.localstorageSelectedCategoryEntry.clear();
};
updateOutput(event) {
this.emitCategory.emit(event.node);
let category = event.node;
this.clearTextSearch.next(Date.now);
this.localstorageSelectedCategoryEntry.value = JSON.stringify(
category.path
);
if (category.data._links[this.dataType] !== undefined) {
if (this.outputType === ShopCategoryWidgetComponent.DEFAULT_OUTPUT_TYPE) {
this.outputUri.next(category.data._links[this.dataType].href);
} else {
var categories = [];
for (var item of event.node.treeModel.activeNodes) {
categories.push(item.data.identifier);
}
this.outputUri.next(categories);
}
this.profile.next("categorySearchResult");
} else {
this.outputUri.next(null);
}
}
@WidgetConfigure()
protected configureWidget(configuration: WidgetConfig) {
this.popupfilter = this.configuration.configuration["popupfilter"];
this.infotext = this.configuration.configuration["infoText"];
this.wikiLink = this.configuration.configuration["wikiLink"];
this.title = this.configuration.configuration["title"];
this.dataType = "dimensions"; //"this.configuration.configuration["dataType"]";
this.localstorageSelectedCategoryEntry = this.localStorageService.getLocalStorageEntry(
this.configuration.configuration["localstorage-category"],
Scope.GLOBAL,
DeletionMode.LOGIN
);
this.colorListIdentifier = this.configuration.configuration[
"colorListIdentifier"
];
if (!this.colorListIdentifier) {
console.error(
"colorListIdentifier is not configured but needed to work properly"
);
}
this.uriParams
.pipe(
map((uriParams) => {
uriParams["context"] = "my-shopMd";
let template = uriTemplates(
this.configuration["_links"]["categories"]["href"]
);
this.rootCategoryLink = template.fill(uriParams);
return this.rootCategoryLink;
}),
skip(1),
mergeMap((uri) => {
return this.categoryService.getRootCategories(uri);
}),
takeUntil(this.unsubscribe),
mergeMap((data) => this.forceLoad(data))
)
.subscribe((data) => {
this.categories = this.transformData(data._embedded.categories);
setTimeout(() => {
this.onUpdateData();
this.refreshDragAndDrop();
}, 1);
});
/*
this.reloadChannel
.asObservable()
.takeUntil(this.unsubscribe)
.subscribe(reload => {
this.doReload(false);
this._changeDetectorRef.markForCheck();
});
this.clearChannel
.asObservable()
.takeUntil(this.unsubscribe)
.subscribe(reset => {
localStorage.removeItem(this.localstorageSelectedCategory);
this.doReload(true);
this._changeDetectorRef.markForCheck();
});
this.resetChannel
.asObservable()
.subscribe(reset => {
localStorage.removeItem(this.localstorageSelectedCategory);
this.doReload(true);
this._changeDetectorRef.markForCheck();
});
*/
this.categoryService
.getOrderedResults(this.colorListIdentifier)
.subscribe((data) => {
this.orderedResults = data;
this._changeDetectorRef.markForCheck();
});
this.outputUri
.pipe(
switchMap((uri) => {
this.categoryService.setCurrentDimensionUri(
this.colorListIdentifier,
uri
);
return this.categoryService.loadOrderedResults(uri);
})
)
.subscribe((data) => {
this.orderedResults = data;
this.categoryService.setOrderedResults(this.colorListIdentifier, data);
this._progressbarService.requestFinished();
this._changeDetectorRef.markForCheck();
});
}
private forceLoad(data: any): Observable<any> {
const localStorageValue = this.localstorageSelectedCategoryEntry.value;
if (localStorageValue) {
const path = JSON.parse(localStorageValue);
//If the path is > 1 we need to load the children
const categories = data._embedded.categories;
if (path.length > 1) {
return this.forceLoadChildren(categories, path).pipe(mapTo(data));
}
return observableOf(data);
} else {
return observableOf(data);
}
}
private forceLoadChildren(categories: any, path): Observable<any> {
const current = path.shift();
const category = categories.find(
(category) => category.identifier == current
);
if (category && category._links.children) {
if (path.length == 0) {
return this.loadChildren(category._links.children.href).pipe(
tap(
(data) =>
(category.children = this.transformData(
data._embedded.categories
))
)
);
} else {
return this.loadChildren(category._links.children.href).pipe(
mergeMap((data) => {
category.children = this.transformData(data._embedded.categories);
return this.forceLoadChildren(category.children, path).pipe(
mapTo(data)
);
})
);
}
} else {
return observableOf([]);
}
}
private doReload(withReset) {
this.currentLevel = 0;
this.categoryService
.getRootCategories(this.rootCategoryLink)
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
this.categories = this.transformData(data._embedded.categories);
this.refreshDragAndDrop();
if (withReset) {
this.tree.treeModel.collapseAll();
this.currentLevel = 0;
let activeNode = this.tree.treeModel.getActiveNode();
if (activeNode) {
activeNode.toggleActivated();
}
}
});
}
refreshDragAndDrop(): void {
let treeModel = this.tree.treeModel;
let halService = this.halService;
let self = this;
this.zone.runOutsideAngular(() => {
setTimeout(() => {
jQuery(".custom-node").droppable({
tolerance: "pointer",
accept: ".ui-widget-content", // prevent drops from details-popup
scroll: true,
greedy: true,
// Add .hoverClass whenever #draggable is being hovered over #droppable
over: function (event, ui) {
$(".ui-draggable-dragging").addClass("hoverClass");
},
// Remove .hoverClass whenever #draggable is no longer hovered over #droppable
out: function (event, ui) {
$(".ui-draggable-dragging").removeClass("hoverClass");
},
// Add .dropClass whenever #draggable is dropped on #droppable
drop: (event, ui) => {
let nodeId = jQuery(event.target).attr("id");
let node = treeModel.getNodeById(nodeId);
let action = <ActionRequest>(
node.data._actions["my-shopmd-attach-dimensions"]
);
let dragged = self.dragAndDrop.completeDrag();
let identifiers = dragged.data.map((item) => item.identifier);
action.payload = {};
action.payload.identifiers = identifiers;
this.zone.run(() => {
halService
.execute("my-shopmd-attach-dimensions", action)
// reload the current category dimensions list
.subscribe((response) => {
node.setActiveAndVisible(false);
let uri = node.data._links["dimensions"];
if (!!uri) {
this.outputUri.next(uri.href);
}
});
});
(ui.helper || ui.item).remove();
},
});
}, 1);
});
this._changeDetectorRef.markForCheck();
}
trim(arr, key) {
var values = {};
return arr.filter(function (item) {
var val = item[key];
var exists = values[val];
values[val] = true;
return !exists;
});
}
transformData(data: any[]): any[] {
if (!data) {
return;
}
for (var item of data) {
item.label = item.description;
item.data = item.identifier;
item.name = item.description;
if (item.properties != null) {
item._actions["details"] = {
type: "event",
href: item._links.products.href,
};
item._actions["properties"] = {
type: "event",
href: item._links.products.href,
payload: { identifier: item.identifier },
};
}
item.isOnline = item["is-published"];
item.expandedIcon = "fa-folder-open";
item.collapsedIcon = "fa-folder";
item.allowDrag = false;
if (item._links.children != undefined) {
item.expandedIcon = "fa-folder-open";
item.collapsedIcon = "fa-folder";
item.leaf = false;
item.hasChildren = true;
} else {
item.expandedIcon = "fa-folder-open";
item.collapsedIcon = "fa-folder-o";
item.leaf = true;
item.hasChildren = false;
}
}
this._changeDetectorRef.markForCheck();
return data;
}
}
<div slot="content" class="nm-widgetframe__content" style="margin-top: 25px">
<tree-root
#tree
[nodes]="categories"
[options]="customTemplateStringOptions"
(activate)="onActivatTreeNode($event)"
(updateData)="onUpdateData()"
>
<ng-template #loadingTemplate>{{
"placeholder.loading" | translate
}}</ng-template>
<ng-template #treeNodeTemplate let-node let-index="index">
<div
class="custom-node"
[id]="node.data.identifier"
[class.disabled]="!node.data.isOnline"
>
<div class="custom-node-content-wrapper">{{ node.data.name }}</div>
<div
class="custom-node-action-wrapper nm-actions"
style="padding-right: 5px"
>
<span *ngFor="let act of node.data._actions | iterable">
<nm-action-icon
[ngClass]="act.key"
*ngIf="
act.key != 'my-shopmd-save-properties' &&
act.key != 'my-shopmd-attach-dimensions' &&
act.key != 'my-shopmd-remove-dimensions'
"
[action]="act.value"
[name]="act.key"
>
</nm-action-icon>
</span>
</div>
</div>
</ng-template>
</tree-root>
</div>