Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

Autoría | Ultima modificación | Ver Log |

import $ from 'jquery';
import Popover from 'bootstrap/js/src/popover';

/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */

const NAME = 'confirmation';
const VERSION = '$VERSION';
const DATA_KEY = `bs.${NAME}`;
const EVENT_KEY = `.${DATA_KEY}`;
const JQUERY_NO_CONFLICT = $.fn[NAME];
const BTN_CLASS_BASE = 'h-100 d-flex align-items-center';
const BTN_CLASS_DEFAULT = 'btn btn-sm';

const DefaultType = {
  ...Popover.DefaultType,
  singleton           : 'boolean',
  popout              : 'boolean',
  copyAttributes      : '(string|array)',
  onConfirm           : 'function',
  onCancel            : 'function',
  btnOkClass          : 'string',
  btnOkLabel          : 'string',
  btnOkIconClass      : 'string',
  btnOkIconContent    : 'string',
  btnCancelClass      : 'string',
  btnCancelLabel      : 'string',
  btnCancelIconClass  : 'string',
  btnCancelIconContent: 'string',
  buttons             : 'array',
};

const Default = {
  ...Popover.Default,
  _attributes         : {},
  _selector           : null,
  placement           : 'top',
  title               : 'Are you sure?',
  trigger             : 'click',
  confirmationEvent   : undefined,
  content             : '',
  singleton           : false,
  popout              : false,
  copyAttributes      : 'href target',
  onConfirm           : $.noop,
  onCancel            : $.noop,
  btnOkClass          : `${BTN_CLASS_DEFAULT} btn-primary`,
  btnOkLabel          : 'Yes',
  btnOkIconClass      : '',
  btnOkIconContent    : '',
  btnCancelClass      : `${BTN_CLASS_DEFAULT} btn-secondary`,
  btnCancelLabel      : 'No',
  btnCancelIconClass  : '',
  btnCancelIconContent: '',
  buttons             : [],
  // @formatter:off
  template            : `
<div class="popover confirmation">
  <div class="arrow"></div>
  <h3 class="popover-header"></h3>
  <div class="popover-body">
    <p class="confirmation-content"></p>
    <div class="confirmation-buttons text-center">
      <div class="btn-group"></div>
    </div>
  </div>
</div>`,
  // @formatter:on
};

if (Default.whiteList) {
  Default.whiteList['*'].push('data-apply', 'data-dismiss');
}

const ClassName = {
  FADE: 'fade',
  SHOW: 'show',
};

const Selector = {
  TITLE  : '.popover-header',
  CONTENT: '.confirmation-content',
  BUTTONS: '.confirmation-buttons .btn-group',
};

const Keymap = {
  13: 'Enter',
  27: 'Escape',
  39: 'ArrowRight',
  40: 'ArrowDown',
};

const Event = {
  HIDE      : `hide${EVENT_KEY}`,
  HIDDEN    : `hidden${EVENT_KEY}`,
  SHOW      : `show${EVENT_KEY}`,
  SHOWN     : `shown${EVENT_KEY}`,
  INSERTED  : `inserted${EVENT_KEY}`,
  CLICK     : `click${EVENT_KEY}`,
  FOCUSIN   : `focusin${EVENT_KEY}`,
  FOCUSOUT  : `focusout${EVENT_KEY}`,
  MOUSEENTER: `mouseenter${EVENT_KEY}`,
  MOUSELEAVE: `mouseleave${EVENT_KEY}`,
  CONFIRMED : `confirmed${EVENT_KEY}`,
  CANCELED  : `canceled${EVENT_KEY}`,
  KEYUP     : `keyup${EVENT_KEY}`,
};

/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */

// keep track of the last openned confirmation for keyboard navigation
let activeConfirmation;

class Confirmation extends Popover {
  // Getters

  static get VERSION() {
    return VERSION;
  }

  static get Default() {
    return Default;
  }

  static get NAME() {
    return NAME;
  }

  static get DATA_KEY() {
    return DATA_KEY;
  }

  static get Event() {
    return Event;
  }

  static get EVENT_KEY() {
    return EVENT_KEY;
  }

  static get DefaultType() {
    return DefaultType;
  }

  // Constructor

