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