Skip to content
Snippets Groups Projects
content-editable.directive.ts 3.44 KiB
Newer Older
import {Directive, ElementRef, forwardRef, HostBinding, HostListener, Input, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';

//https://github.com/KostyaTretyak/ng-stack/blob/master/projects/contenteditable/src/lib/contenteditable.directive.ts

@Directive({
  selector: '[contenteditable]',
  providers: [{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ContentEditableDirective), multi: true}],
})
export class ContentEditableDirective implements ControlValueAccessor {

  @Input() propValueAccessor = 'textContent';
  @HostBinding('attr.contenteditable') @Input() contenteditable = true;

  private onChange: (value: string) => void;
  private onTouched: () => void;
  private removeDisabledState: () => void;

  constructor(private elementRef: ElementRef, private renderer: Renderer2) {
  }

  @HostListener('input')
  callOnChange() {
    if (typeof this.onChange == 'function') {
      this.onChange(this.elementRef.nativeElement[this.propValueAccessor]);
    }
  }

  @HostListener('blur')
  callOnTouched() {
    if (typeof this.onTouched == 'function') {
      this.onTouched();
    }
  }

  @HostListener('paste', ['$event'])
  onPaste($event) {
    // after the paste, remove all the formatting
    setTimeout(() => {
      for(let el of this.elementRef.nativeElement.children) {
        this.clearAttr(el);
      }
    }, 500);
  }

  clearAttr(el: any) {
    this.renderer.removeAttribute(el, 'style');
    this.renderer.removeAttribute(el, 'class');
    this.renderer.removeAttribute(el, 'id');
    for (let e of el.children) {
      this.clearAttr(e);
    }
  }

  /**
   * Writes a new value to the element.
   * This method will be called by the forms API to write
   * to the view when programmatic (model -> view) changes are requested.
   *
   * See: [ControlValueAccessor](https://angular.io/api/forms/ControlValueAccessor#members)
   */
  writeValue(value: any): void {
    const normalizedValue = value == null ? '' : value;
    this.renderer.setProperty(this.elementRef.nativeElement, this.propValueAccessor, normalizedValue);
  }

  /**
   * Registers a callback function that should be called when
   * the control's value changes in the UI.
   *
   * This is called by the forms API on initialization so it can update
   * the form model when values propagate from the view (view -> model).
   */
  registerOnChange(fn: () => void): void {
    this.onChange = fn;
  }

  /**
   * Registers a callback function that should be called when the control receives a blur event.
   * This is called by the forms API on initialization so it can update the form model on blur.
   */
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  /**
   * This function is called by the forms API when the control status changes to or from "DISABLED".
   * Depending on the value, it should enable or disable the appropriate DOM element.
   */
  setDisabledState(isDisabled: boolean): void {
    if (isDisabled) {
      this.renderer.setAttribute(this.elementRef.nativeElement, 'disabled', 'true');
      this.removeDisabledState = this.renderer.listen(
        this.elementRef.nativeElement,
        'keydown',
        this.listenerDisabledState
      );
    } else {
      if (this.removeDisabledState) {
        this.renderer.removeAttribute(this.elementRef.nativeElement, 'disabled');
        this.removeDisabledState();
      }
    }
  }

  private listenerDisabledState(e: KeyboardEvent) {
    e.preventDefault();
  }

}