text: /**
   * @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 and other fields with the same view mode.
              if (
                field !== currentField &&
                field.get('fieldID') !== currentField.get('fieldID')
              ) {
                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 and then 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);
  