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