File

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

Index

Properties

Properties

_links
_links: any[]
Type : any[]
Optional

Category embedded _links, it should contain root category link in this path _links.categories.href

allowDrag
allowDrag: boolean
Type : boolean
Optional

Allow drag and drop of categories. @default(false)

allowSelectableAll
allowSelectableAll: boolean
Type : boolean
Optional

Overrides the global statement whether a category is selectable or not. @default(false)

categoryExpand
categoryExpand: boolean
Type : boolean
Optional

Expand tree categories. @default(false)

dataType
dataType: string
Type : string
Optional

Categories list name in json returned from backend.

display-include-children-toggle
display-include-children-toggle: boolean
Type : boolean
Optional

Display include children checkbox. @default(false)

display-scroll-to-top
display-scroll-to-top: boolean
Type : boolean
Optional

Display scroll to top icon. @default(false)

dynamicHeight
dynamicHeight: boolean
Type : boolean
Optional

Enables dynamicHeight calculation

dynamicHeightAdditionalHeight
dynamicHeightAdditionalHeight: string
Type : string
Optional

Height not filled by category tree

dynamicTreeHeightInDialog
dynamicTreeHeightInDialog: boolean
Type : boolean
Optional

Dynamic height calculation container is set to

emitSelectedCategory
emitSelectedCategory: boolean
Type : boolean
Optional

Whether to emit the selected category to "selectedCategory" widget output subject. @default(false)

includeChildrenValue
includeChildrenValue: boolean
Type : boolean
Optional

Initial value for include children check box. @default(false)

infoText
infoText: string
Type : string
Optional

Information text.

inline
inline: boolean
Type : boolean
Optional

View category component inline or surrounded by widget frame. @default(false)

localstorage-category
localstorage-category: string
Type : string
Optional

Local storage key for selected category.

localstorage-content-visible
localstorage-content-visible: string
Type : string
Optional

Local storage key for visible content property.

localstorage-include-children
localstorage-include-children: string
Type : string
Optional

Local storage key for include children checkbox value.

multi
multi: boolean
Type : boolean
Optional

Allow selection of multiple categories. @default(false)

outputType
outputType: string
Type : string
Optional

Output type. @default(uri)

selectFirstNode
selectFirstNode: boolean
Type : boolean
Optional

Select first node of the category tree. @default(false)

showAutoSelectRootNodeButton
showAutoSelectRootNodeButton: boolean
Type : boolean
Optional

Show auto select root node button. @default(false)

showAutoSelectRootNodeButtonLabel
showAutoSelectRootNodeButtonLabel: string
Type : string
Optional

Auto select root node button label

title
title: string
Type : string
Optional

Category tree title.

tree
tree: CategoryTreeConfiguration
Type : CategoryTreeConfiguration
Optional

Configures the category selection tree

wikiLink
wikiLink: string
Type : string
Optional

Wiki help link.

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

results matching ""

    No results matching ""