import { getFlexiblePositions } from 'ngx-tethys/core' ; import { ThyTreeNode } from 'ngx-tethys/tree' ; import { isArray , isObject , produce , warnDeprecation } from 'ngx-tethys/util' ; import { Observable , of } from 'rxjs' ; import { take } from 'rxjs/operators' ; import { CdkConnectedOverlay , CdkOverlayOrigin , ConnectionPositionPair } from '@angular/cdk/overlay' ; import { Component , ContentChild , ElementRef , forwardRef , HostBinding , HostListener , Input , NgZone , OnInit , Renderer2 , TemplateRef , ViewChild } from '@angular/core' ; import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ; import { ThyTreeSelectNode , ThyTreeSelectType } from './tree-select.class' ; type InputSize = 'xs' | 'sm' | 'md' | 'lg' | '' ; export function filterTreeData ( treeNodes , searchText , searchKey = 'name' ) { const filterNodes = ( node , result ) => { if ( node [ searchKey ] && node [ searchKey ] . indexOf ( searchText ) !== - 1 ) { result . push ( node ) ; return result ; } if ( Array . isArray ( node . children ) ) { const nodes = node . children . reduce ( ( previous , current ) => filterNodes ( current , previous ) , [ ] as ThyTreeSelectNode [ ] ) ; if ( nodes . length ) { const parentNode = { ... node , children : nodes , expand : true } ; result . push ( parentNode ) ; } } return result ; } ; const treeData = treeNodes . reduce ( ( previous , current ) => filterNodes ( current , previous ) , [ ] as ThyTreeSelectNode [ ] ) ; return treeData ; } @ Component ( { selector : 'thy-tree-select' , templateUrl : './tree-select.component.html' , providers : [ { provide : NG_VALUE_ACCESSOR , useExisting : forwardRef ( ( ) => ThyTreeSelectComponent ) , multi : true } ] } ) export class ThyTreeSelectComponent implements OnInit , ControlValueAccessor { @ HostBinding ( 'class.thy-select-custom' ) treeSelectClass = true ; @ HostBinding ( 'class.thy-select' ) isTreeSelect = true ; @ HostBinding ( 'class.menu-is-opened' ) expandTreeSelectOptions = false ; @ HostBinding ( 'class.thy-select-custom--multiple' ) isMulti = false ; public treeNodes : ThyTreeSelectNode [ ] ; public selectedValue : any ; public selectedNode : ThyTreeSelectNode ; public selectedNodes : ThyTreeSelectNode [ ] = [ ] ; public flattenTreeNodes : ThyTreeSelectNode [ ] = [ ] ; public cdkConnectOverlayWidth = 0 ; public positions : ConnectionPositionPair [ ] ; public icons : { expand : string ; collapse : string ; gap ? : number ; } = { expand : 'angle-down' , collapse : 'angle-right' , gap : 15 } ; private initialled = false ; public valueIsObject = false ; originTreeNodes : ThyTreeSelectNode [ ] ; @ ContentChild ( 'thyTreeSelectTriggerDisplay' ) thyTreeSelectTriggerDisplayRef : TemplateRef < any > ; @ ContentChild ( 'treeNodeTemplate' ) treeNodeTemplateRef : TemplateRef < any > ; @ ViewChild ( CdkOverlayOrigin , { static : true } ) cdkOverlayOrigin : CdkOverlayOrigin ; @ ViewChild ( CdkConnectedOverlay , { static : true } ) cdkConnectedOverlay : CdkConnectedOverlay ; @ ViewChild ( 'customDisplayTemplate' , { static : true } ) customDisplayTemplate : TemplateRef < any > ; @ Input ( ) set thyTreeNodes ( value ) { this . treeNodes = value ; this . originTreeNodes = value ; if ( this . initialled ) { this . flattenTreeNodes = this . flattenNodes ( this . treeNodes , this . flattenTreeNodes , [ ] ) ; this . setSelectedNodes ( ) ; } } @ Input ( ) thyPrimaryKey = '_id' ; @ Input ( ) thyShowKey = 'name' ; @ Input ( ) thyChildCountKey = 'childCount' ; @ Input ( ) thyAllowClear : boolean ; @ Input ( ) thyMultiple = false ; @ Input ( ) thyDisable = false ; @ Input ( ) thyPlaceholder = '请选择节点' ; get placeholder ( ) { return this . thyPlaceholder ; } @ Input ( ) thySize : InputSize ; @ Input ( ) thyEmptyOptionsText = '暂时没有数据可选' ; @ Input ( ) thyHiddenNodeKey = 'hidden' ; @ Input ( ) thyDisableNodeKey = 'disabled' ; @ Input ( ) thyAsyncNode = false ; @ Input ( ) thyShowWholeName = false ; @ Input ( ) thyShowSearch = false ; @ Input ( ) set thyIconType ( type ) { warnDeprecation ( 'This parameter has been deprecation' ) ; } @ Input ( ) thyHiddenNodeFn : ( node : ThyTreeSelectNode ) => boolean = ( node ) => node . hidden ; @ Input ( ) thyDisableNodeFn : ( node : ThyTreeSelectNode ) => boolean = ( node ) => node . disabled ; @ Input ( ) thyGetNodeChildren : ( node : ThyTreeSelectNode ) => Observable < ThyTreeSelectNode > = ( node ) => of ( [ ] ) ; public onModelChange : Function = ( ) => { } ; public onModelTouch : Function = ( ) => { } ; private _getNgModelType ( ) { if ( this . thyMultiple ) { this . valueIsObject = ! this . selectedValue [ 0 ] || isObject ( this . selectedValue [ 0 ] ) ; } else { this . valueIsObject = isObject ( this . selectedValue ) ; } } writeValue ( value ) { this . selectedValue = value ; if ( value ) { this . _getNgModelType ( ) ; } this . setSelectedNodes ( ) ; } registerOnChange ( fn ) { this . onModelChange = fn ; } registerOnTouched ( fn ) { this . onModelTouch = fn ; } constructor ( public elementRef , public renderer , private ngZone ) { } @ HostListener ( 'document:click' , [ '$event' ] ) onDocumentClick ( event ) { event . stopPropagation ( ) ; if ( ! this . elementRef . nativeElement . contains ( event . target ) && this . expandTreeSelectOptions ) { this . expandTreeSelectOptions = false ; } } ngOnInit ( ) { this . positions = getFlexiblePositions ( 'bottom' , 4 ) ; this . isMulti = this . thyMultiple ; this . flattenTreeNodes = this . flattenNodes ( this . treeNodes , this . flattenTreeNodes , [ ] ) ; this . setSelectedNodes ( ) ; this . initialled = true ; this . init ( ) ; } get selectedValueObject ( ) { return this . thyMultiple ? this . selectedNodes : this . selectedNode ; } searchValue ( searchText ) { this . treeNodes = filterTreeData ( this . originTreeNodes , searchText . trim ( ) , this . thyShowKey ) ; } public setPosition ( ) { this . ngZone . onStable . asObservable ( ) . pipe ( take ( 1 ) ) . subscribe ( ( ) => { this . cdkConnectedOverlay . overlayRef . updatePosition ( ) ; } ) ; } private init ( ) { this . cdkConnectOverlayWidth = this . cdkOverlayOrigin . elementRef . nativeElement . getBoundingClientRect ( ) . width ; } private flattenNodes ( nodes = [ ] , resultNodes = [ ] , parentPrimaryValue = [ ] ) { resultNodes = resultNodes . concat ( nodes ) ; let nodesLeafs = [ ] ; ( nodes || [ ] ) . forEach ( item => { item . parentValues = parentPrimaryValue ; item . level = item . parentValues . length ; if ( item . children && isArray ( item . children ) ) { const nodeLeafs = this . flattenNodes ( item . children , resultNodes , [ ... parentPrimaryValue , item [ this . thyPrimaryKey ] ] ) ; nodesLeafs = [ ... nodesLeafs , ... nodeLeafs ] ; } } ) ; return [ ... nodes , ... nodesLeafs ] ; } private _findTreeNode ( value ) { return ( this . flattenTreeNodes || [ ] ) . find ( item => item [ this . thyPrimaryKey ] === value ) ; } getShowNodeName ( ) { if ( this . thyShowWholeName ) { let wholeName = '' ; ( this . selectedNode . parentValues || [ ] ) . forEach ( ( item , index ) => { const node = this . _findTreeNode ( item ) ; wholeName = `${ wholeName }${ node [ this . thyShowKey ] } > ` ; } ) ; return `${ wholeName }${ this . selectedNode [ this . thyShowKey ] }` ; } else { return this . selectedNode [ this . thyShowKey ] ; } } private setSelectedNodes ( ) { if ( this . selectedValue ) { if ( this . thyMultiple ) { if ( this . selectedValue . length > 0 ) { if ( this . valueIsObject && Object . keys ( this . selectedValue [ 0 ] ) . indexOf ( this . thyPrimaryKey ) >= 0 ) { this . selectedNodes = this . selectedValue . map ( ( item ) => { return this . _findTreeNode ( item [ this . thyPrimaryKey ] ) ; } ) ; } else { this . selectedNodes = this . selectedValue . map ( ( item ) => { return this . _findTreeNode ( item ) ; } ) ; } } } else { if ( this . valueIsObject ) { if ( Object . keys ( this . selectedValue ) . indexOf ( this . thyPrimaryKey ) >= 0 ) { this . selectedNode = this . _findTreeNode ( this . selectedValue [ this . thyPrimaryKey ] ) ; } } else { this . selectedNode = this . _findTreeNode ( this . selectedValue ) ; } } } else { this . selectedNodes = [ ] ; this . selectedNode = null ; } } openSelectPop ( ) { if ( this . thyDisable ) { return ; } this . cdkConnectOverlayWidth = this . cdkOverlayOrigin . elementRef . nativeElement . getBoundingClientRect ( ) . width ; this . expandTreeSelectOptions = ! this . expandTreeSelectOptions ; } close ( ) { this . expandTreeSelectOptions = false ; } clearSelectedValue ( event ) { event . stopPropagation ( ) ; this . selectedValue = null ; this . selectedNode = null ; this . selectedNodes = [ ] ; this . onModelChange ( this . selectedValue ) ; } private _changeSelectValue ( ) { if ( this . valueIsObject ) { this . selectedValue = this . thyMultiple ? this . selectedNodes : this . selectedNode ; } else { this . selectedValue = this . thyMultiple ? this . selectedNodes . map ( item => item [ this . thyPrimaryKey ] ) : this . selectedNode [ this . thyPrimaryKey ] ; } this . onModelChange ( this . selectedValue ) ; } removeMultipleSelectedNode ( event ) { this . removeSelectedNode ( event . item , event . $event ) ; } removeSelectedNode ( node , event ? ) { if ( event ) { event . stopPropagation ( ) ; } if ( this . thyDisable ) { return ; } if ( this . thyMultiple ) { this . selectedNodes = produce ( this . selectedNodes ) . remove ( ( item ) => { return item [ this . thyPrimaryKey ] === node [ this . thyPrimaryKey ] ; } ) ; this . _changeSelectValue ( ) ; } } selectNode ( node ) { if ( ! this . thyMultiple ) { this . selectedNode = node ; this . expandTreeSelectOptions = false ; } else { if ( this . selectedNodes . find ( item => { return item [ this . thyPrimaryKey ] === node [ this . thyPrimaryKey ] ; } ) ) { this . removeSelectedNode ( node ) ; } else { this . selectedNodes = produce ( this . selectedNodes ) . add ( node ) ; } } this . _changeSelectValue ( ) ; } getNodeChildren ( node ) { const result = this . thyGetNodeChildren ( node ) ; if ( result && result . subscribe ) { result . pipe ( ) . subscribe ( ( data ) => { const nodes = this . flattenNodes ( data , this . flattenTreeNodes , [ ... node . parentValues , node [ this . thyPrimaryKey ] ] ) ; const otherNodes = nodes . filter ( ( item ) => { return ! this . flattenTreeNodes . find ( hasItem => { return hasItem [ this . thyPrimaryKey ] === item [ this . thyPrimaryKey ] ; } ) ; } ) ; this . flattenTreeNodes = [ ... this . flattenTreeNodes , ... otherNodes ] ; node . children = data ; } ) ; return result ; } } } @ Component ( { selector : 'thy-tree-select-nodes' , templateUrl : './tree-select-nodes.component.html' } ) export class ThyTreeSelectNodesComponent implements OnInit { @ HostBinding ( 'class' ) class : string ; @ Input ( ) treeNodes : ThyTreeSelectNode [ ] ; public primaryKey = this . parent . thyPrimaryKey ; public showKey = this . parent . thyShowKey ; public isMultiple = this . parent . thyMultiple ; public valueIsObject = this . parent . valueIsObject ; public selectedValue = this . parent . selectedValue ; public childCountKey = this . parent . thyChildCountKey ; public treeNodeTemplateRef = this . parent . treeNodeTemplateRef ; constructor ( public parent ) { } ngOnInit ( ) { this . class = this . isMultiple ? 'thy-tree-select-dropdown thy-tree-select-dropdown-multiple' : 'thy-tree-select-dropdown' ; } treeNodeIsSelected ( node ) { if ( this . parent . thyMultiple ) { return ( this . parent . selectedNodes || [ ] ) . find ( item => { return item [ this . primaryKey ] === node [ this . primaryKey ] ; } ) ; } else { return this . parent . selectedNode && this . parent . selectedNode [ this . primaryKey ] === node [ this . primaryKey ] ; } } treeNodeIsHidden ( node ) { if ( this . parent . thyHiddenNodeKey ) { return node [ this . parent . thyHiddenNodeKey ] ; } if ( this . parent . thyHiddenNodeFn ) { return this . parent . thyHiddenNodeFn ( node ) ; } return false ; } treeNodeIsDisable ( node ) { if ( this . parent . thyDisableNodeKey ) { return node [ this . parent . thyDisableNodeKey ] ; } if ( this . parent . thyDisableNodeFn ) { return this . parent . thyDisableNodeFn ( node ) ; } return false ; } treeNodeIsExpand ( node ) { let isSelectedNodeParent = false ; if ( this . parent . thyMultiple ) { isSelectedNodeParent = ! ! ( this . parent . selectedNodes || [ ] ) . find ( item => { return item . parentValues . indexOf ( node [ this . primaryKey ] ) > - 1 ; } ) ; } else { isSelectedNodeParent = this . parent . selectedNode ? this . parent . selectedNode . parentValues . indexOf ( node [ this . primaryKey ] ) > - 1 : false ; } const isExpand = node . expand || ( Object . keys ( node ) . indexOf ( 'expand' ) < 0 && isSelectedNodeParent ) ; node . expand = isExpand ; return isExpand ; } getNodeChildren ( node ) { return this . parent . getNodeChildren ( node ) ; } selectTreeNode ( event , node ) { event . stopPropagation ( ) ; if ( this . treeNodeIsDisable ( node ) ) { return ; } this . parent . selectNode ( node ) ; } nodeExpandToggle ( event , node ) { event . stopPropagation ( ) ; if ( Object . keys ( node ) . indexOf ( 'expand' ) > - 1 ) { node . expand = ! node . expand ; } else { if ( this . treeNodeIsExpand ( node ) ) { node . expand = false ; } else { node . expand = true ; } } if ( node . expand && this . parent . thyAsyncNode ) { this . getNodeChildren ( node ) . subscribe ( ( ) => { this . parent . setPosition ( ) ; } ) ; } this . parent . setPosition ( ) ; } }