3 * Form-based in-place editor. Works for any field type.
6 (function ($, Drupal, _) {
10 * @augments Drupal.quickedit.EditorView
12 Drupal.quickedit.editors.form = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.form# */{
15 * Tracks form container DOM element that is used while in-place editing.
22 * Holds the {@link Drupal.Ajax} object.
31 * @param {object} fieldModel
32 * The field model that holds the state.
33 * @param {string} state
34 * The state to change to.
36 stateChange(fieldModel, state) {
37 const from = fieldModel.previous('state');
44 if (from !== 'inactive') {
53 // If coming from an invalid state, then the form is already loaded.
54 if (from !== 'invalid') {
73 this.showValidationErrors();
82 * A settings object for the quick edit UI.
84 getQuickEditUISettings() {
85 return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true };
89 * Loads the form for this field, displays it on top of the actual field.
92 const fieldModel = this.fieldModel;
94 // Generate a DOM-compatible ID for the form container DOM element.
95 const id = `quickedit-form-for-${fieldModel.id.replace(/[\/\[\]]/g, '_')}`;
97 // Render form container.
98 const $formContainer = this.$formContainer = $(Drupal.theme('quickeditFormContainer', {
100 loadingMsg: Drupal.t('Loading…'),
103 .find('.quickedit-form')
104 .addClass('quickedit-editable quickedit-highlighted quickedit-editing')
105 .attr('role', 'dialog');
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);
115 $formContainer.insertBefore(this.$el);
118 // Load form, insert it into the form container and attach event handlers.
119 const formOptions = {
120 fieldID: fieldModel.get('fieldID'),
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:
130 reset: !fieldModel.get('entity').get('inTempStore'),
132 Drupal.quickedit.util.form.load(formOptions, (form, ajax) => {
133 Drupal.AjaxCommands.prototype.insert(ajax, {
135 selector: `#${id} .placeholder`,
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
144 if (state === 'invalid') {
145 fieldModel.set('state', 'activating');
147 // Otherwise assume that the fieldModel is in a candidate state and
148 // set it to changed on formUpdate.
150 fieldModel.set('state', 'changed');
153 .on('keypress.quickedit', 'input', (event) => {
154 if (event.keyCode === 13) {
159 // The in-place editor has loaded; change state to 'active'.
160 fieldModel.set('state', 'active');
165 * Removes the form for this field, detaches behaviors and event handlers.
168 if (this.$formContainer === null) {
172 delete this.formSaveAjax;
173 // Allow form widgets to detach properly.
174 Drupal.detachBehaviors(this.$formContainer.get(0), null, 'unload');
176 .off('change.quickedit', ':input')
177 .off('keypress.quickedit', 'input')
179 this.$formContainer = null;
186 const $formContainer = this.$formContainer;
187 const $submit = $formContainer.find('.quickedit-form-submit');
188 const editorModel = this.model;
189 const fieldModel = this.fieldModel;
191 function cleanUpAjax() {
192 Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax);
196 // Create an AJAX object for the form associated with the field.
197 var formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving({
199 other_view_modes: fieldModel.findOtherViewModes(),
202 // Successfully saved.
203 formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) {
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.
214 fieldModel.set('html', response.data);
218 // Unsuccessfully saved; validation errors.
219 formSaveAjax.commands.quickeditFieldFormValidationErrors = function (ajax, response, status) {
220 editorModel.set('validationErrors', response.data);
221 fieldModel.set('state', 'invalid');
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, {
231 selector: `#${$formContainer.attr('id')} form`,
235 // Click the form's submit button; the scoped AJAX commands above will
236 // handle the server's response.
237 $submit.trigger('click.quickedit');
243 showValidationErrors() {
245 .find('.quickedit-form')
246 .addClass('quickedit-validation-error')
248 .prepend(this.model.get('validationErrors'));
251 }(jQuery, Drupal, _));