field_ui.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. /**
  2. * @file
  3. * Attaches the behaviors for the Field UI module.
  4. */
  5. (function($) {
  6. Drupal.behaviors.fieldUIFieldOverview = {
  7. attach: function (context, settings) {
  8. $('table#field-overview', context).once('field-overview', function () {
  9. Drupal.fieldUIFieldOverview.attachUpdateSelects(this, settings);
  10. });
  11. }
  12. };
  13. Drupal.fieldUIFieldOverview = {
  14. /**
  15. * Implements dependent select dropdowns on the 'Manage fields' screen.
  16. */
  17. attachUpdateSelects: function(table, settings) {
  18. var widgetTypes = settings.fieldWidgetTypes;
  19. var fields = settings.fields;
  20. // Store the default text of widget selects.
  21. $('.widget-type-select', table).each(function () {
  22. this.initialValue = this.options[0].text;
  23. });
  24. // 'Field type' select updates its 'Widget' select.
  25. $('.field-type-select', table).each(function () {
  26. this.targetSelect = $('.widget-type-select', $(this).closest('tr'));
  27. $(this).bind('change keyup', function () {
  28. var selectedFieldType = this.options[this.selectedIndex].value;
  29. var options = (selectedFieldType in widgetTypes ? widgetTypes[selectedFieldType] : []);
  30. this.targetSelect.fieldUIPopulateOptions(options);
  31. });
  32. // Trigger change on initial pageload to get the right widget options
  33. // when field type comes pre-selected (on failed validation).
  34. $(this).trigger('change', false);
  35. });
  36. // 'Existing field' select updates its 'Widget' select and 'Label' textfield.
  37. $('.field-select', table).each(function () {
  38. this.targetSelect = $('.widget-type-select', $(this).closest('tr'));
  39. this.targetTextfield = $('.label-textfield', $(this).closest('tr'));
  40. this.targetTextfield
  41. .data('field_ui_edited', false)
  42. .bind('keyup', function (e) {
  43. $(this).data('field_ui_edited', $(this).val() != '');
  44. });
  45. $(this).bind('change keyup', function (e, updateText) {
  46. var updateText = (typeof updateText == 'undefined' ? true : updateText);
  47. var selectedField = this.options[this.selectedIndex].value;
  48. var selectedFieldType = (selectedField in fields ? fields[selectedField].type : null);
  49. var selectedFieldWidget = (selectedField in fields ? fields[selectedField].widget : null);
  50. var options = (selectedFieldType && (selectedFieldType in widgetTypes) ? widgetTypes[selectedFieldType] : []);
  51. this.targetSelect.fieldUIPopulateOptions(options, selectedFieldWidget);
  52. // Only overwrite the "Label" input if it has not been manually
  53. // changed, or if it is empty.
  54. if (updateText && !this.targetTextfield.data('field_ui_edited')) {
  55. this.targetTextfield.val(selectedField in fields ? fields[selectedField].label : '');
  56. }
  57. });
  58. // Trigger change on initial pageload to get the right widget options
  59. // and label when field type comes pre-selected (on failed validation).
  60. $(this).trigger('change', false);
  61. });
  62. }
  63. };
  64. /**
  65. * Populates options in a select input.
  66. */
  67. jQuery.fn.fieldUIPopulateOptions = function (options, selected) {
  68. return this.each(function () {
  69. var disabled = false;
  70. if (options.length == 0) {
  71. options = [this.initialValue];
  72. disabled = true;
  73. }
  74. // If possible, keep the same widget selected when changing field type.
  75. // This is based on textual value, since the internal value might be
  76. // different (options_buttons vs. node_reference_buttons).
  77. var previousSelectedText = this.options[this.selectedIndex].text;
  78. var html = '';
  79. jQuery.each(options, function (value, text) {
  80. // Figure out which value should be selected. The 'selected' param
  81. // takes precedence.
  82. var is_selected = ((typeof selected != 'undefined' && value == selected) || (typeof selected == 'undefined' && text == previousSelectedText));
  83. html += '<option value="' + value + '"' + (is_selected ? ' selected="selected"' : '') + '>' + text + '</option>';
  84. });
  85. $(this).html(html).attr('disabled', disabled ? 'disabled' : false);
  86. });
  87. };
  88. Drupal.behaviors.fieldUIDisplayOverview = {
  89. attach: function (context, settings) {
  90. $('table#field-display-overview', context).once('field-display-overview', function() {
  91. Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIDisplayOverview);
  92. });
  93. }
  94. };
  95. Drupal.fieldUIOverview = {
  96. /**
  97. * Attaches the fieldUIOverview behavior.
  98. */
  99. attach: function (table, rowsData, rowHandlers) {
  100. var tableDrag = Drupal.tableDrag[table.id];
  101. // Add custom tabledrag callbacks.
  102. tableDrag.onDrop = this.onDrop;
  103. tableDrag.row.prototype.onSwap = this.onSwap;
  104. // Create row handlers.
  105. $('tr.draggable', table).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. // Create the row handler, make it accessible from the DOM row element.
  112. var rowHandler = new rowHandlers[data.rowHandler](row, data);
  113. $(row).data('fieldUIRowHandler', rowHandler);
  114. }
  115. });
  116. },
  117. /**
  118. * Event handler to be attached to form inputs triggering a region change.
  119. */
  120. onChange: function () {
  121. var $trigger = $(this);
  122. var row = $trigger.closest('tr').get(0);
  123. var rowHandler = $(row).data('fieldUIRowHandler');
  124. var refreshRows = {};
  125. refreshRows[rowHandler.name] = $trigger.get(0);
  126. // Handle region change.
  127. var region = rowHandler.getRegion();
  128. if (region != rowHandler.region) {
  129. // Remove parenting.
  130. $('select.field-parent', row).val('');
  131. // Let the row handler deal with the region change.
  132. $.extend(refreshRows, rowHandler.regionChange(region));
  133. // Update the row region.
  134. rowHandler.region = region;
  135. }
  136. // Ajax-update the rows.
  137. Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
  138. },
  139. /**
  140. * Lets row handlers react when a row is dropped into a new region.
  141. */
  142. onDrop: function () {
  143. var dragObject = this;
  144. var row = dragObject.rowObject.element;
  145. var rowHandler = $(row).data('fieldUIRowHandler');
  146. if (rowHandler !== undefined) {
  147. var regionRow = $(row).prevAll('tr.region-message').get(0);
  148. var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
  149. if (region != rowHandler.region) {
  150. // Let the row handler deal with the region change.
  151. refreshRows = rowHandler.regionChange(region);
  152. // Update the row region.
  153. rowHandler.region = region;
  154. // Ajax-update the rows.
  155. Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
  156. }
  157. }
  158. },
  159. /**
  160. * Refreshes placeholder rows in empty regions while a row is being dragged.
  161. *
  162. * Copied from block.js.
  163. *
  164. * @param table
  165. * The table DOM element.
  166. * @param rowObject
  167. * The tableDrag rowObject for the row being dragged.
  168. */
  169. onSwap: function (draggedRow) {
  170. var rowObject = this;
  171. $('tr.region-message', rowObject.table).each(function () {
  172. // If the dragged row is in this region, but above the message row, swap
  173. // it down one space.
  174. if ($(this).prev('tr').get(0) == rowObject.group[rowObject.group.length - 1]) {
  175. // Prevent a recursion problem when using the keyboard to move rows up.
  176. if ((rowObject.method != 'keyboard' || rowObject.direction == 'down')) {
  177. rowObject.swap('after', this);
  178. }
  179. }
  180. // This region has become empty.
  181. if ($(this).next('tr').is(':not(.draggable)') || $(this).next('tr').length == 0) {
  182. $(this).removeClass('region-populated').addClass('region-empty');
  183. }
  184. // This region has become populated.
  185. else if ($(this).is('.region-empty')) {
  186. $(this).removeClass('region-empty').addClass('region-populated');
  187. }
  188. });
  189. },
  190. /**
  191. * Triggers Ajax refresh of selected rows.
  192. *
  193. * The 'format type' selects can trigger a series of changes in child rows.
  194. * The #ajax behavior is therefore not attached directly to the selects, but
  195. * triggered manually through a hidden #ajax 'Refresh' button.
  196. *
  197. * @param rows
  198. * A hash object, whose keys are the names of the rows to refresh (they
  199. * will receive the 'ajax-new-content' effect on the server side), and
  200. * whose values are the DOM element in the row that should get an Ajax
  201. * throbber.
  202. */
  203. AJAXRefreshRows: function (rows) {
  204. // Separate keys and values.
  205. var rowNames = [];
  206. var ajaxElements = [];
  207. $.each(rows, function (rowName, ajaxElement) {
  208. rowNames.push(rowName);
  209. ajaxElements.push(ajaxElement);
  210. });
  211. if (rowNames.length) {
  212. // Add a throbber next each of the ajaxElements.
  213. var $throbber = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
  214. $(ajaxElements)
  215. .addClass('progress-disabled')
  216. .after($throbber);
  217. // Fire the Ajax update.
  218. $('input[name=refresh_rows]').val(rowNames.join(' '));
  219. $('input#edit-refresh').mousedown();
  220. // Disabled elements do not appear in POST ajax data, so we mark the
  221. // elements disabled only after firing the request.
  222. $(ajaxElements).attr('disabled', true);
  223. }
  224. }
  225. };
  226. /**
  227. * Row handlers for the 'Manage display' screen.
  228. */
  229. Drupal.fieldUIDisplayOverview = {};
  230. /**
  231. * Constructor for a 'field' row handler.
  232. *
  233. * This handler is used for both fields and 'extra fields' rows.
  234. *
  235. * @param row
  236. * The row DOM element.
  237. * @param data
  238. * Additional data to be populated in the constructed object.
  239. */
  240. Drupal.fieldUIDisplayOverview.field = function (row, data) {
  241. this.row = row;
  242. this.name = data.name;
  243. this.region = data.region;
  244. this.tableDrag = data.tableDrag;
  245. // Attach change listener to the 'formatter type' select.
  246. this.$formatSelect = $('select.field-formatter-type', row);
  247. this.$formatSelect.change(Drupal.fieldUIOverview.onChange);
  248. return this;
  249. };
  250. Drupal.fieldUIDisplayOverview.field.prototype = {
  251. /**
  252. * Returns the region corresponding to the current form values of the row.
  253. */
  254. getRegion: function () {
  255. return (this.$formatSelect.val() == 'hidden') ? 'hidden' : 'visible';
  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 a
  261. * result of either :
  262. * - a drag-and-drop action (the row's form elements then probably need to be
  263. * updated accordingly)
  264. * - user input in one of the form elements watched by the
  265. * Drupal.fieldUIOverview.onChange change listener.
  266. *
  267. * @param region
  268. * The name of the new region for the row.
  269. * @return
  270. * A hash object indicating which rows should be Ajax-updated as a result
  271. * of the change, in the format expected by
  272. * Drupal.displayOverview.AJAXRefreshRows().
  273. */
  274. regionChange: function (region) {
  275. // When triggered by a row drag, the 'format' select needs to be adjusted
  276. // to the new region.
  277. var currentValue = this.$formatSelect.val();
  278. switch (region) {
  279. case 'visible':
  280. if (currentValue == 'hidden') {
  281. // Restore the formatter back to the default formatter. Pseudo-fields do
  282. // not have default formatters, we just return to 'visible' for those.
  283. var value = (this.defaultFormatter != undefined) ? this.defaultFormatter : 'visible';
  284. }
  285. break;
  286. default:
  287. var value = 'hidden';
  288. break;
  289. }
  290. if (value != undefined) {
  291. this.$formatSelect.val(value);
  292. }
  293. var refreshRows = {};
  294. refreshRows[this.name] = this.$formatSelect.get(0);
  295. return refreshRows;
  296. }
  297. };
  298. })(jQuery);