/js/models/FieldModel.js
JavaScript | 260 lines | 88 code | 22 blank | 150 comment | 8 complexity | 975655b1b4d7bf55cd3f5eaa1f570394 MD5 | raw file
- /**
- * @file
- * A Backbone Model for the state of an in-place editable field in the DOM.
- */
- (function (_, Backbone, Drupal) {
- "use strict";
- /**
- * State of an in-place editable field in the DOM.
- */
- Drupal.quickedit.FieldModel = Drupal.quickedit.BaseModel.extend({
- defaults: {
- // 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>", e.g.
- // "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]",
- // e.g. "node/1/field_tags/und/full[0]".
- id: null,
- // A 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. e.g. "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: @see 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 instance to other instances of the same field.
- 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
- },
- /**
- * {@inheritdoc}
- */
- initialize: function (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);
- },
- /**
- * {@inheritdoc}
- */
- destroy: function (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: function () {
- // We don't use REST updates to sync.
- return;
- },
- /**
- * {@inheritdoc}
- */
- validate: function (attrs, options) {
- var current = this.get('state');
- var 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: function () {
- 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: function () {
- 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: function () {
- var currentField = this;
- var 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(function (field) {
- // Ignore the current field.
- if (field === currentField) {
- return;
- }
- // Also ignore other fields with the same view mode.
- else if (field.get('fieldID') === currentField.get('fieldID')) {
- return;
- }
- else {
- otherViewModes.push(field.getViewMode());
- }
- });
- return otherViewModes;
- }
- }, {
- /**
- * A list (sequence) of all possible states a field can be in during in-place
- * editing.
- */
- 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 TempStore. 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 TempStore. 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 Drupal.quickedit.FieldModel.states.
- * @param String to
- * One of Drupal.quickedit.FieldModel.states.
- * @return Boolean
- */
- followsStateSequence: function (from, to) {
- return _.indexOf(this.states, from) < _.indexOf(this.states, to);
- }
- });
- Drupal.quickedit.FieldCollection = Backbone.Collection.extend({
- model: Drupal.quickedit.FieldModel
- });
- }(_, Backbone, Drupal));