field_ui.es6.js 12 KB

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