123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- /**
- * @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, _);
|