import { Directive , ElementRef , Inject , Input , OnDestroy , OnInit , Renderer2 } from '@angular/core' ; import { NodeDraggableService } from './node-draggable.service' ; import { CapturedNode } from './captured-node' ; import { Tree } from '../tree' ; @ Directive ( { selector : '[nodeDraggable]' } ) export class NodeDraggableDirective implements OnDestroy , OnInit { public static DATA_TRANSFER_STUB_DATA = 'some browsers enable drag-n-drop only when dataTransfer has data' ; @ Input ( ) public nodeDraggable : ElementRef ; @ Input ( ) public tree : Tree ; private nodeNativeElement : HTMLElement ; private disposersForDragListeners : Function [ ] = [ ] ; public constructor ( @ Inject ( ElementRef ) public element , @ Inject ( NodeDraggableService ) private nodeDraggableService , @ Inject ( Renderer2 ) private renderer ) { this . nodeNativeElement = element . nativeElement ; } public ngOnInit ( ) { if ( ! this . tree . isStatic ( ) ) { this . renderer . setAttribute ( this . nodeNativeElement , 'draggable' , 'true' ) ; this . disposersForDragListeners . push ( this . renderer . listen ( this . nodeNativeElement , 'dragenter' , this . handleDragEnter . bind ( this ) ) ) ; this . disposersForDragListeners . push ( this . renderer . listen ( this . nodeNativeElement , 'dragover' , this . handleDragOver . bind ( this ) ) ) ; this . disposersForDragListeners . push ( this . renderer . listen ( this . nodeNativeElement , 'dragstart' , this . handleDragStart . bind ( this ) ) ) ; this . disposersForDragListeners . push ( this . renderer . listen ( this . nodeNativeElement , 'dragleave' , this . handleDragLeave . bind ( this ) ) ) ; this . disposersForDragListeners . push ( this . renderer . listen ( this . nodeNativeElement , 'drop' , this . handleDrop . bind ( this ) ) ) ; this . disposersForDragListeners . push ( this . renderer . listen ( this . nodeNativeElement , 'dragend' , this . handleDragEnd . bind ( this ) ) ) ; } } public ngOnDestroy ( ) { this . disposersForDragListeners . forEach ( dispose => dispose ( ) ) ; } private handleDragStart ( e ) { if ( e . stopPropagation ) { e . stopPropagation ( ) ; } this . nodeDraggableService . captureNode ( new CapturedNode ( this . nodeDraggable , this . tree ) ) ; e . dataTransfer . setData ( 'text' , NodeDraggableDirective . DATA_TRANSFER_STUB_DATA ) ; e . dataTransfer . effectAllowed = 'move' ; } private handleDragOver ( e ) { e . preventDefault ( ) ; e . dataTransfer . dropEffect = 'move' ; } private handleDragEnter ( e ) { e . preventDefault ( ) ; if ( this . containsElementAt ( e ) ) { this . addClass ( 'over-drop-target' ) ; } } private handleDragLeave ( e ) { if ( ! this . containsElementAt ( e ) ) { this . removeClass ( 'over-drop-target' ) ; } } private handleDrop ( e ) { e . preventDefault ( ) ; if ( e . stopPropagation ) { e . stopPropagation ( ) ; } this . removeClass ( 'over-drop-target' ) ; if ( ! this . isDropPossible ( e ) ) { return false ; } if ( this . nodeDraggableService . getCapturedNode ( ) ) { return this . notifyThatNodeWasDropped ( ) ; } } private isDropPossible ( e ) { const capturedNode = this . nodeDraggableService . getCapturedNode ( ) ; return capturedNode && capturedNode . canBeDroppedAt ( this . nodeDraggable ) && this . containsElementAt ( e ) ; } private handleDragEnd ( e ) { this . removeClass ( 'over-drop-target' ) ; this . nodeDraggableService . releaseCapturedNode ( ) ; } private containsElementAt ( e ) { const { x = e . clientX , y = e . clientY } = e ; return this . nodeNativeElement . contains ( document . elementFromPoint ( x , y ) ) ; } private addClass ( className ) { const classList = this . nodeNativeElement . classList ; classList . add ( className ) ; } private removeClass ( className ) { const classList = this . nodeNativeElement . classList ; classList . remove ( className ) ; } private notifyThatNodeWasDropped ( ) { this . nodeDraggableService . fireNodeDragged ( this . nodeDraggableService . getCapturedNode ( ) , this . nodeDraggable ) ; } }