nm-category
src/app/shared/widgets/buy/category/category.component.ts
Used in iPIM Buy amd iPIM Apps. Surrounded by Widgetframe or diaplayed inline.
Tree component used for category trees.
AfterViewChecked
AfterViewInit
providers |
CategoryService
|
selector | nm-category |
styleUrls | category.component.scss |
templateUrl | ./category.component.html |
constructor(categoryService: CategoryService, appdataStore: AppdataStore, _scrollService: ScrollService, localStorageService: LocalStorageService, translateService: TranslateService, currentLocaleService: CurrentLocaleService, _widgetFrameService: WidgetframeService)
|
||||||||||||||||||||||||
Parameters :
|
Public autoSelectRootNode |
autoSelectRootNode()
|
Returns :
void
|
Protected configureWidget | ||||||
configureWidget(configuration: WidgetConfig
|
||||||
Decorators : WidgetConfigure
|
||||||
Parameters :
Returns :
void
|
Private disableNodeAction |
disableNodeAction(category: any, actionName: string, disabled: boolean)
|
Returns :
void
|
Private disableNodeChildrenAction |
disableNodeChildrenAction(children: any[], actionName: string, disabled: boolean)
|
Returns :
void
|
Private doReload | ||||
doReload(withReset: )
|
||||
Parameters :
Returns :
void
|
Private emitSelection | ||||||
emitSelection(selection: any[])
|
||||||
Parameters :
Returns :
void
|
Private forceLoad | ||||||||||||
forceLoad(data: CategoryResource, path: string[])
|
||||||||||||
Parameters :
Returns :
Observable<literal type>
|
Private forceLoadChildren | |||||||||
forceLoadChildren(categories: any, path: )
|
|||||||||
Parameters :
Returns :
Observable<any[]>
|
Public getNodeClass | ||||
getNodeClass(node: )
|
||||
Parameters :
Returns :
"no-search-hit" | "search-hit"
|
getSelectableClass | ||||||
getSelectableClass(node: any)
|
||||||
Parameters :
Returns :
string
|
includeChildrenChange | ||||||
includeChildrenChange(event: MatSlideToggleChange)
|
||||||
Parameters :
Returns :
void
|
isSelectionAllowed |
isSelectionAllowed()
|
Returns :
boolean
|
loadChildren | ||||
loadChildren(link: )
|
||||
Parameters :
Returns :
Observable<any>
|
ngAfterViewChecked |
ngAfterViewChecked()
|
Returns :
void
|
ngAfterViewInit |
ngAfterViewInit()
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Returns :
void
|
Public onDrag | ||||||
onDrag(event: OnDragEvent)
|
||||||
Parameters :
Returns :
void
|
Public onDropped | ||||||
onDropped(event: OnDroppedEvent)
|
||||||
Parameters :
Returns :
void
|
onSelectionEvent | ||||||
onSelectionEvent(event: SelectionEvent)
|
||||||
Parameters :
Returns :
void
|
toogleContentVisibility |
toogleContentVisibility()
|
Returns :
void
|
transformData | ||||||||||||
transformData(data: any[], parentIdentifier: any)
|
||||||||||||
Parameters :
Returns :
any[]
|
Public _id |
_id:
|
Type : string
|
Decorators : WidgetId
|
Private _searchUri |
_searchUri:
|
Type : string
|
Default value : null
|
Public allowDrag |
allowDrag:
|
Type : boolean
|
Default value : false
|
Public allowSelectableAll |
allowSelectableAll:
|
Type : boolean
|
Default value : false
|
Public appdata |
appdata:
|
Type : Appdata
|
Public autoSelectRootNodeButtonClicked |
autoSelectRootNodeButtonClicked:
|
Default value : new Subject<boolean>()
|
Decorators : WidgetOutput
|
Auto select root node button is clicked |
Public autoSelectRootNodeButtonDisabled |
autoSelectRootNodeButtonDisabled:
|
Type : boolean
|
Default value : true
|
Public categories |
categories:
|
Type : any[]
|
Default value : []
|
Private categoriesLoaded |
categoriesLoaded:
|
Default value : new Subject<boolean>()
|
Decorators : WidgetOutput
|
Loaded categories |
Private categoryChannel |
categoryChannel:
|
Default value : new ReplaySubject<string[]>(1)
|
Decorators : WidgetInput
|
Input to select given category, based on its path |
Public categoryExpand |
categoryExpand:
|
Type : boolean
|
Default value : false
|
Public categoryMoved |
categoryMoved:
|
Type : Subject<any>
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Emits when a node is dragged and dropped inside the tree |
Private clearChannel |
clearChannel:
|
Type : Subject<any>
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Clears the current selection and reloads |
Private clearTextSearch |
clearTextSearch:
|
Type : Subject<any>
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Emits the current date, every time the output is updated |
Public configuration |
configuration:
|
Type : WidgetConfig<CategoryConfiguration>
|
Decorators : WidgetConfiguration
|
Public contentVisible |
contentVisible:
|
Type : boolean
|
Default value : true
|
Public currentCategory |
currentCategory:
|
Type : any
|
Public currentDescription |
currentDescription:
|
Type : string
|
Default value : ""
|
Private currentLevel |
currentLevel:
|
Type : number
|
Default value : 0
|
Public currentPublication |
currentPublication:
|
Type : string
|
Default value : ""
|
Public currentVersion |
currentVersion:
|
Type : string
|
Default value : ""
|
Public dataType |
dataType:
|
Type : string
|
Static DEFAULT_OUTPUT_TYPE |
DEFAULT_OUTPUT_TYPE:
|
Type : string
|
Default value : "uri"
|
Private disableAutoSelectRootNodeButtonChannel |
disableAutoSelectRootNodeButtonChannel:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Disable auto select root node button |
Public displayIncludeChildrenToggle |
displayIncludeChildrenToggle:
|
Type : boolean
|
Default value : false
|
Public displayScrollToTopIcon |
displayScrollToTopIcon:
|
Type : boolean
|
Default value : false
|
Public dynamicHeight |
dynamicHeight:
|
Type : boolean
|
Default value : false
|
Public dynamicHeightAdditionalHeight |
dynamicHeightAdditionalHeight:
|
Type : string
|
Public dynamicTreeHeightInDialog |
dynamicTreeHeightInDialog:
|
Type : boolean
|
Default value : false
|
Public emitSelectedCategory |
emitSelectedCategory:
|
Type : boolean
|
Default value : false
|
Public height |
height:
|
Type : string
|
Default value : ""
|
Public includeChildren |
includeChildren:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
The current value of the include children toggle |
Public includeChildrenValue |
includeChildrenValue:
|
Type : boolean
|
Default value : false
|
Public infotext |
infotext:
|
Type : string
|
Public initialExpansion |
initialExpansion:
|
Type : boolean
|
Default value : false
|
Public initLoad |
initLoad:
|
Type : boolean
|
Default value : false
|
Public inline |
inline:
|
Type : boolean
|
Default value : false
|
Public inputLink |
inputLink:
|
Type : string
|
Public isSrollToTopNeeded |
isSrollToTopNeeded:
|
Type : boolean
|
Default value : false
|
Private localstorageIncludeChildrenEntry |
localstorageIncludeChildrenEntry:
|
Type : LocalStorageEntry
|
Private localstorageLastRootUrlEntry |
localstorageLastRootUrlEntry:
|
Type : LocalStorageEntry
|
Private localstorageSelectedCategoryEntry |
localstorageSelectedCategoryEntry:
|
Type : LocalStorageEntry
|
Private localStorageVisibiltyEntry |
localStorageVisibiltyEntry:
|
Type : LocalStorageEntry
|
Private multi |
multi:
|
Default value : new ReplaySubject<boolean>()
|
Decorators : WidgetInput
|
Public outputType |
outputType:
|
Type : string
|
Private outputUri |
outputUri:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
The link of the category if the output type is set to 'uri' else the identifier of all current active nodes |
Private pendingRequests |
pendingRequests:
|
Type : number
|
Default value : 0
|
Private profile |
profile:
|
Type : Subject<any>
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Columns profile to be returned to search result list |
Private programaticExpansion |
programaticExpansion:
|
Type : boolean
|
Default value : false
|
Private reloadChannel |
reloadChannel:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
Force a reload of the data from the uri |
Public rootCategoryLink |
rootCategoryLink:
|
Type : string
|
Private rootUriInput |
rootUriInput:
|
Default value : new Subject<any>()
|
Decorators : WidgetInput
|
URI used to load root node |
Public searchingNodes |
searchingNodes:
|
Type : string[]
|
Private searchUriInput |
searchUriInput:
|
Default value : new Subject<string>()
|
Decorators : WidgetInput
|
Private selectedCategory |
selectedCategory:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
The currently selected category |
Public selectFirstNode |
selectFirstNode:
|
Type : boolean
|
Default value : false
|
Public showAutoSelectRootNodeButton |
showAutoSelectRootNodeButton:
|
Type : boolean
|
Default value : false
|
Public showAutoSelectRootNodeButtonLabel |
showAutoSelectRootNodeButtonLabel:
|
Type : string
|
Public title |
title:
|
Type : string
|
Public treeFilterable |
treeFilterable:
|
Type : boolean
|
Default value : false
|
Public treeHeight |
treeHeight:
|
Type : string | number
|
Default value : "550px"
|
Private treeInitialized |
treeInitialized:
|
Default value : new Subject()
|
treeSelector |
treeSelector:
|
Type : TreeSelectorComponent
|
Decorators : ViewChild
|
Private unsubscribe |
unsubscribe:
|
Default value : NgUnsubscribe.create()
|
Public wikiLink |
wikiLink:
|
Type : string
|
import {
from,
NEVER,
Observable,
of,
of as observableOf,
ReplaySubject,
Subject,
} from "rxjs";
import {
concatAll,
concatMap,
debounceTime,
distinctUntilChanged,
flatMap,
map,
mapTo,
mergeMap,
skip,
takeUntil,
tap,
scan,
last,
} from "rxjs/operators";
import {
AfterViewChecked,
AfterViewInit,
Component,
EventEmitter,
Input,
Output,
ViewChild,
} from "@angular/core";
import {
AppdataStore,
Appdata,
} from "../../../components/appdata/appdata.store";
import { getOrDefault, WidgetConfig } from "../../widget.configuration";
import {
IActionMapping,
KEYS,
TREE_ACTIONS,
TreeComponent,
} from "angular-tree-component";
import {
WidgetComponent,
WidgetConfiguration,
WidgetConfigure,
WidgetId,
WidgetInput,
WidgetOutput,
} from "../../widget.metadata";
import { Category } from "./index";
import { CategoryResource, CategoryService } from "./category.service";
import { NgUnsubscribe } from "../../../ng-unsubscribe";
import { MatSlideToggleChange } from "@angular/material/slide-toggle";
import { ScrollService } from "../../../components/scroll/scroll.service";
import { TranslateService } from "@ngx-translate/core";
import {
LocalStorageEntry,
LocalStorageService,
} from "../../../components/local-storage/local-storage.service";
import {
DeletionMode,
Scope,
} from "../../../components/local-storage/local-storage-constants";
import { CurrentLocaleService } from "../../../components/i18n/currentLocale.service";
import {
OnDragEvent,
OnDroppedEvent,
SelectionEvent,
} from "../../../components/tree-selector/tree-selector.api";
import { TreeSelectorComponent } from "../../../components/tree-selector/tree-selector.component";
import * as _uriTemplates from "uri-templates";
import { WidgetframeService } from "../../widgetframe/widgetframe.service";
const uriTemplates = _uriTemplates;
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();
},
},
};
export interface CategoryTreeConfiguration {
/**
* configure the tree's height
*/
height?: number | string;
/**
* `true` to display and enable the tree's filter field
*/
filterable?: boolean;
}
export interface CategoryConfiguration {
/**
* Initial value for include children check box. @default(false)
*/
includeChildrenValue?: boolean;
/**
* Expand tree categories. @default(false)
*/
categoryExpand?: boolean;
/**
* Select first node of the category tree. @default(false)
*/
selectFirstNode?: boolean;
/**
* Information text.
*/
infoText?: string;
/**
* Wiki help link.
*/
wikiLink?: string;
/**
* Category tree title.
*/
title?: string;
/**
* Category embedded _links, it should contain root category link in this path _links.categories.href
*/
_links?: any[];
/**
* Categories list name in json returned from backend.
*/
dataType?: string;
/**
* Local storage key for selected category.
*/
"localstorage-category"?: string;
/**
* Local storage key for include children checkbox value.
*/
"localstorage-include-children"?: string;
/**
* Allow selection of multiple categories. @default(false)
*/
multi?: boolean;
/**
* Output type. @default(uri)
*/
outputType?: string;
/**
* View category component inline or surrounded by widget frame. @default(false)
*/
inline?: boolean;
/**
* Display include children checkbox. @default(false)
*/
"display-include-children-toggle"?: boolean;
/**
* Display scroll to top icon. @default(false)
*/
"display-scroll-to-top"?: boolean;
/**
* Local storage key for visible content property.
*/
"localstorage-content-visible"?: string;
/**
* Show auto select root node button. @default(false)
*/
showAutoSelectRootNodeButton?: boolean;
/**
* Auto select root node button label
*/
showAutoSelectRootNodeButtonLabel?: string;
/**
* Whether to emit the selected category to "selectedCategory" widget output subject. @default(false)
*/
emitSelectedCategory?: boolean;
/**
* Allow drag and drop of categories. @default(false)
*/
allowDrag?: boolean;
/**
* Overrides the global statement whether a category is selectable or not. @default(false)
*/
allowSelectableAll?: boolean;
/**
* Configures the category selection tree
*/
tree?: CategoryTreeConfiguration;
/**
* Enables dynamicHeight calculation
*/
dynamicHeight?: boolean;
/**
* Dynamic height calculation container is set to
*/
dynamicTreeHeightInDialog?: boolean;
/**
* Height not filled by category tree
*/
dynamicHeightAdditionalHeight?: string;
}
/**
* Used in iPIM Buy amd iPIM Apps.
* Surrounded by Widgetframe or diaplayed inline.
*
* Tree component used for category trees.
*/
@WidgetComponent("nm-category")
@Component({
selector: "nm-category",
templateUrl: "./category.component.html",
styleUrls: ["./category.component.scss"],
providers: [CategoryService],
})
export class CategoryWidgetComponent
implements AfterViewChecked, AfterViewInit
{
private currentLevel: number = 0;
private programaticExpansion: boolean = false;
public inputLink: string;
public rootCategoryLink: string;
public dataType: string;
private localstorageSelectedCategoryEntry: LocalStorageEntry;
private localstorageIncludeChildrenEntry: LocalStorageEntry;
private localstorageLastRootUrlEntry: LocalStorageEntry;
private localStorageVisibiltyEntry: LocalStorageEntry;
public categories: any[] = [];
public appdata: Appdata;
public wikiLink: string;
public infotext: string;
public title: string;
public outputType: string;
public inline: boolean = false;
public currentVersion: string = "";
public currentPublication: string = "";
public currentDescription: string = "";
public currentCategory: any;
public contentVisible: boolean = true;
public includeChildrenValue: boolean = false;
public displayIncludeChildrenToggle: boolean = false;
public displayScrollToTopIcon: boolean = false;
public isSrollToTopNeeded: boolean = false;
public categoryExpand: boolean = false;
public initLoad: boolean = false;
public initialExpansion: boolean = false;
public selectFirstNode: boolean = false;
public showAutoSelectRootNodeButton: boolean = false;
public showAutoSelectRootNodeButtonLabel: string;
public autoSelectRootNodeButtonDisabled: boolean = true;
public emitSelectedCategory: boolean = false;
public treeHeight: string | number = "550px";
public treeFilterable: boolean = false;
public allowDrag: boolean = false;
public allowSelectableAll: boolean = false;
public height: string = "";
private _searchUri: string = null;
//The nodes that we are currently searching for
public searchingNodes: string[];
// The current amount of requests that are running to fetch children
private pendingRequests = 0;
public static DEFAULT_OUTPUT_TYPE: string = "uri";
@Output()
public emitCategory = new EventEmitter<Category>();
/**
* The currently selected category
*/
@WidgetOutput("category")
private selectedCategory = new Subject<any>();
/**
* Loaded categories
*/
@WidgetOutput()
private categoriesLoaded = new Subject<boolean>();
/**
* The current value of the include children toggle
*/
@WidgetOutput("includeChildren")
public includeChildren = new Subject<any>();
/**
* The link of the category if the output type is set to 'uri' else the identifier of all current active nodes
*/
@WidgetOutput("uri")
private outputUri = new Subject<any>();
/**
* Columns profile to be returned to search result list
*/
@WidgetOutput("profile")
private profile: Subject<any> = new Subject<any>();
/**
* Auto select root node button is clicked
*/
@WidgetOutput()
public autoSelectRootNodeButtonClicked = new Subject<boolean>();
/**
* Force a reload of the data from the uri
*/
@WidgetInput("reload")
private reloadChannel = new Subject<any>();
/**
* Disable auto select root node button
*/
@WidgetInput("disableAutoSelectRootNodeButton")
private disableAutoSelectRootNodeButtonChannel = new Subject<any>();
/**
* Takes a list of string arrays, which is the path of the categories to search for. This will expand the path to given nodes, and fade out every other node
*/
@WidgetInput("searchCategory")
private searchCategoryChannel = new ReplaySubject<string[][]>(1);
/**
* Input to select given category, based on its path
*/
@WidgetInput("category")
private categoryChannel = new ReplaySubject<string[]>(1);
/**
* URI used to load root node
*/
@WidgetInput("rooturi")
private rootUriInput = new Subject<any>();
@WidgetInput("searchuri")
private searchUriInput = new Subject<string>();
/**
* Disabling/Enabling action for one node, or Disabling/Enabling it for all nodes.
*/
@WidgetInput("disableAction")
private disableNodesActionChannel = new Subject<any>();
/**
* Clears the current selection and reloads
*/
@WidgetInput("clearcategorysearch")
private clearChannel: Subject<any> = new Subject<any>();
/**
* Emits the current date, every time the output is updated
*/
@WidgetOutput("cleartextsearch")
private clearTextSearch: Subject<any> = new Subject<any>();
@WidgetInput("multi")
private multi = new ReplaySubject<boolean>();
/**
* Emits when a node is dragged and dropped inside the tree
*/
@WidgetOutput("categoryMoved")
public categoryMoved: Subject<any> = new Subject<any>();
@WidgetConfiguration()
public configuration: WidgetConfig<CategoryConfiguration>;
@WidgetId()
public _id: string;
@ViewChild("treeSelector")
treeSelector: TreeSelectorComponent;
private unsubscribe = NgUnsubscribe.create();
private treeInitialized = new Subject();
public dynamicHeight: boolean = false;
public dynamicTreeHeightInDialog: boolean = false;
public dynamicHeightAdditionalHeight: string;
constructor(
private categoryService: CategoryService,
private appdataStore: AppdataStore,
private _scrollService: ScrollService,
private localStorageService: LocalStorageService,
private translateService: TranslateService,
private currentLocaleService: CurrentLocaleService,
private _widgetFrameService: WidgetframeService
) {}
ngOnInit() {
this.currentLocaleService
.getCurrentLocale()
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => (this.searchingNodes = null));
}
ngAfterViewInit() {}
ngAfterViewChecked() {
if (!this.treeSelector || this.treeSelector.rootNodes().length === 0) {
return;
}
if (this.initLoad) {
return;
}
this.treeInitialized.next();
if (
(this.categoryExpand || this.selectFirstNode) &&
this.treeSelector &&
this.treeSelector.rootNodes().length > 0
) {
// TODO: expand & select first root node
}
this.initLoad = true;
}
public loadChildrenOnFilter = (query: string) => {
if (this._searchUri && this.treeSelector) {
const uri = uriTemplates(this._searchUri).fill({ query });
return this._widgetFrameService.getData(uri).pipe(
tap((data) => {
if (!data.paths) {
return;
}
(data.paths as string[][]).forEach((paths) => {
// only expand up until the second-to-last node
// we want to see the retrieved nodes themselves, not their children
this.treeSelector.expand(paths.slice(0, -1));
});
})
);
//return timer(400);
}
return of();
};
isSelectionAllowed(): (id: any) => boolean {
if (this.allowSelectableAll) {
return () => true;
} else {
return undefined;
}
}
getSelectableClass(node: any): string {
if (!this.allowSelectableAll && node.selectable === false) {
return "nonSelectable";
} else {
return "";
}
}
// using property syntax, to bind `this` correctly
loadChildNodes = (parentNode: any, done: (children: any[]) => void) => {
if (
parentNode._links &&
parentNode._links.children &&
parentNode._links.children.href
) {
this.categoryService
.getCategories(parentNode._links.children.href)
.pipe(takeUntil(this.unsubscribe))
.subscribe(
(data) => {
let parentIdentifier = parentNode.identifier;
let children = this.transformData(
data._embedded.categories,
parentIdentifier
);
done(children);
},
(err) => {
console.error(err);
done([]);
}
);
} else {
done([]);
}
};
loadChildren(link): Observable<any> {
return this.categoryService.getCategories(link);
}
onSelectionEvent(event: SelectionEvent) {
if (event.source === "initial") {
return;
}
// use first node from added selection
let category = event.added ? event.added[0] : null;
if (category) {
this.currentCategory = category;
this.currentCategory.version = this.currentVersion;
this.emitCategory.emit(category);
this.clearTextSearch.next(Date.now);
if (this.isSrollToTopNeeded) {
event.added.forEach((node) => {
node._actions = {};
node._actions["scroll-to-top"] = {
type: "event",
description: this.translateService.instant(
"infotext.scroll.to.list"
),
};
});
}
this.localstorageSelectedCategoryEntry.value = JSON.stringify(
this.treeSelector.pathForNode(category.identifier)
);
if (category._links[this.dataType] !== undefined) {
if (this.outputType === CategoryWidgetComponent.DEFAULT_OUTPUT_TYPE) {
this.outputUri.next(category._links[this.dataType].href);
} else {
var categories = event.newSelection.map((node) => node.identifier);
this.outputUri.next(categories);
}
this.profile.next("categorySearchResult");
category.version = this.currentVersion;
category.publication = this.currentPublication;
this.currentDescription = category.description;
this.emitSelection(event.newSelection);
} else if (this.emitSelectedCategory) {
this.emitSelection(event.newSelection);
} else {
this.outputUri.next(null);
}
}
// was the selection cleared?
else if (event.newSelection || event.newSelection.length == 0) {
this.localstorageSelectedCategoryEntry.clear();
this.selectedCategory.next(null);
event.oldSelection.forEach((node) => {
if (node._actions && node._actions["scroll-to-top"]) {
node._actions = {};
}
});
if (!(this.outputType === CategoryWidgetComponent.DEFAULT_OUTPUT_TYPE)) {
this.outputUri.next([]);
}
this.currentDescription = "";
this.currentCategory = null;
}
}
private emitSelection(selection: any[]) {
if (!this.configuration.configuration.multi) {
if (selection.length === 0) {
this.selectedCategory.next(null);
return;
}
if (selection.length > 2) {
console.warn(
"Worklist selector is not in multi mode but multiple selected nodes where detected"
);
}
this.selectedCategory.next(selection[0]);
return;
}
this.selectedCategory.next(selection);
}
@WidgetConfigure()
protected configureWidget(
configuration: WidgetConfig<CategoryConfiguration>
) {
this.includeChildrenValue = getOrDefault(
this.configuration.configuration["includeChildrenValue"],
false
);
this.categoryExpand = getOrDefault(
this.configuration.configuration["categoryExpand"],
false
);
this.selectFirstNode = getOrDefault(
this.configuration.configuration["selectFirstNode"],
false
);
this.infotext = this.configuration.configuration["infoText"];
this.wikiLink = this.configuration.configuration["wikiLink"];
this.title = this.configuration.configuration["title"];
if (
this.configuration["_links"] &&
this.configuration["_links"]["categories"]
) {
this.rootCategoryLink =
this.configuration["_links"]["categories"]["href"];
}
let tree =
this.configuration.configuration.tree ||
({} as CategoryTreeConfiguration);
this.treeHeight = getOrDefault(tree.height, "550px");
this.treeFilterable = getOrDefault(tree.filterable, false);
this.dataType = this.configuration.configuration["dataType"];
this.localstorageSelectedCategoryEntry =
this.localStorageService.getLocalStorageEntry(
this.configuration.configuration["localstorage-category"],
Scope.GLOBAL,
DeletionMode.LOGIN
);
this.localstorageLastRootUrlEntry =
this.localStorageService.getLocalStorageEntry(
this.configuration.configuration["localstorage-category"] + "-root",
Scope.GLOBAL,
DeletionMode.LOGIN
);
this.localstorageIncludeChildrenEntry =
this.localStorageService.getLocalStorageEntry(
this.configuration.configuration["localstorage-include-children"],
Scope.GLOBAL,
DeletionMode.LOGIN
);
this.outputType = this.configuration.configuration["outputType"]
? this.configuration.configuration["outputType"]
: CategoryWidgetComponent.DEFAULT_OUTPUT_TYPE;
this.inline =
this.configuration.configuration["inline"] !== undefined
? this.configuration.configuration["inline"]
: false;
this.displayIncludeChildrenToggle =
this.configuration.configuration["display-include-children-toggle"] !==
undefined
? this.configuration.configuration["display-include-children-toggle"]
: false;
this.displayScrollToTopIcon =
this.configuration.configuration["display-scroll-to-top"] !== undefined
? this.configuration.configuration["display-scroll-to-top"]
: false;
this.localStorageVisibiltyEntry =
this.localStorageService.getLocalStorageEntry(
configuration.configuration["localstorage-content-visible"],
Scope.GLOBAL,
DeletionMode.LOGIN
);
this.showAutoSelectRootNodeButton =
this.configuration.configuration.showAutoSelectRootNodeButton !==
undefined
? this.configuration.configuration.showAutoSelectRootNodeButton
: false;
this.showAutoSelectRootNodeButtonLabel =
this.configuration.configuration.showAutoSelectRootNodeButtonLabel !==
undefined
? this.configuration.configuration.showAutoSelectRootNodeButtonLabel
: "";
this.emitSelectedCategory =
this.configuration.configuration.emitSelectedCategory !== undefined
? this.configuration.configuration.emitSelectedCategory
: false;
this.allowDrag = getOrDefault(
this.configuration.configuration.allowDrag,
false
);
this.dynamicHeight = getOrDefault(
this.configuration.configuration.dynamicHeight,
false
);
this.dynamicTreeHeightInDialog = getOrDefault(
this.configuration.configuration.dynamicTreeHeightInDialog,
false
);
this.dynamicHeightAdditionalHeight = getOrDefault(
this.configuration.configuration.dynamicHeightAdditionalHeight,
null
);
this.allowSelectableAll = getOrDefault(
this.configuration.configuration.allowSelectableAll,
false
);
if (this.localStorageVisibiltyEntry.exists()) {
this.contentVisible = this.localStorageVisibiltyEntry.value === "true";
}
if (this.displayScrollToTopIcon) {
this._scrollService
.getScrollToTopNeeded()
.pipe(debounceTime(100), distinctUntilChanged())
.subscribe((data) => {
this.isSrollToTopNeeded = data;
});
}
this.treeInitialized
.pipe(
flatMap(() => this.categoryChannel),
takeUntil(this.unsubscribe)
)
.subscribe((category) => {
if (!this.treeSelector) {
console.error(
"Received category before tree is loaded, can't select the node"
);
return;
}
this.treeSelector.expand(category);
this.treeSelector.selection([category[category.length - 1]], true);
});
this.treeInitialized
.pipe(
flatMap(() => this.searchCategoryChannel),
takeUntil(this.unsubscribe)
)
.subscribe((paths) => {
if (paths.length === 0) {
this.searchingNodes = null;
} else {
this.searchingNodes = paths.map((path) => path[path.length - 1]);
}
paths.forEach((path) => {
this.treeSelector.expand(path);
});
});
this.multi
.pipe(takeUntil(this.unsubscribe))
.subscribe((multi) => (this.configuration.configuration.multi = multi));
this.searchUriInput
.pipe(takeUntil(this.unsubscribe))
.subscribe((uri) => (this._searchUri = uri));
this.rootUriInput.pipe(takeUntil(this.unsubscribe)).subscribe((uri) => {
this.rootCategoryLink = uri;
if (this.rootCategoryLink) {
this.doReload(false);
} else {
this.rootCategoryLink = "";
this.localstorageSelectedCategoryEntry.clear();
this.localstorageIncludeChildrenEntry.clear();
this.selectedCategory.next(null);
this.categories = [];
if (this.treeSelector) {
this.treeSelector.collapseAll();
this.treeSelector.clearSelection();
}
}
});
this.disableNodesActionChannel
.pipe(takeUntil(this.unsubscribe))
.subscribe((disableNodesAction) => {
if (!disableNodesAction || !this.categories || !this.treeSelector) {
return;
}
const disabled = disableNodesAction.disabled;
const actionName = disableNodesAction.actionName;
const nodeIdentifier = disableNodesAction.nodeIdentifier;
if (nodeIdentifier) {
const category = this.treeSelector.nodeForId(nodeIdentifier);
if (category) {
this.disableNodeAction(category, actionName, disabled);
}
} else {
this.treeSelector.records().forEach((node) => {
this.disableNodeAction(node.data, actionName, disabled);
if (node.children) {
this.disableNodeChildrenAction(
node.children,
actionName,
disabled
);
}
});
}
});
this.reloadChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe), skip(1))
.subscribe((reload) => {
this.doReload(reload === true);
});
this.clearChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((reset) => {
this.localstorageSelectedCategoryEntry.clear();
this.localstorageIncludeChildrenEntry.clear();
this.selectedCategory.next(null);
this.doReload(true);
});
this.disableAutoSelectRootNodeButtonChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe(
(disabled) => (this.autoSelectRootNodeButtonDisabled = disabled)
);
this.appdataStore
.getAppdata()
.pipe(
takeUntil(this.unsubscribe),
mergeMap((data) => {
this.appdata = data;
if (this.rootCategoryLink) {
return this.categoryService.getRootCategories(
this.rootCategoryLink
);
}
return NEVER;
}),
mergeMap((data) => this.forceLoad(data))
)
.subscribe(
(data) => {
this.categories = data.categories;
this.currentVersion = data.root["version"];
this.currentPublication = data.root["identifier"];
},
(err) => console.error(err)
);
}
private disableNodeChildrenAction(
children: any[],
actionName: string,
disabled: boolean
) {
if (children && children.length > 0) {
children.forEach((child) => {
this.disableNodeAction(child.data, actionName, disabled);
this.disableNodeChildrenAction(child.children, actionName, disabled);
});
}
}
private disableNodeAction(
category: any,
actionName: string,
disabled: boolean
) {
const nodeActions = category._actions;
if (nodeActions) {
const action = nodeActions[actionName];
action.disabled = disabled;
}
}
private forceLoad(
data: CategoryResource,
path: string[] = []
): Observable<{ root: CategoryResource; categories: any[] }> {
const localStorageIncludeChildren =
this.localstorageIncludeChildrenEntry.value;
if (localStorageIncludeChildren) {
this.includeChildrenValue = localStorageIncludeChildren === "true";
}
this.includeChildren.next(this.includeChildrenValue);
const categories = this.transformData(data._embedded.categories);
if (path == null || path.length == 0) {
const localStorageValue = this.localstorageSelectedCategoryEntry.value;
if (localStorageValue) {
path = JSON.parse(localStorageValue);
}
}
//If the path is > 1 we need to load the children
if (path.length >= 1) {
return this.forceLoadChildren(categories, path).pipe(
scan((l, r) => [...l, ...r]),
last(),
map((nodes) => ({ root: data, categories: [...categories, ...nodes] }))
);
}
return observableOf({ root: data, categories });
}
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(
map((data) =>
this.transformData(data._embedded.categories, category.identifier)
)
);
} else {
return this.loadChildren(category._links.children.href).pipe(
concatMap((data) => {
let children = this.transformData(
data._embedded.categories,
category.identifier
);
return from([of(children), this.forceLoadChildren(children, path)]);
}),
concatAll()
);
}
} else {
return observableOf([]);
}
}
private doReload(withReset) {
this.currentLevel = 0;
this.categoryService
.getRootCategories(this.rootCategoryLink)
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
this.categoriesLoaded.next(true);
const categories = this.transformData(data._embedded.categories);
// Emitting last selected category because of changing ui locale.
if (this.emitSelectedCategory && this.currentCategory) {
if (this.currentCategory.version === this.currentVersion) {
this.emitSelection([this.currentCategory]);
} else {
this.currentCategory = null;
}
}
if (withReset) {
this.treeSelector.collapseAll();
this.treeSelector.clearSelection();
this.categories = categories;
return;
}
if (this.localstorageLastRootUrlEntry) {
if (
this.rootCategoryLink !== this.localstorageLastRootUrlEntry.value
) {
this.localstorageLastRootUrlEntry.value = this.rootCategoryLink;
// force loading of the first root
// since, igx-tree-grid can't expand rows & load the data on demand via API
let path =
this.categoryExpand && categories.length > 0
? [categories[0].identifier]
: [];
this.forceLoad(data, path).subscribe((result) => {
this.categories = result.categories;
});
return;
}
this.forceLoad(data).subscribe((result) => {
this.categories = result.categories;
});
}
});
}
transformData(data: any[], parentIdentifier: any = null): any[] {
for (var item of data) {
item.label = item.description;
item.data = item.identifier;
item["parent-node"] = parentIdentifier;
item.name = item.description;
item.expandedIcon = "fa-folder-open";
item.collapsedIcon = "fa-folder";
item.allowDrag = false;
if (item._links && 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;
}
}
return data;
}
toogleContentVisibility() {
this.contentVisible = !this.contentVisible;
this.localStorageVisibiltyEntry.value = this.contentVisible.toString();
}
includeChildrenChange(event: MatSlideToggleChange) {
this.localstorageIncludeChildrenEntry.value = event.checked + "";
this.includeChildren.next(event.checked);
}
public getNodeClass(node) {
if (!this.searchingNodes) {
return null;
}
if (this.searchingNodes.indexOf(node) === -1) {
return "no-search-hit";
}
return "search-hit";
}
public autoSelectRootNode() {
if (!this.treeSelector) {
return;
}
let rootNodes = this.treeSelector.rootNodes();
if (rootNodes.length > 0) {
this.treeSelector.clearSelection();
this.treeSelector.selection([rootNodes[0].identifier]);
this.treeSelector.expand([rootNodes[0].identifier]);
this.autoSelectRootNodeButtonClicked.next(true);
}
}
public onDrag(event: OnDragEvent) {
let node = event.dragged;
let parentIdentifier = node["parent-node"];
if (!parentIdentifier) {
event.cancel = true;
}
let parent = this.treeSelector.nodeForId(parentIdentifier);
if (parent == null || parent.virtual) {
event.cancel = true;
}
}
public onDropped(event: OnDroppedEvent) {
this.categoryMoved.next(event);
}
}
<nm-widgetframe
*ngIf="!inline"
[header]="configuration.configuration['header']"
[width]="configuration.configuration['width']"
[infoTitle]="title"
[infoText]="infotext"
[infoWidth]="'500px'"
[infoPlacement]="'bottom'"
[withBorder]="configuration.configuration['withBorder']"
[wikiLink]="wikiLink"
widgetId="{{ _id }}"
>
<div slot="title" class="nm-widgetframe__title">
{{ configuration.configuration["title"] | translate }}
</div>
<div slot="content" class="nm-widgetframe__content">
<span *ngIf="displayIncludeChildrenToggle">
<mat-slide-toggle
(change)="includeChildrenChange($event)"
style="padding: 15px 5px 30px 0px"
[checked]="includeChildrenValue"
></mat-slide-toggle>
{{ "category.include.children" | translate }}</span
>
<ng-container *ngTemplateOutlet="content"></ng-container>
</div>
</nm-widgetframe>
<div *ngIf="inline">
<ng-template #popTemplateSelectedCategory>
<div class="nm-attribute-list-tooltip">
{{ this.currentDescription }}
</div>
</ng-template>
<div slot="title" class="nm-widgetframe__title">
{{ configuration.configuration["title"] | translate }}
<mat-icon
[hidden]="!(this.currentDescription.length > 0)"
class="nm-widgetframe__title-icon nm-widgetframe__title-toggle"
[popover]="popTemplateSelectedCategory"
popoverTitle="{{ 'active.category.filter' | translate }}"
placement="bottom"
container="body"
triggers="mouseenter:mouseleave"
>filter_list</mat-icon
>
</div>
<div
slot="content"
class="nm-widgetframe__content"
[class.hidden]="!contentVisible"
>
<div
*ngIf="displayIncludeChildrenToggle"
style="margin-top: 20px; margin-bottom: 5px"
>
<mat-checkbox
color="primary"
[(ngModel)]="includeChildrenValue"
(change)="includeChildrenChange($event)"
></mat-checkbox>
{{ "category.include.children" | translate }}
</div>
<!-- content -->
<ng-container *ngTemplateOutlet="content"></ng-container>
<!-- button -->
<div
class="nm-category__buttonWrapper"
*ngIf="showAutoSelectRootNodeButton"
>
<button
class="--fullWidth"
mat-button
type="button"
(click)="autoSelectRootNode()"
[disabled]="autoSelectRootNodeButtonDisabled"
>
{{ showAutoSelectRootNodeButtonLabel | translate }}
</button>
</div>
</div>
</div>
<ng-template #content>
<nm-tree-selector
#treeSelector
[nodes]="categories"
[primary-key]="'identifier'"
[parent-key]="'parent-node'"
[has-children-key]="'hasChildren'"
[load-children]="loadChildNodes"
[load-children-on-filter]="loadChildrenOnFilter"
[field]="'name'"
[multi-selection]="configuration.configuration.multi"
[height]="treeHeight"
[filterable]="treeFilterable"
[expansion-depth]="categoryExpand ? 2 : 0"
[drag-and-drop]="allowDrag"
[is-selection-allowed]="isSelectionAllowed()"
(onSelectionEvent)="onSelectionEvent($event)"
[isOverlayOpened]="false"
[dynamicHeight]="dynamicHeight"
[dynamicTreeHeightInDialog]="dynamicTreeHeightInDialog"
[dynamicHeightAdditionalHeight]="dynamicHeightAdditionalHeight"
>
<nm-tree-node
*nmTreeNodeDef="let node; let api = api"
class="tree-node {{ getNodeClass(node.identifier) }}"
(onDrag)="onDrag($event)"
(onDropped)="onDropped($event)"
>
<nm-ellipsis
class="tree-node-description"
[content]="node.name"
[class]="getSelectableClass(node)"
>
</nm-ellipsis>
<nm-tree-node-actions *ngIf="node._actions" [data]="node">
<nm-action-icon
*ngFor="let act of node._actions | iterable"
nm-tree-node-icon-action
[ngClass]="act.key"
[action]="act.value"
[name]="act.key"
[disabled]="act.value.disabled"
>
</nm-action-icon>
</nm-tree-node-actions>
</nm-tree-node>
</nm-tree-selector>
</ng-template>