plus-8.x-4.x-dev/plus_enhancements/js/Drupal.Enhancement.es6.js

plus_enhancements/js/Drupal.Enhancement.es6.js
/**
 * @file
 * Drupal+ Enhancement base class.
 */
(($, Drupal) => {
  'use strict';

  // Private properties.
  const _$element = new WeakMap();
  const _$wrapper = new WeakMap();
  const _attachments = new WeakMap();
  const _deferred = new WeakMap();
  const _detachments = new WeakMap();
  const _debug = new WeakMap();
  const _id = new WeakMap();
  const _initHandlers = new WeakMap();
  const _parent = new WeakMap();
  const _settings = new WeakMap();
  const _initialized = new WeakMap();
  const _storage = new WeakMap();

  /**
   * @class Enhancement
   */
  class Enhancement {

    /**
     * Constructor.
     *
     * @param {String} id
     * @param {Object} [settings = {}]
     * @param {Enhancement} [parent = null]
     */
    constructor(id, settings = {}, parent = null) {
      // Set private properties.
      _debug.set(this, false);
      _id.set(this, id);
      _initialized.set(this, false);
      _parent.set(this, parent);
      _settings.set(this, settings);
      _storage.set(this, Drupal.Storage.create('Drupal.Enhancement.' + id));

      /**
       * The element for this enhancement.
       *
       * @type {jQuery}
       */
      this.$element = Drupal.$noop;

      /**
       * The wrapper around the element for this enhancement.
       *
       * @type {jQuery}
       */
      this.$wrapper = Drupal.$noop;

      /**
       * The attachments object for this enhancement.
       *
       * @type {Object}
       */
      this.attachments = {};

      /**
       * Stores any deferred events.
       *
       * @type {Object}
       */
      this.deferred = {};

      /**
       * The detachments object for this enhancement.
       *
       * @type {Object}
       */
      this.detachments = {};

      // Reference custom parent properties that this instance doesn't have.
      if (parent) {
        this.extend(parent);
      }
    }

    get $element() {
      return _$element.get(this);
    }

    set $element(value) {
      if (!(value instanceof $)) {
        return Drupal.fatal(Drupal.t('A user enhancement $element must be a jQuery object.'));
      }
      return _$element.set(this, value);
    }

    get $wrapper() {
      return _$wrapper.get(this);
    }

    set $wrapper(value) {
      if (!(value instanceof $)) {
        return Drupal.fatal(Drupal.t('A user enhancement $wrapper must be a jQuery object.'));
      }
      return _$wrapper.set(this, value);
    }

    get attachments() {
      return _attachments.get(this);
    }

    set attachments(value) {
      return _attachments.set(this, value);
    }

    get deferred() {
      return _deferred.get(this);
    }

    set deferred(value) {
      return _deferred.set(this, value);
    }

    get detachments() {
      return _detachments.get(this);
    }

    set detachments(value) {
      return _detachments.set(this, value);
    }

    /**
     * Indicates whether debug mode is enabled on the user enhancement.
     *
     * @return {Boolean}
     */
    get debug() {
      return _debug.get(this);
    }

    /**
     * Setter for debug property.
     *
     * @param {Boolean} [value = true]
     *   Flag indicating whether to enable or disable debug mode.
     */
    set debug(value) {
      _debug.set(this, Boolean(value));
    }

    /**
     * The default settings for the user enhancement.
     *
     * @returns {Object}
     */
    get defaultSettings() {
      return {};
    }

    /**
     * The machine name identifier of the user enhancement.
     *
     * @return {String}
     */
    get id() {
      return _id.get(this);
    }

    /**
     * Indicates whether the user enhancement has been initialized.
     *
     * @return {Boolean}
     */
    get initialized() {
      return _initialized.get(this);
    }

    get parent() {
      return _parent.get(this);
    }

    /**
     * This settings for this user enhancement.
     *
     * @return {Object}
     */
    get settings() {
      const parent = this.parent;
      return $.extend(true, parent && parent.settings, this.defaultSettings, _settings.get(this));
    }

    /**
     * The Storage object for this user enhancement.
     *
     * @return {Drupal.Storage}
     */
    get storage() {
      return _storage.get(this);
    }

    /**
     * Creates an attachment behavior for the user enhancement.
     *
     * @param {string} selectors
     *   The selector(s) on which to bind this behavior.
     * @param {Function} [callback]
     *   Optional. A callback function to implement when the behavior is
     *   attached to the element as defined by the selector.
     *
     * @see Drupal.attachBehaviors
     *
     * @chainable
     *
     * @return {Enhancement}
     */
    attach(selectors, callback) {
      this.attachments[selectors] = (context, settings) => {
        // Only attach behaviors that are not already attached (similar to
        // $.once).
        const $selectors = $(context).find(selectors).filter(() => {
          return !$(this).data('Drupal.Enhancement.' + this.id);
        });
        if ($selectors[0]) {
          this.$selectors = $selectors;
          this.__args__ = arguments;
          this.$selectors.data('Drupal.Enhancement.' + this.id, this);
          callback.apply(this, [this.$selectors, this.settings]);
          delete this.__args__;
        }
      };
      return this;
    }

    /**
     * Attaches defined elements to the DOM and creates an attachment
     * behavior.
     *
     * @param {String} method
     *   The method on which to invoke on the selectors to attach the
     *   elements.
     * @param {string} selectors
     *   The selector(s) on which to bind this behavior.
     * @param {Function} [callback]
     *   Optional. A callback function to implement when the behavior is
     *   attached to the element as defined by the selector.
     *
     * @chainable
     *
     * @return {Enhancement}
     */
    attachElements(method, selectors, callback) {
      this.attach(selectors, ($selectors) => {
        const parts = (method + ':*').split(':');
        const filter = parts[1] === '*' ? parts[1] : ':' + parts[1];
        method = parts[0];
        if (this.$wrapper[0]) {
          this.$wrapper.append(this.$element);
          $selectors.filter(filter)[method](this.$wrapper);
        }
        else {
          $selectors.filter(filter)[method](this.$element);
        }
        if (callback) {
          callback.apply(this, arguments);
        }
      });
      return this;
    }

    /**
     * Creates an detachment behavior for the user enhancement.
     *
     * @param {string} selectors
     *   The selector(s) on which to bind this behavior.
     * @param {Function} [callback]
     *   Optional. A callback function to implement when the behavior is
     *   attached to the element as defined by the selector.
     *
     * @see Drupal.detachBehaviors
     *
     * @chainable
     *
     * @return {Enhancement}
     */
    detach(selectors, callback) {
      this.detachments[selectors] = (context, settings, trigger) => {
        const $selectors = $(context).find(selectors);
        if ($selectors[0]) {
          this.__args__ = arguments;
          this.$selectors = $selectors;
          callback.apply(this, [this.$selectors, this.settings]);
          this.$selectors.removeData('Drupal.Enhancement.' + this.id);
          delete this.__args__;
        }
      };
      return this;
    }

    /**
     * Detaches defined elements from the DOM and creates a detachment
     * behavior.
     *
     * @param {string} selectors
     *   The selector(s) on which to bind this behavior.
     * @param {Function} [callback]
     *   Optional. A callback function to implement when the behavior is
     *   attached to the element as defined by the selector.
     *
     * @chainable
     *
     * @return {Enhancement}
     */
    detachElements(selectors, callback) {
      this.detach(selectors, () => {
        this.$element.remove();
        this.$wrapper.remove();
        if (callback) {
          callback.apply(this, arguments);
        }
      });
      return this;
    }

    /**
     * Displays an error message, if debugging is enabled.
     *
     * @param {String} message
     *   The message to display.
     * @param {Object} [args]
     *   An arguments to use in message.
     */
    error(message, args) {
      Drupal.error(message, args);
    }

    /**
     * Extends a user enhancement.
     *
     * @param {...Object} args
     *   The object(s) to extend from. If the first parameter is a boolean, it
     *   will be treated as a flag that determines whether only shallow
     *   properties are extended from any passed objects.
     *
     * @return {Enhancement}
     */
    extend(...args) {
      const deep = args[0] === true || args[0] === false ? args.shift() : false;
      args.forEach(obj => {
        Object.keys(obj).forEach(key => {
          if (!deep && !obj.hasOwnProperty(key)) {
            return;
          }
          this[key] = obj[key];
        });
      });
      return this;
    }

    /**
     * Provide a helper method for displaying when something is went wrong.
     *
     * @param {String} message
     *   The message to display.
     * @param {Object} [args]
     *   An arguments to use in message.
     *
     * @return {Boolean<false>}
     *   Always returns FALSE.
     */
    fatal(message, args) {
      return Drupal.fatal(message, args);
    }

    /**
     * Retrieves a setting value for this user enhancement.
     *
     * @param {String} name
     *   The machine name of a setting to retrieve.
     * @param {*|Function} [defaultValue]
     *   The default value to use if the setting does not exist. A function
     *   can also be passed and it will be invoked in the even complex logic
     *   needs to occur to determine the value (e.g. parsing the DOM).
     *
     * @return {*}
     *   The setting value, if any.
     */
    getSetting(name, defaultValue) {
      const settings = this.settings;
      if (settings[name] === undefined || settings[name] === null) {
        return typeof defaultValue === 'function' ? defaultValue.call(this) : defaultValue;
      }
      return settings[name];
    }

    /**
     * Displays an informative message, if debugging is enabled.
     *
     * @param {String} message
     *   The message to display.
     * @param {Object} [args]
     *   An arguments to use in message.
     */
    info(message, args) {
      if (this.debug) {
        Drupal.info(message, args);
      }
    }

    /**
     * Invoked upon the initial creation of the user enhancement.
     *
     * This is for non DOM related setup tasks. If you need to traverse DOM
     * elements, use the "ready" method instead.
     *
     * @param {Function} [callback = null]
     *   Optional. A callback function to invoke when the user enhancement is
     *   initially created. If not callback is provided, then all currently
     *   registered initialization handlers will be invoked and the user
     *   enhancement will be flagged as "initialized".
     *
     * @chainable
     *
     * @return {Enhancement}
     *
     * @see Enhancement.ready
     */
    init(callback = null) {
      // Immediate return if already initialized.
      if (_initialized.get(this)) {
        return this;
      }
      let handlers = _initHandlers.get(this) || new Set();
      if (callback) {
        handlers.add(callback);
      }
      else {
        handlers.forEach(handler => {
          handler.call(this);
        });
        handlers.clear();
        _initialized.set(this, true);
      }
      _initHandlers.set(this, handlers);
      return this;
    }

    /**
     * Namespaces an Event type by appending the name of the user enhancement.
     *
     * @param {String} [type]
     *   The event type.
     *
     * @return {Array}
     *   The namespaced events.
     */
    namespaceEventType(type) {
      type = type || '';
      const types = type.split(' ');
      types.forEach((type, i) => {
        let namespaced = type.split('.');
        namespaced.push(this.id);
        types[i] = namespaced.join('.');
      });
      return types;
    }

    /**
     * Unbinds any events from the Enhancement.$element object.
     *
     * @param {String} type
     *   The event type to unbind.
     *
     * @chainable
     *
     * @return {Enhancement}
     */
    off(type) {
      const namespaced = this.namespaceEventType(type);

      // Remove any attributes from the element.
      if (this.$element[0]) {
        const attributes = this.$element[0].attributes;
        for (let i in attributes) {
          if (!attributes.hasOwnProperty(i)) {
            continue;
          }
          let name = attributes[i].name;

          // Ignore any attributes that don't start with the following.
          if (!/^data-user-enhancement-/.test(name)) {
            continue;
          }

          // Remove either a specific event type namespaced data attribute, if
          // one was specified, or all namespaced types.
          if (new RegExp(type ? namespaced.join('-').replace(/\./g, '-') : this.id.replace(/\./g, '-')).test(name)) {
            attributes.removeNamedItem(name);
          }
        }
      }

      // Remove the DOM handler.
      Drupal.$document.off(namespaced.join(' '));
      return this;
    }

    /**
     * Binds and event handler on the Enhancement.$element object.
     *
     * @param {String} type
     *   The event type to bind.
     * @param {Function} handler
     *   The event handler callback.
     *
     * @chainable
     *
     * @return {Enhancement}
     */
    on(type, handler) {
      const namespaced = this.namespaceEventType(type);
      const dataAttribute = 'data-user-enhancement-' + namespaced.join('-').replace(/\./g, '-');
      this.$element.attr(dataAttribute, 'true');
      Drupal.$document.on(namespaced.join(' '), '[' + dataAttribute + ']', handler.bind(this));
      return this;
    }

    /**
     * Parses a function's arguments into a proper array.
     *
     * @param {Array} args
     *   The function's arguments.
     * @param {Boolean} [bind=true]
     *   Toggle determining whether or not any functions passed should be
     *   properly bound to this enhancement. Defaults to true.
     *
     * @return {Array}
     *   The parsed arguments.
     */
    parseArguments(args, bind) {
      args = [...args];
      if (bind === void 0 || bind) {
        args.forEach((arg, i) => {
          if (typeof arg === 'function') {
            args[i] = arg.bind(this);
          }
        });
      }
      return args;
    }

    /**
     * Prepend a special "DOM ready" attachment.
     *
     * @param {Function} callback
     *   The callback to invoke when the DOM is ready.
     *
     * @chainable
     *
     * @return {Enhancement}
     */
    ready(callback) {
      let o = {};
      o[this.random('__ready__')] = callback;
      this.attachments = {...o, ...this.attachments};
      return this;
    }

    /**
     * Sets a setting for this user enhancement.
     *
     * @param {String|Object} name
     *   The machine name of a setting to set. Optionally, an object can be
     *   passed instead to set multiple key/value settings.
     * @param {*} [value]
     *   The value to set.
     *
     * @chainable
     *
     * @return {Enhancement}
     */
    setSetting(name, value) {
      const settings = _settings.get(this);

      let obj = {...typeof name === 'object' ? name : {}};
      if (typeof name === 'string') {
        obj[name] = value;
      }

      Object.keys(obj).forEach(key => {
        settings[key] = Drupal.typeCast(this.getSetting(key), obj[key]);
      });

      _settings.set(this, settings);

      return this;
    }

    /**
     * Triggers an event on a defined user enhancement $element.
     *
     * @chainable
     *
     * @return {Enhancement}
     */
    trigger(...args) {
      this.$element.trigger.apply(this.$element, this.parseArguments(args));
      return this;
    }

    /**
     * Displays a warning message, if debugging is enabled.
     *
     * @param {String} message
     *   The message to display.
     * @param {Object} [args]
     *   An arguments to use in message.
     */
    warning(message, args) {
      if (this.debug) {
        Drupal.warn(message, args);
      }
    }

  }

  // Export the Enhancement class to the Drupal namespace.
  Drupal.Enhancement = Enhancement;

})(jQuery, Drupal);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc