Home Reference Source

src/components/modaledit/ModalEdit.js

/* globals InlineEditor */
import _ from 'lodash';
import Formio from '../../Formio';
import TextAreaComponent from '../textarea/TextArea';
import { withSwitch } from '../../utils/utils.js';

const EDIT = Symbol('edit');
const VIEW = Symbol('view');
const CKEDITOR = 'https://cdn.ckeditor.com/ckeditor5/11.2.0/inline/ckeditor.js';

export default class ModalEditComponent extends TextAreaComponent {
  static schema(...extend) {
    return TextAreaComponent.schema({
      type: 'modaledit',
      label: 'Modal Edit',
      key: 'modalEdit',
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Modal Edit',
      group: 'advanced',
      icon: 'fa fa-font',
      weight: 50,
      schema: ModalEditComponent.schema()
    };
  }

  constructor(...args) {
    super(...args);
    const [get, toggle] = withSwitch(VIEW, EDIT);
    this.getMode = get;
    this.toggleMode = () => {
      toggle();
      this.emit('modechange');
    };
  }

  build() {
    this.createElement();

    const labelAtTheBottom = this.component.labelPosition === 'bottom';

    if (!labelAtTheBottom) {
      this.createLabel(this.element);
    }

    this.editElement = this.buildEditMode({
      onCloseRequest: () => {
        this.removeChildFrom(this.editElement, document.body);
        this.toggleMode();
      }
    });
    this.preview = this.ce('div', { class: 'edittable-preview' });
    this.element.appendChild(this.preview);
    this.updateView(this.preview);

    if (labelAtTheBottom) {
      this.createLabel(this.element);
    }

    this.restoreValue();

    this.on('modechange', this.updateView.bind(this, this.preview));
  }

  buildViewMode({ content = '', onEdit: onClick }) {
    const icon = this.ce('i', { class: this.iconClass('edit') });
    const button = this.ce(
      'button',
      {
        type: 'button',
        role: 'button',
        onClick,
        class: 'btn btn-xxs btn-warning formio-modaledit-edit'
      },
      icon
    );
    const child = this.ce('div', { class: 'modaledit-view-inner reset-margins' });

    child.innerHTML = this.interpolate(content);

    return this.ce('div', {
      class: 'formio-modaledit-view-container',
      onDblClick: onClick,
    }, [
      button,
      child
    ]);
  }

  buildEditMode({ onCloseRequest, onCloseClick, onOverlayClick }) {
    const overlay = this.ce('div', { class: 'formio-dialog-overlay' });
    const inner = this.ce('div', { class: 'reset-margins' });
    const close = this.ce(
      'button',
      {
        type: 'button',
        class: 'btn btn-primary btn-xs formio-modaledit-close',
      },
      'Close'
    );
    const container = this.ce(
      'div',
      {
        class: 'formio-modaledit-content'
      },
      [
        close,
        inner
      ]
    );
    const dialog = this.ce('div', {
      class: 'formio-dialog formio-dialog-theme-default formio-modaledit-dialog',
    }, [
      overlay,
      container
    ]);
    const [dw, dh] = this.defaultEditorSize;
    const layout = _.get(this.component, 'editorLayout', this.defaultLayout);
    const widthPath = _.get(this.layoutOptions, [layout, 'width']);
    const heightPath = _.get(this.layoutOptions, [layout, 'height']);
    const width = _.get(this.component, widthPath, dw);
    const height = _.get(this.component, heightPath, dh);

    this.createInput(inner);

    if (this.isPlain) {
      const textarea = container.querySelector('textarea');
      textarea.style.minHeight = `${height}px`;
      textarea.style.borderRadius = 0;
      textarea.style.resize = 'vertical';
    }

    container.style.position = 'absolute';
    container.style.backgroundColor = '#fff';
    container.style.width = `${width}px`;
    container.style.minHeight = `${height}px`;

    this.addEventListener(overlay, 'click', event => {
      event.preventDefault();

      if (_.isFunction(onOverlayClick)) {
        onOverlayClick();
      }

      if (_.isFunction(onCloseRequest)) {
        onCloseRequest();
      }
    });

    this.addEventListener(close, 'click', event => {
      event.preventDefault();

      if (_.isFunction(onCloseClick)) {
        onCloseClick();
      }

      if (_.isFunction(onCloseRequest)) {
        onCloseRequest();
      }
    });

    dialog.updateLayout = () => {
      const rect = this.preview.getBoundingClientRect();
      container.style.top = `${rect.top}px`;
      container.style.left = `${rect.left}px`;
      container.style.width = `${Math.max(width, rect.width)}px`;
    };

    return dialog;
  }

  updateView(container) {
    const mode = this.getMode();

    if (this.options.builder || mode === VIEW) {
      const view = this.buildViewMode({
        onEdit: this.toggleMode,
        content: _.isString(this.dataValue) ? this.dataValue : '',
      });

      if (container.firstChild) {
        container.replaceChild(
          view,
          container.firstChild
        );
      }
      else {
        container.appendChild(view);
      }
    }

    if (mode === EDIT) {
      this.editElement.updateLayout();
      document.body.appendChild(this.editElement);
    }
  }

  // get defaultValue() {
  //   const value = super.defaultValue;
  //   return '';
  // }

  get defaultEditorSize() {
    return [300, 200];
  }

  get defaultLayout() {
    return 'grow';
  }

  get layoutOptions() {
    return {
      grow: {
        width: 'minEditorWidth',
        height: 'minEditorHeight',
      },
      fixed: {
        width: 'width',
        height: 'height'
      }
    };
  }

  addCKE(element, settings, onChange) {
    settings = _.isEmpty(settings) ? null : settings;
    return Formio.requireLibrary('ckeditor', 'InlineEditor', CKEDITOR, true)
      .then(() => {
        if (!element.parentNode) {
          return Promise.reject();
        }
        return InlineEditor.create(element, settings).then(editor => {
          editor.model.document.on('change', () => onChange(editor.data.get()));
          return editor;
        });
      });
  }
}