Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / field_ui / field_ui.es6.js
1 /**
2  * @file
3  * Attaches the behaviors for the Field UI module.
4  */
5
6 (function ($, Drupal, drupalSettings) {
7   /**
8    * @type {Drupal~behavior}
9    *
10    * @prop {Drupal~behaviorAttach} attach
11    *   Adds behaviors to the field storage add form.
12    */
13   Drupal.behaviors.fieldUIFieldStorageAddForm = {
14     attach(context) {
15       const $form = $(context).find('[data-drupal-selector="field-ui-field-storage-add-form"]').once('field_ui_add');
16       if ($form.length) {
17         // Add a few 'js-form-required' and 'form-required' css classes here.
18         // We can not use the Form API '#required' property because both label
19         // elements for "add new" and "re-use existing" can never be filled and
20         // submitted at the same time. The actual validation will happen
21         // server-side.
22         $form.find(
23           '.js-form-item-label label,' +
24           '.js-form-item-field-name label,' +
25           '.js-form-item-existing-storage-label label')
26           .addClass('js-form-required form-required');
27
28         const $newFieldType = $form.find('select[name="new_storage_type"]');
29         const $existingStorageName = $form.find('select[name="existing_storage_name"]');
30         const $existingStorageLabel = $form.find('input[name="existing_storage_label"]');
31
32         // When the user selects a new field type, clear the "existing field"
33         // selection.
34         $newFieldType.on('change', function () {
35           if ($(this).val() !== '') {
36             // Reset the "existing storage name" selection.
37             $existingStorageName.val('').trigger('change');
38           }
39         });
40
41         // When the user selects an existing storage name, clear the "new field
42         // type" selection and populate the 'existing_storage_label' element.
43         $existingStorageName.on('change', function () {
44           const value = $(this).val();
45           if (value !== '') {
46             // Reset the "new field type" selection.
47             $newFieldType.val('').trigger('change');
48
49             // Pre-populate the "existing storage label" element.
50             if (typeof drupalSettings.existingFieldLabels[value] !== 'undefined') {
51               $existingStorageLabel.val(drupalSettings.existingFieldLabels[value]);
52             }
53           }
54         });
55       }
56     },
57   };
58
59   /**
60    * Attaches the fieldUIOverview behavior.
61    *
62    * @type {Drupal~behavior}
63    *
64    * @prop {Drupal~behaviorAttach} attach
65    *   Attaches the fieldUIOverview behavior.
66    *
67    * @see Drupal.fieldUIOverview.attach
68    */
69   Drupal.behaviors.fieldUIDisplayOverview = {
70     attach(context, settings) {
71       $(context).find('table#field-display-overview').once('field-display-overview').each(function () {
72         Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIDisplayOverview);
73       });
74     },
75   };
76
77   /**
78    * Namespace for the field UI overview.
79    *
80    * @namespace
81    */
82   Drupal.fieldUIOverview = {
83
84     /**
85      * Attaches the fieldUIOverview behavior.
86      *
87      * @param {HTMLTableElement} table
88      *   The table element for the overview.
89      * @param {object} rowsData
90      *   The data of the rows in the table.
91      * @param {object} rowHandlers
92      *   Handlers to be added to the rows.
93      */
94     attach(table, rowsData, rowHandlers) {
95       const tableDrag = Drupal.tableDrag[table.id];
96
97       // Add custom tabledrag callbacks.
98       tableDrag.onDrop = this.onDrop;
99       tableDrag.row.prototype.onSwap = this.onSwap;
100
101       // Create row handlers.
102       $(table).find('tr.draggable').each(function () {
103         // Extract server-side data for the row.
104         const row = this;
105         if (row.id in rowsData) {
106           const data = rowsData[row.id];
107           data.tableDrag = tableDrag;
108
109           // Create the row handler, make it accessible from the DOM row
110           // element.
111           const rowHandler = new rowHandlers[data.rowHandler](row, data);
112           $(row).data('fieldUIRowHandler', rowHandler);
113         }
114       });
115     },
116
117     /**
118      * Event handler to be attached to form inputs triggering a region change.
119      */
120     onChange() {
121       const $trigger = $(this);
122       const $row = $trigger.closest('tr');
123       const rowHandler = $row.data('fieldUIRowHandler');
124
125       const refreshRows = {};
126       refreshRows[rowHandler.name] = $trigger.get(0);
127
128       // Handle region change.
129       const region = rowHandler.getRegion();
130       if (region !== rowHandler.region) {
131         // Remove parenting.
132         $row.find('select.js-field-parent').val('');
133         // Let the row handler deal with the region change.
134         $.extend(refreshRows, rowHandler.regionChange(region));
135         // Update the row region.
136         rowHandler.region = region;
137       }
138
139       // Ajax-update the rows.
140       Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
141     },
142
143     /**
144      * Lets row handlers react when a row is dropped into a new region.
145      */
146     onDrop() {
147       const dragObject = this;
148       const row = dragObject.rowObject.element;
149       const $row = $(row);
150       const rowHandler = $row.data('fieldUIRowHandler');
151       if (typeof rowHandler !== 'undefined') {
152         const regionRow = $row.prevAll('tr.region-message').get(0);
153         const region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
154
155         if (region !== rowHandler.region) {
156           // Let the row handler deal with the region change.
157           const refreshRows = rowHandler.regionChange(region);
158           // Update the row region.
159           rowHandler.region = region;
160           // Ajax-update the rows.
161           Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
162         }
163       }
164     },
165
166     /**
167      * Refreshes placeholder rows in empty regions while a row is being dragged.
168      *
169      * Copied from block.js.
170      *
171      * @param {HTMLElement} draggedRow
172      *   The tableDrag rowObject for the row being dragged.
173      */
174     onSwap(draggedRow) {
175       const rowObject = this;
176       $(rowObject.table).find('tr.region-message').each(function () {
177         const $this = $(this);
178         // If the dragged row is in this region, but above the message row, swap
179         // it down one space.
180         if ($this.prev('tr').get(0) === rowObject.group[rowObject.group.length - 1]) {
181           // Prevent a recursion problem when using the keyboard to move rows
182           // up.
183           if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
184             rowObject.swap('after', this);
185           }
186         }
187         // This region has become empty.
188         if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
189           $this.removeClass('region-populated').addClass('region-empty');
190         }
191         // This region has become populated.
192         else if ($this.is('.region-empty')) {
193           $this.removeClass('region-empty').addClass('region-populated');
194         }
195       });
196     },
197
198     /**
199      * Triggers Ajax refresh of selected rows.
200      *
201      * The 'format type' selects can trigger a series of changes in child rows.
202      * The #ajax behavior is therefore not attached directly to the selects, but
203      * triggered manually through a hidden #ajax 'Refresh' button.
204      *
205      * @param {object} rows
206      *   A hash object, whose keys are the names of the rows to refresh (they
207      *   will receive the 'ajax-new-content' effect on the server side), and
208      *   whose values are the DOM element in the row that should get an Ajax
209      *   throbber.
210      */
211     AJAXRefreshRows(rows) {
212       // Separate keys and values.
213       const rowNames = [];
214       const ajaxElements = [];
215       let rowName;
216       for (rowName in rows) {
217         if (rows.hasOwnProperty(rowName)) {
218           rowNames.push(rowName);
219           ajaxElements.push(rows[rowName]);
220         }
221       }
222
223       if (rowNames.length) {
224         // Add a throbber next each of the ajaxElements.
225         $(ajaxElements).after('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
226
227         // Fire the Ajax update.
228         $('input[name=refresh_rows]').val(rowNames.join(' '));
229         $('input[data-drupal-selector="edit-refresh"]').trigger('mousedown');
230
231         // Disabled elements do not appear in POST ajax data, so we mark the
232         // elements disabled only after firing the request.
233         $(ajaxElements).prop('disabled', true);
234       }
235     },
236   };
237
238   /**
239    * Row handlers for the 'Manage display' screen.
240    *
241    * @namespace
242    */
243   Drupal.fieldUIDisplayOverview = {};
244
245   /**
246    * Constructor for a 'field' row handler.
247    *
248    * This handler is used for both fields and 'extra fields' rows.
249    *
250    * @constructor
251    *
252    * @param {HTMLTableRowElement} row
253    *   The row DOM element.
254    * @param {object} data
255    *   Additional data to be populated in the constructed object.
256    *
257    * @return {Drupal.fieldUIDisplayOverview.field}
258    *   The field row handler constructed.
259    */
260   Drupal.fieldUIDisplayOverview.field = function (row, data) {
261     this.row = row;
262     this.name = data.name;
263     this.region = data.region;
264     this.tableDrag = data.tableDrag;
265     this.defaultPlugin = data.defaultPlugin;
266
267     // Attach change listener to the 'plugin type' select.
268     this.$pluginSelect = $(row).find('.field-plugin-type');
269     this.$pluginSelect.on('change', Drupal.fieldUIOverview.onChange);
270
271     // Attach change listener to the 'region' select.
272     this.$regionSelect = $(row).find('select.field-region');
273     this.$regionSelect.on('change', Drupal.fieldUIOverview.onChange);
274
275     return this;
276   };
277
278   Drupal.fieldUIDisplayOverview.field.prototype = {
279
280     /**
281      * Returns the region corresponding to the current form values of the row.
282      *
283      * @return {string}
284      *   Either 'hidden' or 'content'.
285      */
286     getRegion() {
287       return this.$regionSelect.val();
288     },
289
290     /**
291      * Reacts to a row being changed regions.
292      *
293      * This function is called when the row is moved to a different region, as
294      * a
295      * result of either :
296      * - a drag-and-drop action (the row's form elements then probably need to
297      * be updated accordingly)
298      * - user input in one of the form elements watched by the
299      *   {@link Drupal.fieldUIOverview.onChange} change listener.
300      *
301      * @param {string} region
302      *   The name of the new region for the row.
303      *
304      * @return {object}
305      *   A hash object indicating which rows should be Ajax-updated as a result
306      *   of the change, in the format expected by
307      *   {@link Drupal.fieldUIOverview.AJAXRefreshRows}.
308      */
309     regionChange(region) {
310       // Replace dashes with underscores.
311       region = region.replace(/-/g, '_');
312
313       // Set the region of the select list.
314       this.$regionSelect.val(region);
315
316       // Restore the formatter back to the default formatter. Pseudo-fields
317       // do not have default formatters, we just return to 'visible' for
318       // those.
319       const value = (typeof this.defaultPlugin !== 'undefined') ? this.defaultPlugin : this.$pluginSelect.find('option').val();
320
321       if (typeof value !== 'undefined') {
322         this.$pluginSelect.val(value);
323       }
324
325       const refreshRows = {};
326       refreshRows[this.name] = this.$pluginSelect.get(0);
327
328       return refreshRows;
329     },
330   };
331 }(jQuery, Drupal, drupalSettings));