drupal/core/modules/edit/js/models/FieldModel.js

133 lines
3.7 KiB
JavaScript

(function (_, Backbone, Drupal) {
"use strict";
/**
* State of an in-place editable field in the DOM.
*/
Drupal.edit.FieldModel = Backbone.Model.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".
id: null,
// A Drupal.edit.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 EditController::metadata().
metadata: null,
// Callback function for validating changes between states. Receives the
// previous state, new state, context, and a callback
acceptStateChange: 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.edit.FieldModel.states.
state: 'inactive',
// The full HTML representation of this field (with the element that has
// the data-edit-id as the outer element). Used to propagate changes from
// this field instance to other instances of the same field.
html: 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);
},
/**
* {@inheritdoc}
*/
destroy: function (options) {
if (this.get('state') !== 'inactive') {
throw new Error("FieldModel cannot be destroyed if it is not inactive state.");
}
Backbone.Model.prototype.destroy.apply(this, options);
},
/**
* {@inheritdoc}
*/
sync: function () {
// We don't use REST updates to sync.
return;
},
/**
* {@inheritdoc}
*/
validate: function (attrs, options) {
// We only care about validating the 'state' attribute.
if (!_.has(attrs, 'state')) {
return;
}
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)) {
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.id.split('/').slice(0, 2).join('/');
}
}, {
/**
* A list (sequence) of all possible states a field can be in during in-place
* editing.
*/
states: [
'inactive', 'candidate', 'highlighted',
'activating', 'active', 'changed', 'saving', 'saved', 'invalid'
],
/**
* Indicates whether the 'from' state comes before the 'to' state.
*
* @param String from
* One of Drupal.edit.FieldModel.states.
* @param String to
* One of Drupal.edit.FieldModel.states.
* @return Boolean
*/
followsStateSequence: function (from, to) {
return _.indexOf(this.states, from) < _.indexOf(this.states, to);
}
});
Drupal.edit.FieldCollection = Backbone.Collection.extend({
model: Drupal.edit.FieldModel
});
}(_, Backbone, Drupal));