field_ui.es6.js 11 KB

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