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