Security update for Core, with self-updated composer
[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(/** @lends Drupal.quickedit.editors.form# */{
13
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 { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true };
86     },
87
88     /**
89      * Loads the form for this field, displays it on top of the actual field.
90      */
91     loadForm() {
92       const fieldModel = this.fieldModel;
93
94       // Generate a DOM-compatible ID for the form container DOM element.
95       const id = `quickedit-form-for-${fieldModel.id.replace(/[\/\[\]]/g, '_')}`;
96
97       // Render form container.
98       const $formContainer = this.$formContainer = $(Drupal.theme('quickeditFormContainer', {
99         id,
100         loadingMsg: Drupal.t('Loading…'),
101       }));
102       $formContainer
103         .find('.quickedit-form')
104         .addClass('quickedit-editable quickedit-highlighted quickedit-editing')
105         .attr('role', 'dialog');
106
107       // Insert form container in DOM.
108       if (this.$el.css('display') === 'inline') {
109         $formContainer.prependTo(this.$el.offsetParent());
110         // Position the form container to render on top of the field's element.
111         const pos = this.$el.position();
112         $formContainer.css('left', pos.left).css('top', pos.top);
113       }
114       else {
115         $formContainer.insertBefore(this.$el);
116       }
117
118       // Load form, insert it into the form container and attach event handlers.
119       const formOptions = {
120         fieldID: fieldModel.get('fieldID'),
121         $el: this.$el,
122         nocssjs: false,
123         // Reset an existing entry for this entity in the PrivateTempStore (if
124         // any) when loading the field. Logically speaking, this should happen
125         // in a separate request because this is an entity-level operation, not
126         // a field-level operation. But that would require an additional
127         // request, that might not even be necessary: it is only when a user
128         // loads a first changed field for an entity that this needs to happen:
129         // precisely now!
130         reset: !fieldModel.get('entity').get('inTempStore'),
131       };
132       Drupal.quickedit.util.form.load(formOptions, (form, ajax) => {
133         Drupal.AjaxCommands.prototype.insert(ajax, {
134           data: form,
135           selector: `#${id} .placeholder`,
136         });
137
138         $formContainer
139           .on('formUpdated.quickedit', ':input', (event) => {
140             const state = fieldModel.get('state');
141             // If the form is in an invalid state, it will persist on the page.
142             // Set the field to activating so that the user can correct the
143             // invalid value.
144             if (state === 'invalid') {
145               fieldModel.set('state', 'activating');
146             }
147             // Otherwise assume that the fieldModel is in a candidate state and
148             // set it to changed on formUpdate.
149             else {
150               fieldModel.set('state', 'changed');
151             }
152           })
153           .on('keypress.quickedit', 'input', (event) => {
154             if (event.keyCode === 13) {
155               return false;
156             }
157           });
158
159         // The in-place editor has loaded; change state to 'active'.
160         fieldModel.set('state', 'active');
161       });
162     },
163
164     /**
165      * Removes the form for this field, detaches behaviors and event handlers.
166      */
167     removeForm() {
168       if (this.$formContainer === null) {
169         return;
170       }
171
172       delete this.formSaveAjax;
173       // Allow form widgets to detach properly.
174       Drupal.detachBehaviors(this.$formContainer.get(0), null, 'unload');
175       this.$formContainer
176         .off('change.quickedit', ':input')
177         .off('keypress.quickedit', 'input')
178         .remove();
179       this.$formContainer = null;
180     },
181
182     /**
183      * @inheritdoc
184      */
185     save() {
186       const $formContainer = this.$formContainer;
187       const $submit = $formContainer.find('.quickedit-form-submit');
188       const editorModel = this.model;
189       const fieldModel = this.fieldModel;
190
191       function cleanUpAjax() {
192         Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax);
193         formSaveAjax = null;
194       }
195
196       // Create an AJAX object for the form associated with the field.
197       var formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving({
198         nocssjs: false,
199         other_view_modes: fieldModel.findOtherViewModes(),
200       }, $submit);
201
202       // Successfully saved.
203       formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) {
204         cleanUpAjax();
205         // First, transition the state to 'saved'.
206         fieldModel.set('state', 'saved');
207         // Second, set the 'htmlForOtherViewModes' attribute, so that when this
208         // field is rerendered, the change can be propagated to other instances
209         // of this field, which may be displayed in different view modes.
210         fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
211         // Finally, set the 'html' attribute on the field model. This will cause
212         // the field to be rerendered.
213         _.defer(() => {
214           fieldModel.set('html', response.data);
215         });
216       };
217
218       // Unsuccessfully saved; validation errors.
219       formSaveAjax.commands.quickeditFieldFormValidationErrors = function (ajax, response, status) {
220         editorModel.set('validationErrors', response.data);
221         fieldModel.set('state', 'invalid');
222       };
223
224       // The quickeditFieldForm AJAX command is called upon attempting to save
225       // the form; Form API will mark which form items have errors, if any. This
226       // command is invoked only if validation errors exist and then it runs
227       // before editFieldFormValidationErrors().
228       formSaveAjax.commands.quickeditFieldForm = function (ajax, response, status) {
229         Drupal.AjaxCommands.prototype.insert(ajax, {
230           data: response.data,
231           selector: `#${$formContainer.attr('id')} form`,
232         });
233       };
234
235       // Click the form's submit button; the scoped AJAX commands above will
236       // handle the server's response.
237       $submit.trigger('click.quickedit');
238     },
239
240     /**
241      * @inheritdoc
242      */
243     showValidationErrors() {
244       this.$formContainer
245         .find('.quickedit-form')
246         .addClass('quickedit-validation-error')
247         .find('form')
248         .prepend(this.model.get('validationErrors'));
249     },
250   });
251 }(jQuery, Drupal, _));