Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / quickedit / js / editors / formEditor.es6.js
diff --git a/web/core/modules/quickedit/js/editors/formEditor.es6.js b/web/core/modules/quickedit/js/editors/formEditor.es6.js
new file mode 100644 (file)
index 0000000..7864f67
--- /dev/null
@@ -0,0 +1,251 @@
+/**
+ * @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 = this.$formContainer = $(Drupal.theme('quickeditFormContainer', {
+        id,
+        loadingMsg: Drupal.t('Loading…'),
+      }));
+      $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;
+
+      function cleanUpAjax() {
+        Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax);
+        formSaveAjax = null;
+      }
+
+      // Create an AJAX object for the form associated with the field.
+      var formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving({
+        nocssjs: false,
+        other_view_modes: fieldModel.findOtherViewModes(),
+      }, $submit);
+
+      // 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, _));