block.es6.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /**
  2. * @file
  3. * Block behaviors.
  4. */
  5. (function ($, window, Drupal) {
  6. /**
  7. * Provide the summary information for the block settings vertical tabs.
  8. *
  9. * @type {Drupal~behavior}
  10. *
  11. * @prop {Drupal~behaviorAttach} attach
  12. * Attaches the behavior for the block settings summaries.
  13. */
  14. Drupal.behaviors.blockSettingsSummary = {
  15. attach() {
  16. // The drupalSetSummary method required for this behavior is not available
  17. // on the Blocks administration page, so we need to make sure this
  18. // behavior is processed only if drupalSetSummary is defined.
  19. if (typeof $.fn.drupalSetSummary === 'undefined') {
  20. return;
  21. }
  22. /**
  23. * Create a summary for checkboxes in the provided context.
  24. *
  25. * @param {HTMLDocument|HTMLElement} context
  26. * A context where one would find checkboxes to summarize.
  27. *
  28. * @return {string}
  29. * A string with the summary.
  30. */
  31. function checkboxesSummary(context) {
  32. const vals = [];
  33. const $checkboxes = $(context).find('input[type="checkbox"]:checked + label');
  34. const il = $checkboxes.length;
  35. for (let i = 0; i < il; i++) {
  36. vals.push($($checkboxes[i]).html());
  37. }
  38. if (!vals.length) {
  39. vals.push(Drupal.t('Not restricted'));
  40. }
  41. return vals.join(', ');
  42. }
  43. $('[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]').drupalSetSummary(checkboxesSummary);
  44. $('[data-drupal-selector="edit-visibility-request-path"]').drupalSetSummary((context) => {
  45. const $pages = $(context).find('textarea[name="visibility[request_path][pages]"]');
  46. if (!$pages.val()) {
  47. return Drupal.t('Not restricted');
  48. }
  49. return Drupal.t('Restricted to certain pages');
  50. });
  51. },
  52. };
  53. /**
  54. * Move a block in the blocks table between regions via select list.
  55. *
  56. * This behavior is dependent on the tableDrag behavior, since it uses the
  57. * objects initialized in that behavior to update the row.
  58. *
  59. * @type {Drupal~behavior}
  60. *
  61. * @prop {Drupal~behaviorAttach} attach
  62. * Attaches the tableDrag behaviour for blocks in block administration.
  63. */
  64. Drupal.behaviors.blockDrag = {
  65. attach(context, settings) {
  66. // tableDrag is required and we should be on the blocks admin page.
  67. if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag.blocks === 'undefined') {
  68. return;
  69. }
  70. /**
  71. * Function to check empty regions and toggle classes based on this.
  72. *
  73. * @param {jQuery} table
  74. * The jQuery object representing the table to inspect.
  75. * @param {jQuery} rowObject
  76. * The jQuery object representing the table row.
  77. */
  78. function checkEmptyRegions(table, rowObject) {
  79. table.find('tr.region-message').each(function () {
  80. const $this = $(this);
  81. // If the dragged row is in this region, but above the message row,
  82. // swap it down one space.
  83. if ($this.prev('tr').get(0) === rowObject.element) {
  84. // Prevent a recursion problem when using the keyboard to move rows
  85. // up.
  86. if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
  87. rowObject.swap('after', this);
  88. }
  89. }
  90. // This region has become empty.
  91. if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
  92. $this.removeClass('region-populated').addClass('region-empty');
  93. }
  94. // This region has become populated.
  95. else if ($this.is('.region-empty')) {
  96. $this.removeClass('region-empty').addClass('region-populated');
  97. }
  98. });
  99. }
  100. /**
  101. * Function to update the last placed row with the correct classes.
  102. *
  103. * @param {jQuery} table
  104. * The jQuery object representing the table to inspect.
  105. * @param {jQuery} rowObject
  106. * The jQuery object representing the table row.
  107. */
  108. function updateLastPlaced(table, rowObject) {
  109. // Remove the color-success class from new block if applicable.
  110. table.find('.color-success').removeClass('color-success');
  111. const $rowObject = $(rowObject);
  112. if (!$rowObject.is('.drag-previous')) {
  113. table.find('.drag-previous').removeClass('drag-previous');
  114. $rowObject.addClass('drag-previous');
  115. }
  116. }
  117. /**
  118. * Update block weights in the given region.
  119. *
  120. * @param {jQuery} table
  121. * Table with draggable items.
  122. * @param {string} region
  123. * Machine name of region containing blocks to update.
  124. */
  125. function updateBlockWeights(table, region) {
  126. // Calculate minimum weight.
  127. let weight = -Math.round(table.find('.draggable').length / 2);
  128. // Update the block weights.
  129. table.find(`.region-${region}-message`).nextUntil('.region-title')
  130. .find('select.block-weight').val(() =>
  131. // Increment the weight before assigning it to prevent using the
  132. // absolute minimum available weight. This way we always have an
  133. // unused upper and lower bound, which makes manually setting the
  134. // weights easier for users who prefer to do it that way.
  135. ++weight);
  136. }
  137. const table = $('#blocks');
  138. // Get the blocks tableDrag object.
  139. const tableDrag = Drupal.tableDrag.blocks;
  140. // Add a handler for when a row is swapped, update empty regions.
  141. tableDrag.row.prototype.onSwap = function (swappedRow) {
  142. checkEmptyRegions(table, this);
  143. updateLastPlaced(table, this);
  144. };
  145. // Add a handler so when a row is dropped, update fields dropped into
  146. // new regions.
  147. tableDrag.onDrop = function () {
  148. const dragObject = this;
  149. const $rowElement = $(dragObject.rowObject.element);
  150. // Use "region-message" row instead of "region" row because
  151. // "region-{region_name}-message" is less prone to regexp match errors.
  152. const regionRow = $rowElement.prevAll('tr.region-message').get(0);
  153. const regionName = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
  154. const regionField = $rowElement.find('select.block-region-select');
  155. // Check whether the newly picked region is available for this block.
  156. if (regionField.find(`option[value=${regionName}]`).length === 0) {
  157. // If not, alert the user and keep the block in its old region
  158. // setting.
  159. window.alert(Drupal.t('The block cannot be placed in this region.'));
  160. // Simulate that there was a selected element change, so the row is
  161. // put back to from where the user tried to drag it.
  162. regionField.trigger('change');
  163. }
  164. // Update region and weight fields if the region has been changed.
  165. if (!regionField.is(`.block-region-${regionName}`)) {
  166. const weightField = $rowElement.find('select.block-weight');
  167. const oldRegionName = weightField[0].className.replace(/([^ ]+[ ]+)*block-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
  168. regionField.removeClass(`block-region-${oldRegionName}`).addClass(`block-region-${regionName}`);
  169. weightField.removeClass(`block-weight-${oldRegionName}`).addClass(`block-weight-${regionName}`);
  170. regionField.val(regionName);
  171. }
  172. updateBlockWeights(table, regionName);
  173. };
  174. // Add the behavior to each region select list.
  175. $(context).find('select.block-region-select').once('block-region-select')
  176. .on('change', function (event) {
  177. // Make our new row and select field.
  178. const row = $(this).closest('tr');
  179. const select = $(this);
  180. // Find the correct region and insert the row as the last in the
  181. // region.
  182. tableDrag.rowObject = new tableDrag.row(row[0]);
  183. const regionMessage = table.find(`.region-${select[0].value}-message`);
  184. const regionItems = regionMessage.nextUntil('.region-message, .region-title');
  185. if (regionItems.length) {
  186. regionItems.last().after(row);
  187. }
  188. // We found that regionMessage is the last row.
  189. else {
  190. regionMessage.after(row);
  191. }
  192. updateBlockWeights(table, select[0].value);
  193. // Modify empty regions with added or removed fields.
  194. checkEmptyRegions(table, tableDrag.rowObject);
  195. // Update last placed block indication.
  196. updateLastPlaced(table, row);
  197. // Show unsaved changes warning.
  198. if (!tableDrag.changed) {
  199. $(Drupal.theme('tableDragChangedWarning')).insertBefore(tableDrag.table).hide().fadeIn('slow');
  200. tableDrag.changed = true;
  201. }
  202. // Remove focus from selectbox.
  203. select.trigger('blur');
  204. });
  205. },
  206. };
  207. }(jQuery, window, Drupal));