@WidgetComponent

nm-category

File

src/app/shared/widgets/buy/category/category.component.ts

Description

Used in iPIM Buy amd iPIM Apps. Surrounded by Widgetframe or diaplayed inline.

Tree component used for category trees.

Implements

AfterViewChecked AfterViewInit

Metadata

providers CategoryService
selector nm-category
styleUrls category.component.scss
templateUrl ./category.component.html

Index

Widget inputs
Widget outputs
Properties
Methods

Constructor

constructor(categoryService: CategoryService, appdataStore: AppdataStore, _scrollService: ScrollService, localStorageService: LocalStorageService, translateService: TranslateService, currentLocaleService: CurrentLocaleService, _widgetFrameService: WidgetframeService)
Parameters :
Name Type Optional
categoryService CategoryService no
appdataStore AppdataStore no
_scrollService ScrollService no
localStorageService LocalStorageService no
translateService TranslateService no
currentLocaleService CurrentLocaleService no
_widgetFrameService WidgetframeService no

Methods

Public autoSelectRootNode
autoSelectRootNode()
Returns : void
Protected configureWidget
configureWidget(configuration: WidgetConfig)
Decorators : WidgetConfigure
Parameters :
Name Type Optional
configuration WidgetConfig<CategoryConfiguration> no
Returns : void
Private disableNodeAction
disableNodeAction(category: any, actionName: string, disabled: boolean)
Parameters :
Name Type Optional
category any no
actionName string no
disabled boolean no
Returns : void
Private disableNodeChildrenAction
disableNodeChildrenAction(children: any[], actionName: string, disabled: boolean)
Parameters :
Name Type Optional
children any[] no
actionName string no
disabled boolean no
Returns : void
Private doReload
doReload(withReset: )
Parameters :
Name Optional
withReset no
Returns : void
Private emitSelection
emitSelection(selection: any[])
Parameters :
Name Type Optional
selection any[] no
Returns : void
Private forceLoad
forceLoad(data: CategoryResource, path: string[])
Parameters :
Name Type Optional Default value
data CategoryResource no
path string[] no []
Returns : Observable<literal type>
Private forceLoadChildren
forceLoadChildren(categories: any, path: )
Parameters :
Name Type Optional
categories any no
path no
Returns : Observable<any[]>
Public getNodeClass
getNodeClass(node: )
Parameters :
Name Optional
node no
Returns : "no-search-hit" | "search-hit"
getSelectableClass
getSelectableClass(node: any)
Parameters :
Name Type Optional
node any no
Returns : string
includeChildrenChange
includeChildrenChange(event: MatSlideToggleChange)
Parameters :
Name Type Optional
event MatSlideToggleChange no
Returns : void
isSelectionAllowed
isSelectionAllowed()
Returns : boolean
loadChildren
loadChildren(link: )
Parameters :
Name Optional
link no
Returns : Observable<any>
ngAfterViewChecked
ngAfterViewChecked()
Returns : void
ngAfterViewInit
ngAfterViewInit()
Returns : void
ngOnInit
ngOnInit()
Returns : void
Public onDrag
onDrag(event: OnDragEvent)
Parameters :
Name Type Optional
event OnDragEvent no
Returns : void
Public onDropped
onDropped(event: OnDroppedEvent)
Parameters :
Name Type Optional
event OnDroppedEvent no
Returns : void
onSelectionEvent
onSelectionEvent(event: SelectionEvent)
Parameters :
Name Type Optional
event SelectionEvent no
Returns : void
toogleContentVisibility
toogleContentVisibility()
Returns : void
transformData
transformData(data: any[], parentIdentifier: any)
Parameters :
Name Type Optional Default value
data any[] no
parentIdentifier any no null
Returns : any[]

Properties

Public _id
_id: string
Type : string
Decorators : WidgetId
Private _searchUri
_searchUri: string
Type : string
Default value : null
Public allowDrag
allowDrag: boolean
Type : boolean
Default value : false
Public allowSelectableAll
allowSelectableAll: boolean
Type : boolean
Default value : false
Public appdata
appdata: Appdata
Type : Appdata
Public autoSelectRootNodeButtonClicked
autoSelectRootNodeButtonClicked:
Default value : new Subject<boolean>()
Decorators : WidgetOutput

Auto select root node button is clicked

Public autoSelectRootNodeButtonDisabled
autoSelectRootNodeButtonDisabled: boolean
Type : boolean
Default value : true
Public categories
categories: any[]
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: boolean
Type : boolean
Default value : false
Public categoryMoved
categoryMoved: Subject<any>
Type : Subject<any>
Default value : new Subject<any>()
Decorators : WidgetOutput

Emits when a node is dragged and dropped inside the tree

Private clearChannel
clearChannel: Subject<any>
Type : Subject<any>
Default value : new Subject<any>()
Decorators : WidgetInput

