Mercurial > hg > isophonics-drupal-site
diff core/modules/quickedit/js/models/FieldModel.es6.js @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/modules/quickedit/js/models/FieldModel.es6.js Wed Nov 29 16:09:58 2017 +0000 @@ -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));