Chris@0: /** Chris@0: * @file Chris@0: * Block behaviors. Chris@0: */ Chris@0: Chris@17: (function($, window, Drupal) { Chris@0: /** Chris@0: * Provide the summary information for the block settings vertical tabs. Chris@0: * Chris@0: * @type {Drupal~behavior} Chris@0: * Chris@0: * @prop {Drupal~behaviorAttach} attach Chris@0: * Attaches the behavior for the block settings summaries. Chris@0: */ Chris@0: Drupal.behaviors.blockSettingsSummary = { Chris@0: attach() { Chris@0: // The drupalSetSummary method required for this behavior is not available Chris@0: // on the Blocks administration page, so we need to make sure this Chris@0: // behavior is processed only if drupalSetSummary is defined. Chris@0: if (typeof $.fn.drupalSetSummary === 'undefined') { Chris@0: return; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Create a summary for checkboxes in the provided context. Chris@0: * Chris@0: * @param {HTMLDocument|HTMLElement} context Chris@0: * A context where one would find checkboxes to summarize. Chris@0: * Chris@0: * @return {string} Chris@0: * A string with the summary. Chris@0: */ Chris@0: function checkboxesSummary(context) { Chris@0: const vals = []; Chris@17: const $checkboxes = $(context).find( Chris@17: 'input[type="checkbox"]:checked + label', Chris@17: ); Chris@0: const il = $checkboxes.length; Chris@0: for (let i = 0; i < il; i++) { Chris@0: vals.push($($checkboxes[i]).html()); Chris@0: } Chris@0: if (!vals.length) { Chris@0: vals.push(Drupal.t('Not restricted')); Chris@0: } Chris@0: return vals.join(', '); Chris@0: } Chris@0: Chris@17: $( Chris@17: '[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]', Chris@17: ).drupalSetSummary(checkboxesSummary); Chris@0: Chris@17: $( Chris@17: '[data-drupal-selector="edit-visibility-request-path"]', Chris@17: ).drupalSetSummary(context => { Chris@17: const $pages = $(context).find( Chris@17: 'textarea[name="visibility[request_path][pages]"]', Chris@17: ); Chris@0: if (!$pages.val()) { Chris@0: return Drupal.t('Not restricted'); Chris@0: } Chris@0: Chris@0: return Drupal.t('Restricted to certain pages'); Chris@0: }); Chris@0: }, Chris@0: }; Chris@0: Chris@0: /** Chris@0: * Move a block in the blocks table between regions via select list. Chris@0: * Chris@0: * This behavior is dependent on the tableDrag behavior, since it uses the Chris@0: * objects initialized in that behavior to update the row. Chris@0: * Chris@0: * @type {Drupal~behavior} Chris@0: * Chris@0: * @prop {Drupal~behaviorAttach} attach Chris@0: * Attaches the tableDrag behaviour for blocks in block administration. Chris@0: */ Chris@0: Drupal.behaviors.blockDrag = { Chris@0: attach(context, settings) { Chris@0: // tableDrag is required and we should be on the blocks admin page. Chris@17: if ( Chris@17: typeof Drupal.tableDrag === 'undefined' || Chris@17: typeof Drupal.tableDrag.blocks === 'undefined' Chris@17: ) { Chris@0: return; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Function to check empty regions and toggle classes based on this. Chris@0: * Chris@0: * @param {jQuery} table Chris@0: * The jQuery object representing the table to inspect. Chris@0: * @param {jQuery} rowObject Chris@0: * The jQuery object representing the table row. Chris@0: */ Chris@0: function checkEmptyRegions(table, rowObject) { Chris@17: table.find('tr.region-message').each(function() { Chris@0: const $this = $(this); Chris@0: // If the dragged row is in this region, but above the message row, Chris@0: // swap it down one space. Chris@0: if ($this.prev('tr').get(0) === rowObject.element) { Chris@0: // Prevent a recursion problem when using the keyboard to move rows Chris@0: // up. Chris@17: if ( Chris@17: rowObject.method !== 'keyboard' || Chris@17: rowObject.direction === 'down' Chris@17: ) { Chris@0: rowObject.swap('after', this); Chris@0: } Chris@0: } Chris@0: // This region has become empty. Chris@17: if ( Chris@17: $this.next('tr').is(':not(.draggable)') || Chris@17: $this.next('tr').length === 0 Chris@17: ) { Chris@0: $this.removeClass('region-populated').addClass('region-empty'); Chris@0: } Chris@0: // This region has become populated. Chris@0: else if ($this.is('.region-empty')) { Chris@0: $this.removeClass('region-empty').addClass('region-populated'); Chris@0: } Chris@0: }); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Function to update the last placed row with the correct classes. Chris@0: * Chris@0: * @param {jQuery} table Chris@0: * The jQuery object representing the table to inspect. Chris@0: * @param {jQuery} rowObject Chris@0: * The jQuery object representing the table row. Chris@0: */ Chris@0: function updateLastPlaced(table, rowObject) { Chris@0: // Remove the color-success class from new block if applicable. Chris@0: table.find('.color-success').removeClass('color-success'); Chris@0: Chris@0: const $rowObject = $(rowObject); Chris@0: if (!$rowObject.is('.drag-previous')) { Chris@0: table.find('.drag-previous').removeClass('drag-previous'); Chris@0: $rowObject.addClass('drag-previous'); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Update block weights in the given region. Chris@0: * Chris@0: * @param {jQuery} table Chris@0: * Table with draggable items. Chris@0: * @param {string} region Chris@0: * Machine name of region containing blocks to update. Chris@0: */ Chris@0: function updateBlockWeights(table, region) { Chris@0: // Calculate minimum weight. Chris@0: let weight = -Math.round(table.find('.draggable').length / 2); Chris@0: // Update the block weights. Chris@17: table Chris@17: .find(`.region-${region}-message`) Chris@17: .nextUntil('.region-title') Chris@17: .find('select.block-weight') Chris@17: .val( Chris@0: // Increment the weight before assigning it to prevent using the Chris@0: // absolute minimum available weight. This way we always have an Chris@0: // unused upper and lower bound, which makes manually setting the Chris@0: // weights easier for users who prefer to do it that way. Chris@17: () => ++weight, Chris@17: ); Chris@0: } Chris@0: Chris@0: const table = $('#blocks'); Chris@0: // Get the blocks tableDrag object. Chris@0: const tableDrag = Drupal.tableDrag.blocks; Chris@0: // Add a handler for when a row is swapped, update empty regions. Chris@17: tableDrag.row.prototype.onSwap = function(swappedRow) { Chris@0: checkEmptyRegions(table, this); Chris@0: updateLastPlaced(table, this); Chris@0: }; Chris@0: Chris@0: // Add a handler so when a row is dropped, update fields dropped into Chris@0: // new regions. Chris@17: tableDrag.onDrop = function() { Chris@0: const dragObject = this; Chris@0: const $rowElement = $(dragObject.rowObject.element); Chris@0: // Use "region-message" row instead of "region" row because Chris@0: // "region-{region_name}-message" is less prone to regexp match errors. Chris@0: const regionRow = $rowElement.prevAll('tr.region-message').get(0); Chris@17: const regionName = regionRow.className.replace( Chris@17: /([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, Chris@17: '$2', Chris@17: ); Chris@0: const regionField = $rowElement.find('select.block-region-select'); Chris@0: // Check whether the newly picked region is available for this block. Chris@0: if (regionField.find(`option[value=${regionName}]`).length === 0) { Chris@0: // If not, alert the user and keep the block in its old region Chris@0: // setting. Chris@0: window.alert(Drupal.t('The block cannot be placed in this region.')); Chris@0: // Simulate that there was a selected element change, so the row is Chris@0: // put back to from where the user tried to drag it. Chris@0: regionField.trigger('change'); Chris@0: } Chris@0: Chris@0: // Update region and weight fields if the region has been changed. Chris@0: if (!regionField.is(`.block-region-${regionName}`)) { Chris@0: const weightField = $rowElement.find('select.block-weight'); Chris@17: const oldRegionName = weightField[0].className.replace( Chris@17: /([^ ]+[ ]+)*block-weight-([^ ]+)([ ]+[^ ]+)*/, Chris@17: '$2', Chris@17: ); Chris@17: regionField Chris@17: .removeClass(`block-region-${oldRegionName}`) Chris@17: .addClass(`block-region-${regionName}`); Chris@17: weightField Chris@17: .removeClass(`block-weight-${oldRegionName}`) Chris@17: .addClass(`block-weight-${regionName}`); Chris@0: regionField.val(regionName); Chris@0: } Chris@0: Chris@0: updateBlockWeights(table, regionName); Chris@0: }; Chris@0: Chris@0: // Add the behavior to each region select list. Chris@17: $(context) Chris@17: .find('select.block-region-select') Chris@17: .once('block-region-select') Chris@17: .on('change', function(event) { Chris@0: // Make our new row and select field. Chris@0: const row = $(this).closest('tr'); Chris@0: const select = $(this); Chris@0: // Find the correct region and insert the row as the last in the Chris@0: // region. Chris@0: tableDrag.rowObject = new tableDrag.row(row[0]); Chris@17: const regionMessage = table.find( Chris@17: `.region-${select[0].value}-message`, Chris@17: ); Chris@17: const regionItems = regionMessage.nextUntil( Chris@17: '.region-message, .region-title', Chris@17: ); Chris@14: if (regionItems.length) { Chris@14: regionItems.last().after(row); Chris@0: } Chris@14: // We found that regionMessage is the last row. Chris@0: else { Chris@14: regionMessage.after(row); Chris@0: } Chris@0: updateBlockWeights(table, select[0].value); Chris@0: // Modify empty regions with added or removed fields. Chris@0: checkEmptyRegions(table, tableDrag.rowObject); Chris@0: // Update last placed block indication. Chris@0: updateLastPlaced(table, row); Chris@0: // Show unsaved changes warning. Chris@0: if (!tableDrag.changed) { Chris@17: $(Drupal.theme('tableDragChangedWarning')) Chris@17: .insertBefore(tableDrag.table) Chris@17: .hide() Chris@17: .fadeIn('slow'); Chris@0: tableDrag.changed = true; Chris@0: } Chris@0: // Remove focus from selectbox. Chris@0: select.trigger('blur'); Chris@0: }); Chris@0: }, Chris@0: }; Chris@17: })(jQuery, window, Drupal);