  constructor(element, config) {
    super(element, config);

    if ((this.config.popout || this.config.singleton) && !this.config.rootSelector) {
      throw new Error('The rootSelector option is required to use popout and singleton features since jQuery 3.');
    }

    // keep trace of selectors
    this._isDelegate = false;

    if (config.selector) { // container of buttons
      config._selector = `${config.rootSelector} ${config.selector}`;
      this.config._selector = config._selector;
    }
    else if (config._selector) { // children of container
      this.config._selector = config._selector;
      this._isDelegate = true;
    }
    else { // standalone
      this.config._selector = config.rootSelector;
    }

    if (this.config.confirmationEvent === undefined) {
      this.config.confirmationEvent = this.config.trigger;
    }

    if (!this.config.selector) {
      this._copyAttributes();
    }

    this._setConfirmationListeners();
  }

  // Overrides

  isWithContent() {
    return true;
  }

  setContent() {
    const $tip = $(this.getTipElement());
    let content = this._getContent();

    if (typeof content === 'function') {
      content = content.call(this.element);
    }

    this.setElementContent($tip.find(Selector.TITLE), this.getTitle());

    $tip.find(Selector.CONTENT).toggle(!!content);
    if (content) {
      this.setElementContent($tip.find(Selector.CONTENT), content);
    }

    if (this.config.buttons.length > 0) {
      this._setButtons($tip, this.config.buttons);
    }
    else {
      this._setStandardButtons($tip);
    }

    $tip.removeClass(`${ClassName.FADE} ${ClassName.SHOW}`);

    this._setupKeyupEvent();
  }

  dispose() {
    $('body').off(`${Event.CLICK}.${this.uid}`);
    this.eventBody = false;
    this._cleanKeyupEvent();
    super.dispose();
  }

  hide(callback) {
    this._cleanKeyupEvent();
    super.hide(callback);
  }

  // Private

  /**
   * Copy the value of `copyAttributes` on the config object
   * @private
   */
  _copyAttributes() {
    this.config._attributes = {};
    if (this.config.copyAttributes) {
      if (typeof this.config.copyAttributes === 'string') {
        this.config.copyAttributes = this.config.copyAttributes.split(' ');
      }
    }
    else {
      this.config.copyAttributes = [];
    }

    this.config.copyAttributes.forEach((attr) => {
      this.config._attributes[attr] = $(this.element).attr(attr);
    });
  }

  /**
   * Custom event listeners for popouts and singletons
   * @private
   */
  _setConfirmationListeners() {
    const self = this;

    if (!this.config.selector) {
      // cancel original event
      $(this.element).on(this.config.trigger, (e, ack) => {
        if (!ack) {
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
        }
      });

      // manage singleton
      $(this.element).on(Event.SHOWN, function () {
        if (self.config.singleton) {
          // close all other popover already initialized
          $(self.config._selector).not($(this)).filter(function () {
            return $(this).data(DATA_KEY) !== undefined;
          }).confirmation('hide');
        }
      });
    }
    else {
      // cancel original event
      $(this.element).on(this.config.trigger, this.config.selector, (e, ack) => {
        if (!ack) {
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
        }
      });
    }

    if (!this._isDelegate) {
      // manage popout
      this.eventBody = false;
      this.uid = this.element.id || Confirmation.getUID(`${NAME}_group`);

      $(this.element).on(Event.SHOWN, () => {
        if (self.config.popout && !self.eventBody) {
          self.eventBody = $('body').on(`${Event.CLICK}.${self.uid}`, (e) => {
            if ($(self.config._selector).is(e.target) || $(self.config._selector).has(e.target).length > 0) {
              return;
            }
            // close all popover already initialized
            $(self.config._selector).filter(function () {
              return $(this).data(DATA_KEY) !== undefined;
            }).confirmation('hide');

            $('body').off(`${Event.CLICK}.${self.uid}`);
            self.eventBody = false;
          });
        }
      });
    }
  }

  /**
   * Init the standard ok/cancel buttons
   * @param $tip
   * @private
   */
  _setStandardButtons($tip) {
    const buttons = [
      {
        class      : this.config.btnOkClass,
        label      : this.config.btnOkLabel,
        iconClass  : this.config.btnOkIconClass,
        iconContent: this.config.btnOkIconContent,
        attr       : this.config._attributes,
      },
      {
        class      : this.config.btnCancelClass,
        label      : this.config.btnCancelLabel,
        iconClass  : this.config.btnCancelIconClass,
        iconContent: this.config.btnCancelIconContent,
        cancel     : true,
      },
    ];

    this._setButtons($tip, buttons);
  }

