refreshless-8.x-1.x-dev/modules/refreshless_turbo/js/stylesheet_manager.js

modules/refreshless_turbo/js/stylesheet_manager.js
(function(Drupal, drupalSettings, $) {

  'use strict';

  /**
   * Our event namespace.
   *
   * @type {String}
   *
   * @see https://learn.jquery.com/events/event-basics/#namespacing-events
   */
  const eventNamespace = 'refreshless-turbo-stylesheet-manager';

  /**
   * Order attribute name added to stylesheet <link> elements by the back-end.
   *
   * @type {String}
   */
  const weightAttributeName =
    drupalSettings.refreshless.stylesheetOrderAttributeName;

  /**
   * RefreshLess Turbo stylesheet manager class.
   */
  class StylesheetManager {

    /**
     * The context element to attach to; usually the <html> element.
     *
     * @type {HTMLElement}
     */
    #context;

    constructor(context) {

      this.#context = context;

      this.#bindEventHandlers();

    }

    /**
     * Destroy this instance.
     */
    destroy() {

      this.#unbindEventHandlers();

    }

    /**
     * Bind all of our event handlers.
     */
    #bindEventHandlers() {

      // @see https://ambientimpact.com/web/snippets/javascript-template-literal-as-object-property-name
      $(this.#context).on({
        // This event can be triggered to sort stylesheets at any time.
        [`refreshless:sort-stylesheets.${eventNamespace}`]: (event) => {

          const $closest = $(event.target).closest(this.#context);

          if ($closest.length === 0) {

            console.warn(
              '%cRefreshLess%c: %s requires %o to either be or be within %o!',
              'font-style: italic', 'font-style: normal',
              event.type, event.target, this.#context,
            );

            return;

          }

          this.#sort($closest);

        },
        [`turbo:before-stylesheets-merge.${eventNamespace}`]: (event) => {
          this.#beforeMergeEventHandler(event);
        },
        [`turbo:stylesheets-merged.${eventNamespace}`]: (event) => {
          this.#mergedEventHandler(event);
        },
        [`turbo:stylesheets-loaded.${eventNamespace}`]: (event) => {
          this.#loadedEventHandler(event);
        },
        [`turbo:before-stylesheets-remove.${eventNamespace}`]: (event) => {
          this.#beforeRemoveEventHandler(event);
        },
        [`turbo:stylesheets-removed.${eventNamespace}`]: (event) => {
          this.#removedEventHandler(event);
        },
      });

    }

    /**
     * Unbind all of our event handlers.
     */
    #unbindEventHandlers() {

      $(this.#context).off(`.${eventNamespace}`);

    }

    /**
     * 'turbo:before-stylesheets-merge' event handler.
     *
     * This prevents duplicate stylesheets that only differ by their weight
     * attribute being merged in by transfering a new weight attribute value
     * to the existing stylesheet pointing to the same href, only merging a
     * stylesheet if one with that href isn't already present in the document.
     *
     * @param {jQuery.Event} event
     */
    #beforeMergeEventHandler(event) {

      const alteredNewStylesheets = [];

      $(event.detail.newStylesheets)
      .filter(`[${weightAttributeName}]`)
      .each((i, element) => {

        const $existing = $(event.detail.oldStylesheets).filter(
          `[href="${$(element).attr('href')}"][${weightAttributeName}]`,
        );

        // If there is no existing stylesheet with the same URL, allow this
        // stylesheet to be merged and continue to the next one.
        if ($existing.length === 0) {

          alteredNewStylesheets.push(element);

          return;

        }

        // If an existing stylesheet exists, transfer the new element's weight
        // attribute to the existing one, and don't merge in the new element.
        $existing.attr(
          weightAttributeName, $(element).attr(weightAttributeName),
        );

      });

      event.detail.newStylesheets = alteredNewStylesheets;

      const beforeMergeEvent = new CustomEvent(
        'refreshless:before-stylesheets-merge', {
          detail: event.detail,
        },
      );

      this.#context.dispatchEvent(beforeMergeEvent);

    }

    /**
     * Sort all stylesheets in the <head> based on their weight attribute.
     *
     * @param {HTMLElement|jQuery} context
     *   The context; usually the <html> element.
     *
     * @return {Boolean}
     *   True if sorting occurred, false if no sorting was required.
     */
    #sort(context) {

      const $elements = $(
        `head link[rel="stylesheet"][${weightAttributeName}]`, context,
      );

      /**
       * True if the sort order was changed, false otherwise.
       *
       * This prevents unnececessary DOM manipulation if no sorting occurred.
       *
       * @type {Boolean}
       */
      let wasSorted = false;

      /**
       * <link> elements sorted by the order property generated by the back-end.
       *
       * @type {HTMLLinkElement[]}
       *
       * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
       */
      const sorted = $elements.toArray().sort((a, b) => {

        const aWeight = Number.parseInt($(a).attr(weightAttributeName));

        const bWeight = Number.parseInt($(b).attr(weightAttributeName));

        if (aWeight < bWeight) {

          wasSorted = true;

          return -1;

        } else if (aWeight > bWeight) {

          wasSorted = true;

          return 1;

        }

        return 0;

      });

      if (wasSorted === false) {
        return false;
      }

      // Append all the sorted <link> elements. This will be done in the newly
      // sorted order.
      $('head', context).append(sorted);

      return true;

    }

    /**
     * 'turbo:stylesheets-merged' event handler.
     *
     * This sorts all stylesheets using their weight attribute to ensure CSS
     * specificity is preserved.
     *
     * @param {jQuery.Event} event
     *
     * @see https://www.drupal.org/project/refreshless/issues/3399314
     *   CSS specificity issue.
     */
    #mergedEventHandler(event) {

      this.#sort(event.target);

      const mergedEvent = new CustomEvent(
        'refreshless:stylesheets-merged', {detail: event.detail},
      );

      this.#context.dispatchEvent(mergedEvent);

    }

    /**
     * 'turbo:stylesheets-loaded' event handler.
     *
     * @param {jQuery.Event} event
     */
    #loadedEventHandler(event) {

      const loadedEvent = new CustomEvent(
        'refreshless:stylesheets-loaded', {detail: event.detail},
      );

      this.#context.dispatchEvent(loadedEvent);

    }

    /**
     * 'turbo:before-stylesheets-remove' event handler.
     *
     * @param {jQuery.Event} event
     */
    #beforeRemoveEventHandler(event) {

      const beforeRemoveEvent = new CustomEvent(
        'refreshless:before-stylesheets-remove', {
          detail: event.detail,
        },
      );

      this.#context.dispatchEvent(beforeRemoveEvent);

    }

    /**
     * 'turbo:stylesheets-removed' event handler.
     *
     * @param {jQuery.Event} event
     */
    #removedEventHandler(event) {

      const removedEvent = new CustomEvent(
        'refreshless:stylesheets-removed', {detail: event.detail},
      );

      this.#context.dispatchEvent(removedEvent);

    }

  }

  // Merge Drupal.RefreshLess.classes into the existing Drupal global.
  $.extend(true, Drupal, {RefreshLess: {classes: {
    TurboStylesheetManager: StylesheetManager,
  }}});

})(Drupal, drupalSettings, jQuery);

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

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