import { App } from '../../gogocarto' import { Category } from '../../classes/classes' declare let $: any export enum CategoryOptionTreeNodeType { Option, Category, } export enum NodeState { Checked, PartiallyChecked, Disabled, } /** * Class representating a Node in the Directory Menu Tree * * A CategoryOptionTreeNode can be a Category or an Option */ export class CategoryOptionTreeNode { id: any customId: any name: string nameShort: string displayInMenu: boolean displayInInfoBar: boolean enableDescription: boolean descriptionLabel: string showExpanded: boolean unexpandable: boolean children: CategoryOptionTreeNode[] = [] state: NodeState = NodeState.Checked isPristine: boolean = false isHidden: boolean = false // we can load the map hiding some categories with ?fullTaxonomy=0 ownerId: number = null // main option Id, or "all" mainOwnerId: any = null isMainOption = false constructor( private TYPE: CategoryOptionTreeNodeType, private DOM_ID: string, private DOM_CHECKBOX_ID: string, private DOM_CHILDREN_CLASS: string, ) {} getDom() { return $(this.DOM_ID + this.id) } getDomCheckbox() { return $(this.DOM_CHECKBOX_ID + this.id) } getDomChildren() { return this.getDom().next(this.DOM_CHILDREN_CLASS) } getOwner(): CategoryOptionTreeNode { if (this.isOption()) return App.taxonomyModule.getCategoryById(this.ownerId) else return App.taxonomyModule.getOptionById(this.ownerId) } getMainOwner() { return ['RootFakeOption', 'all'].indexOf(this.mainOwnerId) > -1 ? this : App.taxonomyModule.getOptionById(this.mainOwnerId) } getMainCategoryOwner(): CategoryOptionTreeNode { return this.getMainOwner().getOwner() } protected disabledChildren(): CategoryOptionTreeNode[] { return this.children.filter((child) => child.isDisabled) } protected nonDisabledChildren(): CategoryOptionTreeNode[] { return this.children.filter((child) => child.state != NodeState.Disabled) } protected activeChildren(): CategoryOptionTreeNode[] { return this.children.filter((child) => child.isActive) } protected checkedChildren(): CategoryOptionTreeNode[] { return this.children.filter((child) => child.isChecked) } isOption() { return this.TYPE == CategoryOptionTreeNodeType.Option } setState(state: NodeState) { if (this.isHidden && state != NodeState.Disabled) return this.state = state const displayAsChecked = this.isOption() ? this.isChecked && !this.isPristine : this.isChecked this.getDom().toggleClass('checked', displayAsChecked) this.getDomCheckbox().prop('checked', displayAsChecked) this.getDom().toggleClass('disabled', this.isDisabled) } get isChecked(): boolean { return this.state == NodeState.Checked } get isDisabled(): boolean { return this.state == NodeState.Disabled } get isActive(): boolean { return ( this.isChecked || (!this.isDisabled && this.isMainOption && App.config.menu.showOnePanePerMainOption) || (!this.isDisabled && this.children.length == 0) || (!this.isDisabled && this.children.length > 0 && this.children.every((child) => child.isActive)) ) } toggle(value: boolean = null, humanAction = true): void { const wasInPristine = this.isPristine this.isPristine = false let check = !this.isChecked if (value !== null) { check = value } // console.log("toggle " + this.name, check) const humanClickedOnPristineOption = this.isOption() && wasInPristine && humanAction if (humanClickedOnPristineOption) { // console.log("click on pristine", this.recursivelyGetPristine(this).map(o => o.name)) this.recursivelyGetPristine(this).forEach((node) => { node.toggle(false, false) }) this.recursivelySetPristine() // Force this option to be checked cause recursivelySetPristine affect also current option this.isPristine = false this.setState(NodeState.Checked) check = true } else { this.setState(check ? NodeState.Checked : NodeState.Disabled) } // Toggle children if (humanAction && !this.isOption() && check) { this.recursivelySetPristine() } else if (!humanClickedOnPristineOption && !(this.isMainOption && App.config.menu.showOnePanePerMainOption)) { for (const child of this.children) { child.toggle(check, false) } } if (humanAction) { if (this.getOwner()) { this.getOwner().updateState() } // delay the update so it's not freezing the UI setTimeout(() => { App.elementsModule.updateElementsToDisplay(true, true) App.historyModule.updateCurrState() }, 200) } } hide() { // console.log("hiding " + this.name); this.getDom().hide() this.isHidden = true if (this.isMainOption) { $('#main-option-gogo-icon-' + this.id).hide() } } recursivelySetPristine() { this.isPristine = true this.setState(NodeState.Checked) for (const child of this.children) { child.recursivelySetPristine() } } updateState(propage = true, humanAction = true) { if ( !['RootFakeCategory', 'RootFakeOption'].includes(this.name) && this.children.length > 0 && this.children.every((child) => child.isHidden === true) ) { // console.log("hide cause all children hidden", this.name) this.hide() } // console.log(`update state ${this.name}, propage = ${propage}, humanAction = ${humanAction}`) if (this.isOption()) { if (this.children.length > 0 /*&& !(this.isMainOption && App.config.menu.showOnePanePerMainOption)*/) { if (this.children.every((child) => child.isChecked)) { this.setState(NodeState.Checked) } else if (this.children.every((child) => child.isDisabled)) { this.setState(NodeState.Disabled) } else { this.setState(NodeState.PartiallyChecked) } } else { // Init first state so DOM is updated this.setState(this.isChecked ? NodeState.Checked : NodeState.Disabled) } if (this.getOwner() && (this.getOwner()).isRootCategory && App.nonDefaultPane) { propage = false } } else { if ((this).isRootCategory && App.nonDefaultPane) { propage = false } if (this.children.every((child) => child.isChecked || child.isHidden)) { // If all options are checked, we restore the pristine state this.recursivelySetPristine() } else if (this.nonDisabledChildren().length == 0) { // If all options are disabled, we restore the pristine state // humanAction : when loading filters from URL we don't want to restore all category with only disbaled options humanAction ? this.recursivelySetPristine() : this.setState(NodeState.Disabled) } else { this.setState(NodeState.PartiallyChecked) } } if (this.getOwner() && propage) this.getOwner().updateState(propage, humanAction) } recursivelyUpdateStates(humanAction = true) { for (const child of this.children) { child.recursivelyUpdateStates(humanAction) } this.updateState(false, humanAction) } isExpanded(): boolean { return this.getDom().hasClass('expanded') } isUnexpandable(): boolean { return this.getDom().hasClass('unexpandable') } toggleChildrenDetail(forceExpand: null | boolean = null): void { if (this.isUnexpandable()) { return } if (false === forceExpand || (null === forceExpand && this.isExpanded())) { this.getDomChildren() .stop(true, false) .slideUp({ duration: 350, easing: 'easeOutQuart', queue: false, complete: function () { $(this).css('height', '') }, }) this.getDom().removeClass('expanded') return } this.getDomChildren() .stop(true, false) .slideDown({ duration: 350, easing: 'easeOutQuart', queue: false, complete: function () { $(this).css('height', '') }, }) this.getDom().addClass('expanded') } getSiblingsPristine(): CategoryOptionTreeNode[] { return this.getOwner().children.filter((node) => node.isPristine && node.id != this.id) } private recursivelyGetPristine(currOption: CategoryOptionTreeNode) { let resultNodes = [] resultNodes = resultNodes.concat(currOption.getSiblingsPristine()) const parentOption = currOption.getOwner().getOwner() if (this.isMainOption || (App.config.menu.showOnePanePerMainOption && parentOption && parentOption.isMainOption)) return resultNodes else if (parentOption) resultNodes = resultNodes.concat(this.recursivelyGetPristine(parentOption)) return resultNodes } }