Home Reference Source

src/components/_classes/componentModal/ComponentModal.js

import _ from 'lodash';
import { fastCloneDeep } from '../../../utils/utils';

export default class ComponentModal {
  static render(component, data, topLevel) {
    const children = component.renderTemplate('component', data, topLevel);
    const isOpened = this;
    return component.renderTemplate('componentModal', {
      ...data,
      children,
      isOpened
    });
  }

  constructor(component, element, isOpened, currentValue) {
    this.isOpened = isOpened;
    this.component = component;
    this.element = element;
    this.currentValue = fastCloneDeep(currentValue);
    this.dataLoaded = false;
    this.init();
  }

  get refs() {
    return this.component.refs;
  }

  init() {
    this.openModalListener = this.openModalHandler.bind(this);
    this.showDialogListener = (event) => {
      if (this.isValueChanged() && !this.component.disabled) {
        this.showDialog();
      }
      else {
        this.closeModalHandler(event);
      }
    };
    this.closeModalListener = this.closeModalHandler.bind(this);
    this.saveModalListener = this.saveModalValueHandler.bind(this);
    this.closeDialogListener = this.closeDialog.bind(this);
    this.saveDialogListener = this.saveDialog.bind(this);
    this.loadRefs();
  }

  setValue(value) {
    if (this.dataLoaded) {
      return;
    }

    this.currentValue = fastCloneDeep(value);
    this.dataLoaded = true;
    this.updateView();
  }

  setOpenModalElement(template) {
    this.openModalTemplate = template;
    this.component.setContent(this.refs.openModalWrapper, template);
    this.loadRefs();
    this.setEventListeners();
    if (this.isOpened) {
      this.refs.modalWrapper.classList.add('formio-dialog-disabled-animation');
      this.openModal();
    }
  }

  get templateRefs() {
    return {
      modalOverlay: 'single',
      modalContents: 'single',
      modalClose: 'single',
      openModalWrapper: 'single',
      openModal: 'single',
      modalSave: 'single',
      modalWrapper: 'single',
    };
  }

  loadRefs() {
    this.component.loadRefs(this.element, this.templateRefs);
  }

  removeEventListeners() {
    this.component.removeEventListener(this.refs.openModal, 'click', this.openModalListener);
    this.component.removeEventListener(this.refs.modalOverlay, 'click', this.refs.modalSave ? this.showDialogListener : this.saveModalListener);
    this.component.removeEventListener(this.refs.modalClose, 'click', this.showDialogListener);
    this.component.removeEventListener(this.refs.modalSave, 'click', this.saveModalListener);
  }

  setEventListeners() {
    this.removeEventListeners();
    this.component.addEventListener(this.refs.openModal, 'click', this.openModalListener);
    this.component.addEventListener(this.refs.modalOverlay, 'click', this.refs.modalSave ? this.showDialogListener : this.saveModalListener);
    this.component.addEventListener(this.refs.modalClose, 'click', this.showDialogListener);
    this.component.addEventListener(this.refs.modalSave, 'click', this.saveModalListener);
  }

  isValueChanged() {
    let componentValue = this.component.getValue();
    let currentValue = this.currentValue;

    //excluding metadata comparison for components that have it in dataValue (for ex. nested forms)
    if (componentValue && componentValue.data && componentValue.metadata) {
      componentValue = this.component.getValue().data;
      currentValue = this.currentValue.data;
    }

    return !_.isEqual(componentValue, currentValue);
  }

  setOpenEventListener() {
    this.component.removeEventListener(this.refs.openModal, 'click', this.openModalListener);
    this.component.loadRefs(this.refs.openModalWrapper, {
      'openModal': 'single',
    });
    this.component.addEventListener(this.refs.openModal, 'click', this.openModalListener);
  }

  openModalHandler(event) {
    event.preventDefault();
    this.openModal();
  }

  positionOverElement() {
    // Position the modal just over the element on the page.
    const elementOffset = this.element.getBoundingClientRect().top;
    const modalHeight = this.refs.modalContents.getBoundingClientRect().height;
    let modalTop = elementOffset - modalHeight - 10;
    modalTop = modalTop > 0 ? modalTop : 10;
    this.refs.modalWrapper.style.paddingTop = `${modalTop}px`;
  }

  openModal() {
    this.isOpened = true;
    this.refs.modalWrapper.classList.remove('component-rendering-hidden');
    if (this.component.component.type === 'signature') {
      // Position signature modals just above the signature button.
      this.positionOverElement();
    }
  }

  updateView() {
    const template = _.isEqual(this.currentValue, this.component.defaultValue)
      ? this.openModalTemplate
      : this.component.getModalPreviewTemplate();
    this.component.setContent(this.refs.openModalWrapper, template);
    this.setOpenEventListener();
  }

  closeModal() {
    this.refs.modalWrapper.classList.remove('formio-dialog-disabled-animation');
    this.refs.modalWrapper.classList.add('component-rendering-hidden');
    this.isOpened = false;
    this.updateView();
  }

  closeModalHandler(event) {
    event.preventDefault();
    if (!this.component.disabled) {
      this.component.setValue(_.cloneDeep(this.currentValue), { resetValue: true });
    }
    this.closeModal();
  }

  showDialog() {
    this.dialogElement = this.component.ce('div');
    const dialogContent = `
      <h3 ref="dialogHeader">${this.component.t('Do you want to clear changes?')}</h3>
      <div style="display:flex; justify-content: flex-end;">
        <button ref="dialogCancelButton" class="btn btn-secondary">${this.component.t('Cancel')}</button>
        <button ref="dialogYesButton" class="btn btn-danger">${this.component.t('Yes, delete it')}</button>
      </div>
    `;

    this.dialogElement.innerHTML = dialogContent;
    this.dialogElement.refs = {};
    this.component.loadRefs.call(this.dialogElement, this.dialogElement, {
      dialogHeader: 'single',
      dialogCancelButton: 'single',
      dialogYesButton: 'single',
    });

    this.dialog = this.component.createModal(this.dialogElement);
    this.component.addEventListener(this.dialogElement.refs.dialogYesButton, 'click', this.saveDialogListener);
    this.component.addEventListener(this.dialogElement.refs.dialogCancelButton, 'click', this.closeDialogListener);
  }

  closeDialog(event) {
    event.preventDefault();
    this.dialog.close();
    this.component.removeEventListener(this.dialogElement.refs.dialogYesButton, 'click', this.saveDialogListener);
    this.component.removeEventListener(this.dialogElement.refs.dialogCancelButton, 'click', this.closeDialogListener);
  }

  saveDialog(event) {
    this.closeDialog(event);
    this.closeModalHandler(event);
  }

  saveModalValueHandler(event) {
    event.preventDefault();
    this.currentValue = fastCloneDeep(this.component.dataValue);
    this.closeModal();
  }
}