3 * Attaches the behaviors for the Field UI module.
6 (function ($, Drupal, drupalSettings) {
11 * @type {Drupal~behavior}
13 * @prop {Drupal~behaviorAttach} attach
14 * Adds behaviors to the field storage add form.
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');
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
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');
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"]');
35 // When the user selects a new field type, clear the "existing field"
37 $newFieldType.on('change', function () {
38 if ($(this).val() !== '') {
39 // Reset the "existing storage name" selection.
40 $existingStorageName.val('').trigger('change');
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();
49 // Reset the "new field type" selection.
50 $newFieldType.val('').trigger('change');
52 // Pre-populate the "existing storage label" element.
53 if (typeof drupalSettings.existingFieldLabels[value] !== 'undefined') {
54 $existingStorageLabel.val(drupalSettings.existingFieldLabels[value]);
63 * Attaches the fieldUIOverview behavior.
65 * @type {Drupal~behavior}
67 * @prop {Drupal~behaviorAttach} attach
68 * Attaches the fieldUIOverview behavior.
70 * @see Drupal.fieldUIOverview.attach
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);
81 * Namespace for the field UI overview.
85 Drupal.fieldUIOverview = {
88 * Attaches the fieldUIOverview behavior.
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.
97 attach: function (table, rowsData, rowHandlers) {
98 var tableDrag = Drupal.tableDrag[table.id];
100 // Add custom tabledrag callbacks.
101 tableDrag.onDrop = this.onDrop;
102 tableDrag.row.prototype.onSwap = this.onSwap;
104 // Create row handlers.
105 $(table).find('tr.draggable').each(function () {
106 // Extract server-side data for the row.
108 if (row.id in rowsData) {
109 var data = rowsData[row.id];
110 data.tableDrag = tableDrag;
112 // Create the row handler, make it accessible from the DOM row
114 var rowHandler = new rowHandlers[data.rowHandler](row, data);
115 $(row).data('fieldUIRowHandler', rowHandler);
121 * Event handler to be attached to form inputs triggering a region change.
123 onChange: function () {
124 var $trigger = $(this);
125 var $row = $trigger.closest('tr');
126 var rowHandler = $row.data('fieldUIRowHandler');
128 var refreshRows = {};
129 refreshRows[rowHandler.name] = $trigger.get(0);
131 // Handle region change.
132 var region = rowHandler.getRegion();
133 if (region !== rowHandler.region) {
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;
142 // Ajax-update the rows.
143 Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
147 * Lets row handlers react when a row is dropped into a new region.
149 onDrop: function () {
150 var dragObject = this;
151 var row = dragObject.rowObject.element;
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');
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);
170 * Refreshes placeholder rows in empty regions while a row is being dragged.
172 * Copied from block.js.
174 * @param {HTMLElement} draggedRow
175 * The tableDrag rowObject for the row being dragged.
177 onSwap: function (draggedRow) {
178 var rowObject = this;
179 $(rowObject.table).find('tr.region-message').each(function () {
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
186 if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
187 rowObject.swap('after', this);
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');
194 // This region has become populated.
195 else if ($this.is('.region-empty')) {
196 $this.removeClass('region-empty').addClass('region-populated');
202 * Triggers Ajax refresh of selected rows.
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.
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
214 AJAXRefreshRows: function (rows) {
215 // Separate keys and values.
217 var ajaxElements = [];
219 for (rowName in rows) {
220 if (rows.hasOwnProperty(rowName)) {
221 rowNames.push(rowName);
222 ajaxElements.push(rows[rowName]);
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"> </div></div>');
230 // Fire the Ajax update.
231 $('input[name=refresh_rows]').val(rowNames.join(' '));
232 $('input[data-drupal-selector="edit-refresh"]').trigger('mousedown');
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);
242 * Row handlers for the 'Manage display' screen.
246 Drupal.fieldUIDisplayOverview = {};
249 * Constructor for a 'field' row handler.
251 * This handler is used for both fields and 'extra fields' rows.
255 * @param {HTMLTableRowElement} row
256 * The row DOM element.
257 * @param {object} data
258 * Additional data to be populated in the constructed object.
260 * @return {Drupal.fieldUIDisplayOverview.field}
261 * The field row handler constructed.
263 Drupal.fieldUIDisplayOverview.field = function (row, data) {
265 this.name = data.name;
266 this.region = data.region;
267 this.tableDrag = data.tableDrag;
268 this.defaultPlugin = data.defaultPlugin;
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);
274 // Attach change listener to the 'region' select.
275 this.$regionSelect = $(row).find('select.field-region');
276 this.$regionSelect.on('change', Drupal.fieldUIOverview.onChange);
281 Drupal.fieldUIDisplayOverview.field.prototype = {
284 * Returns the region corresponding to the current form values of the row.
287 * Either 'hidden' or 'content'.
289 getRegion: function () {
290 return this.$regionSelect.val();
294 * Reacts to a row being changed regions.
296 * This function is called when the row is moved to a different region, as
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.
304 * @param {string} region
305 * The name of the new region for the row.
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}.
312 regionChange: function (region) {
313 // Replace dashes with underscores.
314 region = region.replace(/-/g, '_');
316 // Set the region of the select list.
317 this.$regionSelect.val(region);
319 // Restore the formatter back to the default formatter. Pseudo-fields
320 // do not have default formatters, we just return to 'visible' for
322 var value = (typeof this.defaultPlugin !== 'undefined') ? this.defaultPlugin : this.$pluginSelect.find('option').val();
324 if (typeof value !== 'undefined') {
325 this.$pluginSelect.val(value);
328 var refreshRows = {};
329 refreshRows[this.name] = this.$pluginSelect.get(0);
335 })(jQuery, Drupal, drupalSettings);