/** * @file * Form-based in-place editor. Works for any field type. */ (function($, Drupal, _) { /** * @constructor * * @augments Drupal.quickedit.EditorView */ Drupal.quickedit.editors.form = Drupal.quickedit.EditorView.extend( /** @lends Drupal.quickedit.editors.form# */ { /** * Tracks form container DOM element that is used while in-place editing. * * @type {jQuery} */ $formContainer: null, /** * Holds the {@link Drupal.Ajax} object. * * @type {Drupal.Ajax} */ formSaveAjax: null, /** * @inheritdoc * * @param {object} fieldModel * The field model that holds the state. * @param {string} state * The state to change to. */ stateChange(fieldModel, state) { const from = fieldModel.previous('state'); const to = state; switch (to) { case 'inactive': break; case 'candidate': if (from !== 'inactive') { this.removeForm(); } break; case 'highlighted': break; case 'activating': // If coming from an invalid state, then the form is already loaded. if (from !== 'invalid') { this.loadForm(); } break; case 'active': break; case 'changed': break; case 'saving': this.save(); break; case 'saved': break; case 'invalid': this.showValidationErrors(); break; } }, /** * @inheritdoc * * @return {object} * A settings object for the quick edit UI. */ getQuickEditUISettings() { return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true, }; }, /** * Loads the form for this field, displays it on top of the actual field. */ loadForm() { const fieldModel = this.fieldModel; // Generate a DOM-compatible ID for the form container DOM element. const id = `quickedit-form-for-${fieldModel.id.replace( /[/[\]]/g, '_', )}`; // Render form container. const $formContainer = $( Drupal.theme('quickeditFormContainer', { id, loadingMsg: Drupal.t('Loading…'), }), ); this.$formContainer = $formContainer; $formContainer .find('.quickedit-form') .addClass( 'quickedit-editable quickedit-highlighted quickedit-editing', ) .attr('role', 'dialog'); // Insert form container in DOM. if (this.$el.css('display') === 'inline') { $formContainer.prependTo(this.$el.offsetParent()); // Position the form container to render on top of the field's element. const pos = this.$el.position(); $formContainer.css('left', pos.left).css('top', pos.top); } else { $formContainer.insertBefore(this.$el); } // Load form, insert it into the form container and attach event handlers. const formOptions = { fieldID: fieldModel.get('fieldID'), $el: this.$el, nocssjs: false, // Reset an existing entry for this entity in the PrivateTempStore (if // any) when loading the field. Logically speaking, this should happen // in a separate request because this is an entity-level operation, not // a field-level operation. But that would require an additional // request, that might not even be necessary: it is only when a user // loads a first changed field for an entity that this needs to happen: // precisely now! reset: !fieldModel.get('entity').get('inTempStore'), }; Drupal.quickedit.util.form.load(formOptions, (form, ajax) => { Drupal.AjaxCommands.prototype.insert(ajax, { data: form, selector: `#${id} .placeholder`, }); $formContainer .on('formUpdated.quickedit', ':input', event => { const state = fieldModel.get('state'); // If the form is in an invalid state, it will persist on the page. // Set the field to activating so that the user can correct the // invalid value. if (state === 'invalid') { fieldModel.set('state', 'activating'); } // Otherwise assume that the fieldModel is in a candidate state and // set it to changed on formUpdate. else { fieldModel.set('state', 'changed'); } }) .on('keypress.quickedit', 'input', event => { if (event.keyCode === 13) { return false; } }); // The in-place editor has loaded; change state to 'active'. fieldModel.set('state', 'active'); }); }, /** * Removes the form for this field, detaches behaviors and event handlers. */ removeForm() { if (this.$formContainer === null) { return; } delete this.formSaveAjax; // Allow form widgets to detach properly. Drupal.detachBehaviors(this.$formContainer.get(0), null, 'unload'); this.$formContainer .off('change.quickedit', ':input') .off('keypress.quickedit', 'input') .remove(); this.$formContainer = null; }, /** * @inheritdoc */ save() { const $formContainer = this.$formContainer; const $submit = $formContainer.find('.quickedit-form-submit'); const editorModel = this.model; const fieldModel = this.fieldModel; // Create an AJAX object for the form associated with the field. let formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving( { nocssjs: false, other_view_modes: fieldModel.findOtherViewModes(), }, $submit, ); function cleanUpAjax() { Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax); formSaveAjax = null; } // Successfully saved. formSaveAjax.commands.quickeditFieldFormSaved = function( ajax, response, status, ) { cleanUpAjax(); // First, transition the state to 'saved'. fieldModel.set('state', 'saved'); // Second, set the 'htmlForOtherViewModes' attribute, so that when this // field is rerendered, the change can be propagated to other instances // of this field, which may be displayed in different view modes. fieldModel.set('htmlForOtherViewModes', response.other_view_modes); // Finally, set the 'html' attribute on the field model. This will cause // the field to be rerendered. _.defer(() => { fieldModel.set('html', response.data); }); }; // Unsuccessfully saved; validation errors. formSaveAjax.commands.quickeditFieldFormValidationErrors = function( ajax, response, status, ) { editorModel.set('validationErrors', response.data); fieldModel.set('state', 'invalid'); }; // The quickeditFieldForm AJAX command is called upon attempting to save // the form; Form API will mark which form items have errors, if any. This // command is invoked only if validation errors exist and then it runs // before editFieldFormValidationErrors(). formSaveAjax.commands.quickeditFieldForm = function( ajax, response, status, ) { Drupal.AjaxCommands.prototype.insert(ajax, { data: response.data, selector: `#${$formContainer.attr('id')} form`, }); }; // Click the form's submit button; the scoped AJAX commands above will // handle the server's response. $submit.trigger('click.quickedit'); }, /** * @inheritdoc */ showValidationErrors() { this.$formContainer .find('.quickedit-form') .addClass('quickedit-validation-error') .find('form') .prepend(this.model.get('validationErrors')); }, }, ); })(jQuery, Drupal, _);