Clears the current selection and reloads

Private clearTextSearch
clearTextSearch: Subject<any>
Type : Subject<any>
Default value : new Subject<any>()
Decorators : WidgetOutput

Emits the current date, every time the output is updated

Public configuration
configuration: WidgetConfig<CategoryConfiguration>
Type : WidgetConfig<CategoryConfiguration>
Decorators : WidgetConfiguration
Public contentVisible
contentVisible: boolean
Type : boolean
Default value : true
Public currentCategory
currentCategory: any
Type : any
Public currentDescription
currentDescription: string
Type : string
Default value : ""
Private currentLevel
currentLevel: number
Type : number
Default value : 0
Public currentPublication
currentPublication: string
Type : string
Default value : ""
Public currentVersion
currentVersion: string
Type : string
Default value : ""
Public dataType
dataType: string
Type : string
Static DEFAULT_OUTPUT_TYPE
DEFAULT_OUTPUT_TYPE: string
Type : string
Default value : "uri"
Private disableAutoSelectRootNodeButtonChannel
disableAutoSelectRootNodeButtonChannel:
Default value : new Subject<any>()
Decorators : WidgetInput

Disable auto select root node button

Private disableNodesActionChannel
disableNodesActionChannel:
Default value : new Subject<any>()
Decorators : WidgetInput

Disabling/Enabling action for one node, or Disabling/Enabling it for all nodes.

Public displayIncludeChildrenToggle
displayIncludeChildrenToggle: boolean
Type : boolean
Default value : false
Public displayScrollToTopIcon
displayScrollToTopIcon: boolean
Type : boolean
Default value : false
Public dynamicHeight
dynamicHeight: boolean
Type : boolean
Default value : false
Public dynamicHeightAdditionalHeight
dynamicHeightAdditionalHeight: string
Type : string
Public dynamicTreeHeightInDialog
dynamicTreeHeightInDialog: boolean
Type : boolean
Default value : false
Public emitSelectedCategory
emitSelectedCategory: boolean
Type : boolean
Default value : false
Public height
height: string
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: boolean
Type : boolean
Default value : false
Public infotext
infotext: string
Type : string
Public initialExpansion
initialExpansion: boolean
Type : boolean
Default value : false
Public initLoad
initLoad: boolean
Type : boolean
Default value : false
Public inline
inline: boolean
Type : boolean
Default value : false
Public inputLink
inputLink: string
Type : string
Public isSrollToTopNeeded
isSrollToTopNeeded: boolean
Type : boolean
Default value : false
loadChildNodes
loadChildNodes:
Default value : (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([]); } }
Public loadChildrenOnFilter
loadChildrenOnFilter:
Default value : (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(); }
Private localstorageIncludeChildrenEntry
localstorageIncludeChildrenEntry: LocalStorageEntry
Type : LocalStorageEntry
Private localstorageLastRootUrlEntry
localstorageLastRootUrlEntry: LocalStorageEntry
Type : LocalStorageEntry
Private localstorageSelectedCategoryEntry
localstorageSelectedCategoryEntry: LocalStorageEntry
Type : LocalStorageEntry
Private localStorageVisibiltyEntry
localStorageVisibiltyEntry: LocalStorageEntry
Type : LocalStorageEntry
Private multi
multi:
Default value : new ReplaySubject<boolean>()
Decorators : WidgetInput
Public outputType
outputType: string
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: number
Type : number
Default value : 0
Private profile
profile: Subject<any>
Type : Subject<any>
Default value : new Subject<any>()
Decorators : WidgetOutput

Columns profile to be returned to search result list

Private programaticExpansion
programaticExpansion: boolean
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: string
Type : string
Private rootUriInput
rootUriInput:
Default value : new Subject<any>()
Decorators : WidgetInput

URI used to load root node

Private searchCategoryChannel
searchCategoryChannel:
Default value : new ReplaySubject<string[][]>(1)
Decorators : WidgetInput

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

Public searchingNodes
searchingNodes: string[]
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: boolean
Type : boolean
Default value : false
Public showAutoSelectRootNodeButton
showAutoSelectRootNodeButton: boolean
Type : boolean
Default value : false
Public showAutoSelectRootNodeButtonLabel
showAutoSelectRootNodeButtonLabel: string
Type : string
Public title
title: string
Type : string
Public treeFilterable
treeFilterable: boolean
Type : boolean
Default value : false
Public treeHeight
treeHeight: string | number
Type : string | number
Default value : "550px"
Private treeInitialized
treeInitialized:
Default value : new Subject()
treeSelector
treeSelector: TreeSelectorComponent
Type : TreeSelectorComponent
Decorators : ViewChild
Private unsubscribe
unsubscribe:
Default value : NgUnsubscribe.create()
Public wikiLink
wikiLink: string
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>
      &nbsp;{{ "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>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""