Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / quickedit / js / models / FieldModel.es6.js
diff --git a/web/core/modules/quickedit/js/models/FieldModel.es6.js b/web/core/modules/quickedit/js/models/FieldModel.es6.js
new file mode 100644 (file)
index 0000000..776c331
--- /dev/null
@@ -0,0 +1,344 @@
+/**
+ * @file
+ * A Backbone Model for the state of an in-place editable field in the DOM.
+ */
+
+(function (_, Backbone, Drupal) {
+  Drupal.quickedit.FieldModel = Drupal.quickedit.BaseModel.extend(/** @lends Drupal.quickedit.FieldModel# */{
+
+    /**
+     * @type {object}
+     */
+    defaults: /** @lends Drupal.quickedit.FieldModel# */{
+
+      /**
+       * The DOM element that represents this field. It may seem bizarre to have
+       * a DOM element in a Backbone Model, but we need to be able to map fields
+       * in the DOM to FieldModels in memory.
+       */
+      el: null,
+
+      /**
+       * A field ID, of the form
+       * `<entity type>/<id>/<field name>/<language>/<view mode>`
+       *
+       * @example
+       * "node/1/field_tags/und/full"
+       */
+      fieldID: null,
+
+      /**
+       * The unique ID of this field within its entity instance on the page, of
+       * the form `<entity type>/<id>/<field name>/<language>/<view
+       * mode>[entity instance ID]`.
+       *
+       * @example
+       * "node/1/field_tags/und/full[0]"
+       */
+      id: null,
+
+      /**
+       * A {@link Drupal.quickedit.EntityModel}. Its "fields" attribute, which
+       * is a FieldCollection, is automatically updated to include this
+       * FieldModel.
+       */
+      entity: null,
+
+      /**
+       * This field's metadata as returned by the
+       * QuickEditController::metadata().
+       */
+      metadata: null,
+
+      /**
+       * Callback function for validating changes between states. Receives the
+       * previous state, new state, context, and a callback.
+       */
+      acceptStateChange: null,
+
+      /**
+       * A logical field ID, of the form
+       * `<entity type>/<id>/<field name>/<language>`, i.e. the fieldID without
+       * the view mode, to be able to identify other instances of the same
+       * field on the page but rendered in a different view mode.
+       *
+       * @example
+       * "node/1/field_tags/und".
+       */
+      logicalFieldID: null,
+
+      // The attributes below are stateful. The ones above will never change
+      // during the life of a FieldModel instance.
+
+      /**
+       * In-place editing state of this field. Defaults to the initial state.
+       * Possible values: {@link Drupal.quickedit.FieldModel.states}.
+       */
+      state: 'inactive',
+
+      /**
+       * The field is currently in the 'changed' state or one of the following
+       * states in which the field is still changed.
+       */
+      isChanged: false,
+
+      /**
+       * Is tracked by the EntityModel, is mirrored here solely for decorative
+       * purposes: so that FieldDecorationView.renderChanged() can react to it.
+       */
+      inTempStore: false,
+
+      /**
+       * The full HTML representation of this field (with the element that has
+       * the data-quickedit-field-id as the outer element). Used to propagate
+       * changes from this field to other instances of the same field storage.
+       */
+      html: null,
+
+      /**
+       * An object containing the full HTML representations (values) of other
+       * view modes (keys) of this field, for other instances of this field
+       * displayed in a different view mode.
+       */
+      htmlForOtherViewModes: null,
+    },
+
+    /**
+     * State of an in-place editable field in the DOM.
+     *
+     * @constructs
+     *
+     * @augments Drupal.quickedit.BaseModel
+     *
+     * @param {object} options
+     *   Options for the field model.
+     */
+    initialize(options) {
+      // Store the original full HTML representation of this field.
+      this.set('html', options.el.outerHTML);
+
+      // Enlist field automatically in the associated entity's field collection.
+      this.get('entity').get('fields').add(this);
+
+      // Automatically generate the logical field ID.
+      this.set('logicalFieldID', this.get('fieldID').split('/').slice(0, 4).join('/'));
+
+      // Call Drupal.quickedit.BaseModel's initialize() method.
+      Drupal.quickedit.BaseModel.prototype.initialize.call(this, options);
+    },
+
+    /**
+     * Destroys the field model.
+     *
+     * @param {object} options
+     *   Options for the field model.
+     */
+    destroy(options) {
+      if (this.get('state') !== 'inactive') {
+        throw new Error('FieldModel cannot be destroyed if it is not inactive state.');
+      }
+      Drupal.quickedit.BaseModel.prototype.destroy.call(this, options);
+    },
+
+    /**
+     * @inheritdoc
+     */
+    sync() {
+      // We don't use REST updates to sync.
+
+    },
+
+    /**
+     * Validate function for the field model.
+     *
+     * @param {object} attrs
+     *   The attributes changes in the save or set call.
+     * @param {object} options
+     *   An object with the following option:
+     * @param {string} [options.reason]
+     *   A string that conveys a particular reason to allow for an exceptional
+     *   state change.
+     * @param {Array} options.accept-field-states
+     *   An array of strings that represent field states that the entities must
+     *   be in to validate. For example, if `accept-field-states` is
+     *   `['candidate', 'highlighted']`, then all the fields of the entity must
+     *   be in either of these two states for the save or set call to
+     *   validate and proceed.
+     *
+     * @return {string}
+     *   A string to say something about the state of the field model.
+     */
+    validate(attrs, options) {
+      const current = this.get('state');
+      const next = attrs.state;
+      if (current !== next) {
+        // Ensure it's a valid state.
+        if (_.indexOf(this.constructor.states, next) === -1) {
+          return `"${next}" is an invalid state`;
+        }
+        // Check if the acceptStateChange callback accepts it.
+        if (!this.get('acceptStateChange')(current, next, options, this)) {
+          return 'state change not accepted';
+        }
+      }
+    },
+
+    /**
+     * Extracts the entity ID from this field's ID.
+     *
+     * @return {string}
+     *   An entity ID: a string of the format `<entity type>/<id>`.
+     */
+    getEntityID() {
+      return this.get('fieldID').split('/').slice(0, 2).join('/');
+    },
+
+    /**
+     * Extracts the view mode ID from this field's ID.
+     *
+     * @return {string}
+     *   A view mode ID.
+     */
+    getViewMode() {
+      return this.get('fieldID').split('/').pop();
+    },
+
+    /**
+     * Find other instances of this field with different view modes.
+     *
+     * @return {Array}
+     *   An array containing view mode IDs.
+     */
+    findOtherViewModes() {
+      const currentField = this;
+      const otherViewModes = [];
+      Drupal.quickedit.collections.fields
+        // Find all instances of fields that display the same logical field
+        // (same entity, same field, just a different instance and maybe a
+        // different view mode).
+        .where({ logicalFieldID: currentField.get('logicalFieldID') })
+        .forEach((field) => {
+          // Ignore the current field.
+          if (field === currentField) {
+
+          }
+          // Also ignore other fields with the same view mode.
+          else if (field.get('fieldID') === currentField.get('fieldID')) {
+
+          }
+          else {
+            otherViewModes.push(field.getViewMode());
+          }
+        });
+      return otherViewModes;
+    },
+
+  }, /** @lends Drupal.quickedit.FieldModel */{
+
+    /**
+     * Sequence of all possible states a field can be in during quickediting.
+     *
+     * @type {Array.<string>}
+     */
+    states: [
+      // The field associated with this FieldModel is linked to an EntityModel;
+      // the user can choose to start in-place editing that entity (and
+      // consequently this field). No in-place editor (EditorView) is associated
+      // with this field, because this field is not being in-place edited.
+      // This is both the initial (not yet in-place editing) and the end state
+      // (finished in-place editing).
+      'inactive',
+      // The user is in-place editing this entity, and this field is a
+      // candidate
+      // for in-place editing. In-place editor should not
+      // - Trigger: user.
+      // - Guarantees: entity is ready, in-place editor (EditorView) is
+      //   associated with the field.
+      // - Expected behavior: visual indicators
+      //   around the field indicate it is available for in-place editing, no
+      //   in-place editor presented yet.
+      'candidate',
+      // User is highlighting this field.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate'.
+      // - Expected behavior: visual indicators to convey highlighting, in-place
+      //   editing toolbar shows field's label.
+      'highlighted',
+      // User has activated the in-place editing of this field; in-place editor
+      // is activating.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate'.
+      // - Expected behavior: loading indicator, in-place editor is loading
+      //   remote data (e.g. retrieve form from back-end). Upon retrieval of
+      //   remote data, the in-place editor transitions the field's state to
+      //   'active'.
+      'activating',
+      // In-place editor has finished loading remote data; ready for use.
+      // - Trigger: in-place editor.
+      // - Guarantees: see 'candidate'.
+      // - Expected behavior: in-place editor for the field is ready for use.
+      'active',
+      // User has modified values in the in-place editor.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate', plus in-place editor is ready for use.
+      // - Expected behavior: visual indicator of change.
+      'changed',
+      // User is saving changed field data in in-place editor to
+      // PrivateTempStore. The save mechanism of the in-place editor is called.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate' and 'active'.
+      // - Expected behavior: saving indicator, in-place editor is saving field
+      //   data into PrivateTempStore. Upon successful saving (without
+      //   validation errors), the in-place editor transitions the field's state
+      //   to 'saved', but to 'invalid' upon failed saving (with validation
+      //   errors).
+      'saving',
+      // In-place editor has successfully saved the changed field.
+      // - Trigger: in-place editor.
+      // - Guarantees: see 'candidate' and 'active'.
+      // - Expected behavior: transition back to 'candidate' state because the
+      //   deed is done. Then: 1) transition to 'inactive' to allow the field
+      //   to be rerendered, 2) destroy the FieldModel (which also destroys
+      //   attached views like the EditorView), 3) replace the existing field
+      //   HTML with the existing HTML and 4) attach behaviors again so that the
+      //   field becomes available again for in-place editing.
+      'saved',
+      // In-place editor has failed to saved the changed field: there were
+      // validation errors.
+      // - Trigger: in-place editor.
+      // - Guarantees: see 'candidate' and 'active'.
+      // - Expected behavior: remain in 'invalid' state, let the user make more
+      //   changes so that he can save it again, without validation errors.
+      'invalid',
+    ],
+
+    /**
+     * Indicates whether the 'from' state comes before the 'to' state.
+     *
+     * @param {string} from
+     *   One of {@link Drupal.quickedit.FieldModel.states}.
+     * @param {string} to
+     *   One of {@link Drupal.quickedit.FieldModel.states}.
+     *
+     * @return {bool}
+     *   Whether the 'from' state comes before the 'to' state.
+     */
+    followsStateSequence(from, to) {
+      return _.indexOf(this.states, from) < _.indexOf(this.states, to);
+    },
+
+  });
+
+  /**
+   * @constructor
+   *
+   * @augments Backbone.Collection
+   */
+  Drupal.quickedit.FieldCollection = Backbone.Collection.extend(/** @lends Drupal.quickedit.FieldCollection */{
+
+    /**
+     * @type {Drupal.quickedit.FieldModel}
+     */
+    model: Drupal.quickedit.FieldModel,
+  });
+}(_, Backbone, Drupal));