  /**
   * Init the buttons
   * @param $tip
   * @param buttons
   * @private
   */
  _setButtons($tip, buttons) {
    const self = this;
    const $group = $tip.find(Selector.BUTTONS).empty();

    buttons.forEach((button) => {
      const btn = $('<a href="#"></a>')
        .addClass(BTN_CLASS_BASE)
        .addClass(button.class || `${BTN_CLASS_DEFAULT} btn-secondary`)
        .html(button.label || '')
        .attr(button.attr || {});

      if (button.iconClass || button.iconContent) {
        btn.prepend($('<i></i>')
          .addClass(button.iconClass || '')
          .text(button.iconContent || ''));
      }

      btn.one('click', function (e) {
        if ($(this).attr('href') === '#') {
          e.preventDefault();
        }

        if (button.onClick) {
          button.onClick.call($(self.element));
        }

        if (button.cancel) {
          self.config.onCancel.call(self.element, button.value);
          $(self.element).trigger(Event.CANCELED, [button.value]);
        }
        else {
          self.config.onConfirm.call(self.element, button.value);
          $(self.element).trigger(Event.CONFIRMED, [button.value]);
          $(self.element).trigger(self.config.confirmationEvent, [true]);
        }

        self.hide();
      });

      $group.append(btn);
    });
  }

  /**
   * Install the keyboatd event handler
   * @private
   */
  _setupKeyupEvent() {
    activeConfirmation = this;
    $(window)
      .off(Event.KEYUP)
      .on(Event.KEYUP, this._onKeyup.bind(this));
  }

  /**
   * Remove the keyboard event handler
   * @private
   */
  _cleanKeyupEvent() {
    if (activeConfirmation === this) {
      activeConfirmation = undefined;
      $(window).off(Event.KEYUP);
    }
  }

  /**
   * Event handler for keyboard navigation
   * @param event
   * @private
   */
  _onKeyup(event) {
    if (!this.tip) {
      this._cleanKeyupEvent();
      return;
    }

    const $tip = $(this.getTipElement());
    const key = event.key || Keymap[event.keyCode || event.which];

    const $group = $tip.find(Selector.BUTTONS);
    const $active = $group.find('.active');
    let $next;

    switch (key) {
      case 'Escape':
        this.hide();
        break;

      case 'ArrowRight':
        if ($active.length && $active.next().length) {
          $next = $active.next();
        }
        else {
          $next = $group.children().first();
        }
        $active.removeClass('active');
        $next.addClass('active').focus();
        break;

      case 'ArrowLeft':
        if ($active.length && $active.prev().length) {
          $next = $active.prev();
        }
        else {
          $next = $group.children().last();
        }
        $active.removeClass('active');
        $next.addClass('active').focus();
        break;

      default:
        break;
    }
  }

  // Static

  /**
   * Generates an uui, copied from Bootrap's utils
   * @param {string} prefix
   * @returns {string}
   */
  static getUID(prefix) {
    let uid = prefix;
    do {
      // eslint-disable-next-line no-bitwise
      uid += ~~(Math.random() * 1000000); // "~~" acts like a faster Math.floor() here
    } while (document.getElementById(uid));
    return uid;
  }

  static _jQueryInterface(config) {
    return this.each(function () {
      let data = $(this).data(DATA_KEY);

      const _config = typeof config === 'object' ? config : {};
      _config.rootSelector = $(this).selector || _config.rootSelector; // this.selector removed in jQuery > 3

      if (!data && /destroy|hide/.test(config)) {
        return;
      }

      if (!data) {
        data = new Confirmation(this, _config);
        $(this).data(DATA_KEY, data);
      }

      if (typeof config === 'string') {
        if (typeof data[config] === 'undefined') {
          throw new TypeError(`No method named "${config}"`);
        }
        data[config]();
      }
    });
  }
}

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 */

$.fn[NAME] = Confirmation._jQueryInterface;
$.fn[NAME].Constructor = Confirmation;
$.fn[NAME].noConflict = function () {
  $.fn[NAME] = JQUERY_NO_CONFLICT;
  return Confirmation._jQueryInterface;
};

export default Confirmation;