X-Git-Url: http://www.aleph1.co.uk/gitweb/?a=blobdiff_plain;ds=sidebyside;f=web%2Fcore%2Fmodules%2Fquickedit%2Fjs%2Fmodels%2FFieldModel.es6.js;fp=web%2Fcore%2Fmodules%2Fquickedit%2Fjs%2Fmodels%2FFieldModel.es6.js;h=fbb510cf7fdfa81b0af46273f50b093577c831e1;hb=0bf8d09d2542548982e81a441b1f16e75873a04f;hp=0c01cc30240678fb10049246bcf2330d35effd5f;hpb=74df008bdbb3a11eeea356744f39b802369bda3c;p=yaffs-website diff --git a/web/core/modules/quickedit/js/models/FieldModel.es6.js b/web/core/modules/quickedit/js/models/FieldModel.es6.js index 0c01cc302..fbb510cf7 100644 --- a/web/core/modules/quickedit/js/models/FieldModel.es6.js +++ b/web/core/modules/quickedit/js/models/FieldModel.es6.js @@ -3,335 +3,351 @@ * 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 + * `////` + * + * @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 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 + * `///`, 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 - * `////` + * 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 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 - * `///`, 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 `/`. */ - 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.} */ - 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 `/`. - */ - 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.} - */ - 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);