formEditor.es6.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. /**
  2. * @file
  3. * Form-based in-place editor. Works for any field type.
  4. */
  5. (function ($, Drupal, _) {
  6. /**
  7. * @constructor
  8. *
  9. * @augments Drupal.quickedit.EditorView
  10. */
  11. Drupal.quickedit.editors.form = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.form# */{
  12. /**
  13. * Tracks form container DOM element that is used while in-place editing.
  14. *
  15. * @type {jQuery}
  16. */
  17. $formContainer: null,
  18. /**
  19. * Holds the {@link Drupal.Ajax} object.
  20. *
  21. * @type {Drupal.Ajax}
  22. */
  23. formSaveAjax: null,
  24. /**
  25. * @inheritdoc
  26. *
  27. * @param {object} fieldModel
  28. * The field model that holds the state.
  29. * @param {string} state
  30. * The state to change to.
  31. */
  32. stateChange(fieldModel, state) {
  33. const from = fieldModel.previous('state');
  34. const to = state;
  35. switch (to) {
  36. case 'inactive':
  37. break;
  38. case 'candidate':
  39. if (from !== 'inactive') {
  40. this.removeForm();
  41. }
  42. break;
  43. case 'highlighted':
  44. break;
  45. case 'activating':
  46. // If coming from an invalid state, then the form is already loaded.
  47. if (from !== 'invalid') {
  48. this.loadForm();
  49. }
  50. break;
  51. case 'active':
  52. break;
  53. case 'changed':
  54. break;
  55. case 'saving':
  56. this.save();
  57. break;
  58. case 'saved':
  59. break;
  60. case 'invalid':
  61. this.showValidationErrors();
  62. break;
  63. }
  64. },
  65. /**
  66. * @inheritdoc
  67. *
  68. * @return {object}
  69. * A settings object for the quick edit UI.
  70. */
  71. getQuickEditUISettings() {
  72. return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true };
  73. },
  74. /**
  75. * Loads the form for this field, displays it on top of the actual field.
  76. */
  77. loadForm() {
  78. const fieldModel = this.fieldModel;
  79. // Generate a DOM-compatible ID for the form container DOM element.
  80. const id = `quickedit-form-for-${fieldModel.id.replace(/[/[\]]/g, '_')}`;
  81. // Render form container.
  82. const $formContainer = $(Drupal.theme('quickeditFormContainer', {
  83. id,
  84. loadingMsg: Drupal.t('Loading…'),
  85. }));
  86. this.$formContainer = $formContainer;
  87. $formContainer
  88. .find('.quickedit-form')
  89. .addClass('quickedit-editable quickedit-highlighted quickedit-editing')
  90. .attr('role', 'dialog');
  91. // Insert form container in DOM.
  92. if (this.$el.css('display') === 'inline') {
  93. $formContainer.prependTo(this.$el.offsetParent());
  94. // Position the form container to render on top of the field's element.
  95. const pos = this.$el.position();
  96. $formContainer.css('left', pos.left).css('top', pos.top);
  97. }
  98. else {
  99. $formContainer.insertBefore(this.$el);
  100. }
  101. // Load form, insert it into the form container and attach event handlers.
  102. const formOptions = {
  103. fieldID: fieldModel.get('fieldID'),
  104. $el: this.$el,
  105. nocssjs: false,
  106. // Reset an existing entry for this entity in the PrivateTempStore (if
  107. // any) when loading the field. Logically speaking, this should happen
  108. // in a separate request because this is an entity-level operation, not
  109. // a field-level operation. But that would require an additional
  110. // request, that might not even be necessary: it is only when a user
  111. // loads a first changed field for an entity that this needs to happen:
  112. // precisely now!
  113. reset: !fieldModel.get('entity').get('inTempStore'),
  114. };
  115. Drupal.quickedit.util.form.load(formOptions, (form, ajax) => {
  116. Drupal.AjaxCommands.prototype.insert(ajax, {
  117. data: form,
  118. selector: `#${id} .placeholder`,
  119. });
  120. $formContainer
  121. .on('formUpdated.quickedit', ':input', (event) => {
  122. const state = fieldModel.get('state');
  123. // If the form is in an invalid state, it will persist on the page.
  124. // Set the field to activating so that the user can correct the
  125. // invalid value.
  126. if (state === 'invalid') {
  127. fieldModel.set('state', 'activating');
  128. }
  129. // Otherwise assume that the fieldModel is in a candidate state and
  130. // set it to changed on formUpdate.
  131. else {
  132. fieldModel.set('state', 'changed');
  133. }
  134. })
  135. .on('keypress.quickedit', 'input', (event) => {
  136. if (event.keyCode === 13) {
  137. return false;
  138. }
  139. });
  140. // The in-place editor has loaded; change state to 'active'.
  141. fieldModel.set('state', 'active');
  142. });
  143. },
  144. /**
  145. * Removes the form for this field, detaches behaviors and event handlers.
  146. */
  147. removeForm() {
  148. if (this.$formContainer === null) {
  149. return;
  150. }
  151. delete this.formSaveAjax;
  152. // Allow form widgets to detach properly.
  153. Drupal.detachBehaviors(this.$formContainer.get(0), null, 'unload');
  154. this.$formContainer
  155. .off('change.quickedit', ':input')
  156. .off('keypress.quickedit', 'input')
  157. .remove();
  158. this.$formContainer = null;
  159. },
  160. /**
  161. * @inheritdoc
  162. */
  163. save() {
  164. const $formContainer = this.$formContainer;
  165. const $submit = $formContainer.find('.quickedit-form-submit');
  166. const editorModel = this.model;
  167. const fieldModel = this.fieldModel;
  168. function cleanUpAjax() {
  169. Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax);
  170. formSaveAjax = null;
  171. }
  172. // Create an AJAX object for the form associated with the field.
  173. let formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving({
  174. nocssjs: false,
  175. other_view_modes: fieldModel.findOtherViewModes(),
  176. }, $submit);
  177. // Successfully saved.
  178. formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) {
  179. cleanUpAjax();
  180. // First, transition the state to 'saved'.
  181. fieldModel.set('state', 'saved');
  182. // Second, set the 'htmlForOtherViewModes' attribute, so that when this
  183. // field is rerendered, the change can be propagated to other instances
  184. // of this field, which may be displayed in different view modes.
  185. fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
  186. // Finally, set the 'html' attribute on the field model. This will cause
  187. // the field to be rerendered.
  188. _.defer(() => {
  189. fieldModel.set('html', response.data);
  190. });
  191. };
  192. // Unsuccessfully saved; validation errors.
  193. formSaveAjax.commands.quickeditFieldFormValidationErrors = function (ajax, response, status) {
  194. editorModel.set('validationErrors', response.data);
  195. fieldModel.set('state', 'invalid');
  196. };
  197. // The quickeditFieldForm AJAX command is called upon attempting to save
  198. // the form; Form API will mark which form items have errors, if any. This
  199. // command is invoked only if validation errors exist and then it runs
  200. // before editFieldFormValidationErrors().
  201. formSaveAjax.commands.quickeditFieldForm = function (ajax, response, status) {
  202. Drupal.AjaxCommands.prototype.insert(ajax, {
  203. data: response.data,
  204. selector: `#${$formContainer.attr('id')} form`,
  205. });
  206. };
  207. // Click the form's submit button; the scoped AJAX commands above will
  208. // handle the server's response.
  209. $submit.trigger('click.quickedit');
  210. },
  211. /**
  212. * @inheritdoc
  213. */
  214. showValidationErrors() {
  215. this.$formContainer
  216. .find('.quickedit-form')
  217. .addClass('quickedit-validation-error')
  218. .find('form')
  219. .prepend(this.model.get('validationErrors'));
  220. },
  221. });
  222. }(jQuery, Drupal, _));