206 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * @file
 | 
						|
 * User permission page behaviors.
 | 
						|
 */
 | 
						|
 | 
						|
(function ($, Drupal, debounce) {
 | 
						|
  /**
 | 
						|
   * Shows checked and disabled checkboxes for inherited permissions.
 | 
						|
   *
 | 
						|
   * @type {Drupal~behavior}
 | 
						|
   *
 | 
						|
   * @prop {Drupal~behaviorAttach} attach
 | 
						|
   *   Attaches functionality to the permissions table.
 | 
						|
   */
 | 
						|
  Drupal.behaviors.permissions = {
 | 
						|
    attach() {
 | 
						|
      const [table] = once('permissions', 'table#permissions');
 | 
						|
      if (!table) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // Create fake checkboxes. We use fake checkboxes instead of reusing
 | 
						|
      // the existing checkboxes here because new checkboxes don't alter the
 | 
						|
      // submitted form. If we'd automatically check existing checkboxes, the
 | 
						|
      // permission table would be polluted with redundant entries. This is
 | 
						|
      // deliberate, but desirable when we automatically check them.
 | 
						|
      const $fakeCheckbox = $(Drupal.theme('checkbox'))
 | 
						|
        .removeClass('form-checkbox')
 | 
						|
        .addClass('fake-checkbox js-fake-checkbox')
 | 
						|
        .attr({
 | 
						|
          disabled: 'disabled',
 | 
						|
          checked: 'checked',
 | 
						|
          title: Drupal.t(
 | 
						|
            'This permission is inherited from the authenticated user role.',
 | 
						|
          ),
 | 
						|
        });
 | 
						|
      const $wrapper = $('<div></div>').append($fakeCheckbox);
 | 
						|
      const fakeCheckboxHtml = $wrapper.html();
 | 
						|
 | 
						|
      /**
 | 
						|
       * Process each table row to create fake checkboxes.
 | 
						|
       *
 | 
						|
       * @param {object} object
 | 
						|
       * @param {HTMLElement} object.target
 | 
						|
       */
 | 
						|
      function tableRowProcessing({ target }) {
 | 
						|
        once('permission-checkbox', target).forEach((checkbox) => {
 | 
						|
          checkbox
 | 
						|
            .closest('tr')
 | 
						|
            .querySelectorAll(
 | 
						|
              'input[type="checkbox"]:not(.js-rid-anonymous, .js-rid-authenticated)',
 | 
						|
            )
 | 
						|
            .forEach((check) => {
 | 
						|
              check.classList.add('real-checkbox', 'js-real-checkbox');
 | 
						|
              check.insertAdjacentHTML('beforebegin', fakeCheckboxHtml);
 | 
						|
            });
 | 
						|
        });
 | 
						|
      }
 | 
						|
 | 
						|
      // An IntersectionObserver object is associated with each of the table
 | 
						|
      // rows to activate checkboxes interactively as users scroll the page
 | 
						|
      // up or down. This prevents processing all checkboxes on page load.
 | 
						|
      const checkedCheckboxObserver = new IntersectionObserver(
 | 
						|
        (entries, thisObserver) => {
 | 
						|
          entries
 | 
						|
            .filter((entry) => entry.isIntersecting)
 | 
						|
            .forEach((entry) => {
 | 
						|
              tableRowProcessing(entry);
 | 
						|
              thisObserver.unobserve(entry.target);
 | 
						|
            });
 | 
						|
        },
 | 
						|
        {
 | 
						|
          rootMargin: '50%',
 | 
						|
        },
 | 
						|
      );
 | 
						|
 | 
						|
      // Select rows with checked authenticated role and attach an observer
 | 
						|
      // to each.
 | 
						|
      table
 | 
						|
        .querySelectorAll(
 | 
						|
          'tbody tr input[type="checkbox"].js-rid-authenticated:checked',
 | 
						|
        )
 | 
						|
        .forEach((checkbox) => checkedCheckboxObserver.observe(checkbox));
 | 
						|
 | 
						|
      // Create checkboxes only when necessary on click.
 | 
						|
      $(table).on(
 | 
						|
        'click.permissions',
 | 
						|
        'input[type="checkbox"].js-rid-authenticated',
 | 
						|
        tableRowProcessing,
 | 
						|
      );
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  /**
 | 
						|
   * Filters the permission list table by a text input search string.
 | 
						|
   *
 | 
						|
   * Text search input: input.table-filter-text
 | 
						|
   * Target table:      input.table-filter-text[data-table]
 | 
						|
   * Source text:       .table-filter-text-source
 | 
						|
   *
 | 
						|
   * @type {Drupal~behavior}
 | 
						|
   */
 | 
						|
  Drupal.behaviors.tableFilterByText = {
 | 
						|
    attach(context, settings) {
 | 
						|
      const [input] = once('table-filter-text', 'input.table-filter-text');
 | 
						|
      if (!input) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      const tableSelector = input.getAttribute('data-table');
 | 
						|
      const $table = $(tableSelector);
 | 
						|
      const $rows = $table.find('tbody tr');
 | 
						|
 | 
						|
      function hideEmptyPermissionHeader(index, row) {
 | 
						|
        const tdsWithModuleClass = row.querySelectorAll('td.module');
 | 
						|
        // Function to check if an element is visible (`display: block`).
 | 
						|
        function isVisible(element) {
 | 
						|
          return getComputedStyle(element).display !== 'none';
 | 
						|
        }
 | 
						|
        if (tdsWithModuleClass.length > 0) {
 | 
						|
          // Find the next visible sibling `<tr>`.
 | 
						|
          let nextVisibleSibling = row.nextElementSibling;
 | 
						|
          while (nextVisibleSibling && !isVisible(nextVisibleSibling)) {
 | 
						|
            nextVisibleSibling = nextVisibleSibling.nextElementSibling;
 | 
						|
          }
 | 
						|
 | 
						|
          // Check if the next visible sibling has the "module" class in any of
 | 
						|
          // its `<td>` elements.
 | 
						|
          let nextVisibleSiblingHasModuleClass = false;
 | 
						|
          if (nextVisibleSibling) {
 | 
						|
            const nextSiblingTdsWithModuleClass =
 | 
						|
              nextVisibleSibling.querySelectorAll('td.module');
 | 
						|
            nextVisibleSiblingHasModuleClass =
 | 
						|
              nextSiblingTdsWithModuleClass.length > 0;
 | 
						|
          }
 | 
						|
 | 
						|
          // Check if this is the last visible row with class "module".
 | 
						|
          const isLastVisibleModuleRow =
 | 
						|
            !nextVisibleSibling || !isVisible(nextVisibleSibling);
 | 
						|
 | 
						|
          // Hide the current row with class "module" if it meets the
 | 
						|
          // conditions.
 | 
						|
          $(row).toggle(
 | 
						|
            !nextVisibleSiblingHasModuleClass && !isLastVisibleModuleRow,
 | 
						|
          );
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      function filterPermissionList(e) {
 | 
						|
        const query = e.target.value;
 | 
						|
        if (query.length === 0) {
 | 
						|
          // Reset table when the textbox is cleared.
 | 
						|
          $rows.show();
 | 
						|
        }
 | 
						|
        // Case insensitive expression to find query at the beginning of a word.
 | 
						|
        const re = new RegExp(`\\b${query}`, 'i');
 | 
						|
 | 
						|
        function showPermissionRow(index, row) {
 | 
						|
          const sources = row.querySelectorAll('.table-filter-text-source');
 | 
						|
          if (sources.length > 0) {
 | 
						|
            const textMatch = sources[0].textContent.search(re) !== -1;
 | 
						|
            $(row).closest('tr').toggle(textMatch);
 | 
						|
          }
 | 
						|
        }
 | 
						|
        // Search over all rows.
 | 
						|
        $rows.show();
 | 
						|
 | 
						|
        // Filter if the length of the query is at least 2 characters.
 | 
						|
        if (query.length >= 2) {
 | 
						|
          $rows.each(showPermissionRow);
 | 
						|
 | 
						|
          // Hide the empty header if they don't have any visible rows.
 | 
						|
          const visibleRows = $table.find('tbody tr:visible');
 | 
						|
          visibleRows.each(hideEmptyPermissionHeader);
 | 
						|
          const rowsWithoutEmptyModuleName = $table.find('tbody tr:visible');
 | 
						|
          // Find elements with class "permission" within visible rows.
 | 
						|
          const tdsWithModuleOrPermissionClass =
 | 
						|
            rowsWithoutEmptyModuleName.find('.permission');
 | 
						|
 | 
						|
          Drupal.announce(
 | 
						|
            Drupal.formatPlural(
 | 
						|
              tdsWithModuleOrPermissionClass.length,
 | 
						|
              '1 permission is available in the modified list.',
 | 
						|
              '@count permissions are available in the modified list.',
 | 
						|
            ),
 | 
						|
          );
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      function preventEnterKey(event) {
 | 
						|
        if (event.which === 13) {
 | 
						|
          event.preventDefault();
 | 
						|
          event.stopPropagation();
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if ($table.length) {
 | 
						|
        $(input).on({
 | 
						|
          keyup: debounce(filterPermissionList, 200),
 | 
						|
          click: debounce(filterPermissionList, 200),
 | 
						|
          keydown: preventEnterKey,
 | 
						|
        });
 | 
						|
      }
 | 
						|
    },
 | 
						|
  };
 | 
						|
})(jQuery, Drupal, Drupal.debounce);
 |