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