import { ControlValueAccessor , NG_VALUE_ACCESSOR } from "@angular/forms" ; import { Directive , ElementRef , EventEmitter , forwardRef , Input , NgZone , Output } from '@angular/core' ; import FroalaEditor from 'froala-editor' ; @ Directive ( { selector : '[froalaEditor]' , exportAs : 'froalaEditor' , providers : [ { provide : NG_VALUE_ACCESSOR , useExisting : forwardRef ( ( ) => FroalaEditorDirective ) , multi : true } ] } ) export class FroalaEditorDirective implements ControlValueAccessor { private _opts : any = { immediateAngularModelUpdate : false , angularIgnoreAttrs : null } ; private _element : any ; private SPECIAL_TAGS : string [ ] = [ 'img' , 'button' , 'input' , 'a' ] ; private INNER_HTML_ATTR : string = 'innerHTML' ; private _hasSpecialTag : boolean = false ; private _editor : any ; private _model : string ; private _editorInitialized : boolean = false ; private _oldModel : string = null ; constructor ( el , private zone ) { let element = el . nativeElement ; if ( this . SPECIAL_TAGS . indexOf ( element . tagName . toLowerCase ( ) ) != - 1 ) { this . _hasSpecialTag = true ; } this . _element = element ; this . zone = zone ; } onChange = ( _ ) => { } ; onTouched = ( ) => { } ; writeValue ( content ) { this . updateEditor ( content ) ; } registerOnChange ( fn ) { this . onChange = fn ; } registerOnTouched ( fn ) { this . onTouched = fn ; } @ Input ( ) set froalaEditor ( opts ) { this . _opts = this . clone ( opts || this . _opts ) ; this . _opts = { ... this . _opts } ; } private clone ( item ) { const me = this ; if ( ! item ) { return item ; } let types = [ Number , String , Boolean ] , result ; types . forEach ( function ( type ) { if ( item instanceof type ) { result = type ( item ) ; } } ) ; if ( typeof result == "undefined" ) { if ( Object . prototype . toString . call ( item ) === "[object Array]" ) { result = [ ] ; item . forEach ( function ( child , index , array ) { result [ index ] = me . clone ( child ) ; } ) ; } else if ( typeof item == "object" ) { if ( item . nodeType && typeof item . cloneNode == "function" ) { result = item . cloneNode ( true ) ; } else if ( ! item . prototype ) { if ( item instanceof Date ) { result = new Date ( item ) ; } else { result = { } ; for ( var i in item ) { result [ i ] = me . clone ( item [ i ] ) ; } } } else { if ( false && item . constructor ) { result = new item . constructor ( ) ; } else { result = item ; } } } else { result = item ; } } return result ; } @ Input ( ) set froalaModel ( content ) { this . updateEditor ( content ) ; } private updateEditor ( content ) { if ( JSON . stringify ( this . _oldModel ) == JSON . stringify ( content ) ) { return ; } if ( ! this . _hasSpecialTag ) { this . _oldModel = content ; } else { this . _model = content ; } if ( this . _editorInitialized ) { if ( ! this . _hasSpecialTag ) { this . _editor . html . set ( content ) ; } else { this . setContent ( ) ; } } else { if ( ! this . _hasSpecialTag ) { this . _element . innerHTML = content || '' ; } else { this . setContent ( ) ; } } } @ Output ( ) froalaModelChange : EventEmitter < any > = new EventEmitter < any > ( ) ; @ Output ( ) froalaInit : EventEmitter < Object > = new EventEmitter < Object > ( ) ; private updateModel ( ) { this . zone . run ( ( ) => { let modelContent = null ; if ( this . _hasSpecialTag ) { let attributeNodes = this . _element . attributes ; let attrs = { } ; for ( let i = 0 ; i < attributeNodes . length ; i ++ ) { let attrName = attributeNodes [ i ] . name ; if ( this . _opts . angularIgnoreAttrs && this . _opts . angularIgnoreAttrs . indexOf ( attrName ) != - 1 ) { continue ; } attrs [ attrName ] = attributeNodes [ i ] . value ; } if ( this . _element . innerHTML ) { attrs [ this . INNER_HTML_ATTR ] = this . _element . innerHTML ; } modelContent = attrs ; } else { let returnedHtml = this . _editor . html . get ( ) ; if ( typeof returnedHtml === 'string' ) { modelContent = returnedHtml ; } } if ( this . _oldModel !== modelContent ) { this . _oldModel = modelContent ; this . froalaModelChange . emit ( modelContent ) ; this . onChange ( modelContent ) ; } } ) ; } private registerEvent ( eventName , callback ) { if ( ! eventName || ! callback ) { return ; } if ( ! this . _opts . events ) { this . _opts . events = { } ; } this . _opts . events [ eventName ] = callback ; } private initListeners ( ) { let self = this ; if ( this . _editor . events ) { this . _editor . events . on ( 'contentChanged' , function ( ) { self . updateModel ( ) ; } ) ; this . _editor . events . on ( 'mousedown' , function ( ) { setTimeout ( function ( ) { self . onTouched ( ) ; } , 0 ) ; } ) ; if ( this . _opts . immediateAngularModelUpdate ) { this . _editor . events . on ( 'keyup' , function ( ) { setTimeout ( function ( ) { self . updateModel ( ) ; } , 0 ) ; } ) ; } } this . _editorInitialized = true ; } private createEditor ( ) { if ( this . _editorInitialized ) { return ; } this . setContent ( true ) ; this . zone . runOutsideAngular ( ( ) => { if ( ! this . _opts . events ) this . _opts . events = { } ; this . registerEvent ( 'initialized' , this . _opts . events && this . _opts . events . initialized ) ; const existingInitCallback = this . _opts . events . initialized ; if ( ! this . _opts . events . initialized || ! this . _opts . events . initialized . overridden ) { this . _opts . events . initialized = ( ) => { this . initListeners ( ) ; existingInitCallback && existingInitCallback . call ( this . _editor , this ) ; } ; this . _opts . events . initialized . overridden = true ; } this . _editor = new FroalaEditor ( this . _element , this . _opts ) ; } ) ; } private setHtml ( ) { this . _editor . html . set ( this . _model || "" ) ; this . _editor . undo . reset ( ) ; this . _editor . undo . saveStep ( ) ; } private setContent ( firstTime = false ) { let self = this ; if ( this . _model || this . _model == '' ) { this . _oldModel = this . _model ; if ( this . _hasSpecialTag ) { let tags = this . _model ; if ( tags ) { for ( let attr in tags ) { if ( tags . hasOwnProperty ( attr ) && attr != this . INNER_HTML_ATTR ) { this . _element . setAttribute ( attr , tags [ attr ] ) ; } } if ( tags . hasOwnProperty ( this . INNER_HTML_ATTR ) ) { this . _element . innerHTML = tags [ this . INNER_HTML_ATTR ] ; } } } else { if ( firstTime ) { this . registerEvent ( 'initialized' , function ( ) { self . setHtml ( ) ; } ) ; } else { self . setHtml ( ) ; } } } } private destroyEditor ( ) { if ( this . _editorInitialized ) { this . _editor . destroy ( ) ; this . _editorInitialized = false ; } } private getEditor ( ) { if ( this . _element ) { return this . _editor ; } return null ; } private generateManualController ( ) { let controls = { initialize : this . createEditor . bind ( this ) , destroy : this . destroyEditor . bind ( this ) , getEditor : this . getEditor . bind ( this ) , } ; this . froalaInit . emit ( controls ) ; } ngAfterViewInit ( ) { if ( ! this . froalaInit . observers . length ) { this . createEditor ( ) ; } else { this . generateManualController ( ) ; } } ngOnDestroy ( ) { this . destroyEditor ( ) ; } setDisabledState ( isDisabled ) { } }