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