block.es6.js 9.3 KB

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