* 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# */{
-
+(function(_, Backbone, Drupal) {
+ Drupal.quickedit.FieldModel = Drupal.quickedit.BaseModel.extend(
+ /** @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.
+ * @type {object}
*/
- el: null,
+ 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,
+ },
/**
- * A field ID, of the form
- * `<entity type>/<id>/<field name>/<language>/<view mode>`
+ * State of an in-place editable field in the DOM.
*
- * @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]`.
+ * @constructs
*
- * @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.
+ * @augments Drupal.quickedit.BaseModel
+ *
+ * @param {object} options
+ * Options for the field model.
*/
- entity: null,
+ 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);
+ },
/**
- * This field's metadata as returned by the
- * QuickEditController::metadata().
+ * Destroys the field model.
+ *
+ * @param {object} options
+ * Options for the field model.
*/
- metadata: null,
+ 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);
+ },
/**
- * Callback function for validating changes between states. Receives the
- * previous state, new state, context, and a callback.
+ * @inheritdoc
*/
- acceptStateChange: null,
+ sync() {
+ // We don't use REST updates to sync.
+ },
/**
- * 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.
+ * Validate function for the field model.
*
- * @example
- * "node/1/field_tags/und".
+ * @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.
*/
- logicalFieldID: null,
-
- // The attributes below are stateful. The ones above will never change
- // during the life of a FieldModel instance.
+ 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';
+ }
+ }
+ },
/**
- * In-place editing state of this field. Defaults to the initial state.
- * Possible values: {@link Drupal.quickedit.FieldModel.states}.
+ * Extracts the entity ID from this field's ID.
+ *
+ * @return {string}
+ * An entity ID: a string of the format `<entity type>/<id>`.
*/
- state: 'inactive',
+ getEntityID() {
+ return this.get('fieldID')
+ .split('/')
+ .slice(0, 2)
+ .join('/');
+ },
/**
- * The field is currently in the 'changed' state or one of the following
- * states in which the field is still changed.
+ * Extracts the view mode ID from this field's ID.
+ *
+ * @return {string}
+ * A view mode ID.
*/
- isChanged: false,
+ getViewMode() {
+ return this.get('fieldID')
+ .split('/')
+ .pop();
+ },
/**
- * Is tracked by the EntityModel, is mirrored here solely for decorative
- * purposes: so that FieldDecorationView.renderChanged() can react to it.
+ * Find other instances of this field with different view modes.
+ *
+ * @return {Array}
+ * An array containing view mode IDs.
*/
- inTempStore: false,
-
+ 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 */ {
/**
- * 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.
+ * Sequence of all possible states a field can be in during quickediting.
+ *
+ * @type {Array.<string>}
*/
- html: null,
+ 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',
+ ],
/**
- * 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.
+ * 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.
*/
- 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.
-
+ followsStateSequence(from, to) {
+ return _.indexOf(this.states, from) < _.indexOf(this.states, to);
+ },
},
-
- /**
- * 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 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));
+ Drupal.quickedit.FieldCollection = Backbone.Collection.extend(
+ /** @lends Drupal.quickedit.FieldCollection */ {
+ /**
+ * @type {Drupal.quickedit.FieldModel}
+ */
+ model: Drupal.quickedit.FieldModel,
+ },
+ );
+})(_, Backbone, Drupal);