Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
57
web/core/modules/quickedit/js/models/AppModel.js
Normal file
57
web/core/modules/quickedit/js/models/AppModel.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* @file
|
||||
* A Backbone Model for the state of the in-place editing application.
|
||||
*
|
||||
* @see Drupal.quickedit.AppView
|
||||
*/
|
||||
|
||||
(function (Backbone, Drupal) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
Drupal.quickedit.AppModel = Backbone.Model.extend(/** @lends Drupal.quickedit.AppModel# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*
|
||||
* @prop {Drupal.quickedit.FieldModel} highlightedField
|
||||
* @prop {Drupal.quickedit.FieldModel} activeField
|
||||
* @prop {Drupal.dialog~dialogDefinition} activeModal
|
||||
*/
|
||||
defaults: /** @lends Drupal.quickedit.AppModel# */{
|
||||
|
||||
/**
|
||||
* The currently state='highlighted' Drupal.quickedit.FieldModel, if any.
|
||||
*
|
||||
* @type {Drupal.quickedit.FieldModel}
|
||||
*
|
||||
* @see Drupal.quickedit.FieldModel.states
|
||||
*/
|
||||
highlightedField: null,
|
||||
|
||||
/**
|
||||
* The currently state = 'active' Drupal.quickedit.FieldModel, if any.
|
||||
*
|
||||
* @type {Drupal.quickedit.FieldModel}
|
||||
*
|
||||
* @see Drupal.quickedit.FieldModel.states
|
||||
*/
|
||||
activeField: null,
|
||||
|
||||
/**
|
||||
* Reference to a {@link Drupal.dialog} instance if a state change
|
||||
* requires confirmation.
|
||||
*
|
||||
* @type {Drupal.dialog~dialogDefinition}
|
||||
*/
|
||||
activeModal: null
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}(Backbone, Drupal));
|
60
web/core/modules/quickedit/js/models/BaseModel.js
Normal file
60
web/core/modules/quickedit/js/models/BaseModel.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* @file
|
||||
* A Backbone Model subclass that enforces validation when calling set().
|
||||
*/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.quickedit.BaseModel = Backbone.Model.extend(/** @lends Drupal.quickedit.BaseModel# */{
|
||||
|
||||
/**
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
*
|
||||
* @param {object} options
|
||||
* Options for the base model-
|
||||
*
|
||||
* @return {Drupal.quickedit.BaseModel}
|
||||
* A quickedit base model.
|
||||
*/
|
||||
initialize: function (options) {
|
||||
this.__initialized = true;
|
||||
return Backbone.Model.prototype.initialize.call(this, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a value on the model
|
||||
*
|
||||
* @param {object|string} key
|
||||
* The key to set a value for.
|
||||
* @param {*} val
|
||||
* The value to set.
|
||||
* @param {object} [options]
|
||||
* Options for the model.
|
||||
*
|
||||
* @return {*}
|
||||
* The result of `Backbone.Model.prototype.set` with the specified
|
||||
* parameters.
|
||||
*/
|
||||
set: function (key, val, options) {
|
||||
if (this.__initialized) {
|
||||
// Deal with both the "key", value and {key:value}-style arguments.
|
||||
if (typeof key === 'object') {
|
||||
key.validate = true;
|
||||
}
|
||||
else {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
options.validate = true;
|
||||
}
|
||||
}
|
||||
return Backbone.Model.prototype.set.call(this, key, val, options);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}(Drupal, Backbone));
|
54
web/core/modules/quickedit/js/models/EditorModel.js
Normal file
54
web/core/modules/quickedit/js/models/EditorModel.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* @file
|
||||
* A Backbone Model for the state of an in-place editor.
|
||||
*
|
||||
* @see Drupal.quickedit.EditorView
|
||||
*/
|
||||
|
||||
(function (Backbone, Drupal) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
Drupal.quickedit.EditorModel = Backbone.Model.extend(/** @lends Drupal.quickedit.EditorModel# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*
|
||||
* @prop {string} originalValue
|
||||
* @prop {string} currentValue
|
||||
* @prop {Array} validationErrors
|
||||
*/
|
||||
defaults: /** @lends Drupal.quickedit.EditorModel# */{
|
||||
|
||||
/**
|
||||
* Not the full HTML representation of this field, but the "actual"
|
||||
* original value of the field, stored by the used in-place editor, and
|
||||
* in a representation that can be chosen by the in-place editor.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
originalValue: null,
|
||||
|
||||
/**
|
||||
* Analogous to originalValue, but the current value.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
currentValue: null,
|
||||
|
||||
/**
|
||||
* Stores any validation errors to be rendered.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
validationErrors: null
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}(Backbone, Drupal));
|
741
web/core/modules/quickedit/js/models/EntityModel.js
Normal file
741
web/core/modules/quickedit/js/models/EntityModel.js
Normal file
|
@ -0,0 +1,741 @@
|
|||
/**
|
||||
* @file
|
||||
* A Backbone Model for the state of an in-place editable entity in the DOM.
|
||||
*/
|
||||
|
||||
(function (_, $, Backbone, Drupal) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.quickedit.EntityModel = Drupal.quickedit.BaseModel.extend(/** @lends Drupal.quickedit.EntityModel# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
defaults: /** @lends Drupal.quickedit.EntityModel# */{
|
||||
|
||||
/**
|
||||
* The DOM element that represents this entity.
|
||||
*
|
||||
* It may seem bizarre to have a DOM element in a Backbone Model, but we
|
||||
* need to be able to map entities in the DOM to EntityModels in memory.
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
el: null,
|
||||
|
||||
/**
|
||||
* An entity ID, of the form `<entity type>/<entity ID>`
|
||||
*
|
||||
* @example
|
||||
* "node/1"
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
entityID: null,
|
||||
|
||||
/**
|
||||
* An entity instance ID.
|
||||
*
|
||||
* The first instance of a specific entity (i.e. with a given entity ID)
|
||||
* is assigned 0, the second 1, and so on.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
entityInstanceID: null,
|
||||
|
||||
/**
|
||||
* The unique ID of this entity instance on the page, of the form
|
||||
* `<entity type>/<entity ID>[entity instance ID]`
|
||||
*
|
||||
* @example
|
||||
* "node/1[0]"
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
id: null,
|
||||
|
||||
/**
|
||||
* The label of the entity.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
label: null,
|
||||
|
||||
/**
|
||||
* A FieldCollection for all fields of the entity.
|
||||
*
|
||||
* @type {Drupal.quickedit.FieldCollection}
|
||||
*
|
||||
* @see Drupal.quickedit.FieldCollection
|
||||
*/
|
||||
fields: null,
|
||||
|
||||
// The attributes below are stateful. The ones above will never change
|
||||
// during the life of a EntityModel instance.
|
||||
|
||||
/**
|
||||
* Indicates whether this entity is currently being edited in-place.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isActive: false,
|
||||
|
||||
/**
|
||||
* Whether one or more fields are already been stored in PrivateTempStore.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
inTempStore: false,
|
||||
|
||||
/**
|
||||
* Indicates whether a "Save" button is necessary or not.
|
||||
*
|
||||
* Whether one or more fields have already been stored in PrivateTempStore
|
||||
* *or* the field that's currently being edited is in the 'changed' or a
|
||||
* later state.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isDirty: false,
|
||||
|
||||
/**
|
||||
* Whether the request to the server has been made to commit this entity.
|
||||
*
|
||||
* Used to prevent multiple such requests.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isCommitting: false,
|
||||
|
||||
/**
|
||||
* The current processing state of an entity.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
state: 'closed',
|
||||
|
||||
/**
|
||||
* IDs of fields whose new values have been stored in PrivateTempStore.
|
||||
*
|
||||
* We must store this on the EntityModel as well (even though it already
|
||||
* is on the FieldModel) because when a field is rerendered, its
|
||||
* FieldModel is destroyed and this allows us to transition it back to
|
||||
* the proper state.
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
fieldsInTempStore: [],
|
||||
|
||||
/**
|
||||
* A flag the tells the application that this EntityModel must be reloaded
|
||||
* in order to restore the original values to its fields in the client.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
reload: false
|
||||
},
|
||||
|
||||
/**
|
||||
* @constructs
|
||||
*
|
||||
* @augments Drupal.quickedit.BaseModel
|
||||
*/
|
||||
initialize: function () {
|
||||
this.set('fields', new Drupal.quickedit.FieldCollection());
|
||||
|
||||
// Respond to entity state changes.
|
||||
this.listenTo(this, 'change:state', this.stateChange);
|
||||
|
||||
// The state of the entity is largely dependent on the state of its
|
||||
// fields.
|
||||
this.listenTo(this.get('fields'), 'change:state', this.fieldStateChange);
|
||||
|
||||
// Call Drupal.quickedit.BaseModel's initialize() method.
|
||||
Drupal.quickedit.BaseModel.prototype.initialize.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates FieldModels' states when an EntityModel change occurs.
|
||||
*
|
||||
* @param {Drupal.quickedit.EntityModel} entityModel
|
||||
* The entity model
|
||||
* @param {string} state
|
||||
* The state of the associated entity. One of
|
||||
* {@link Drupal.quickedit.EntityModel.states}.
|
||||
* @param {object} options
|
||||
* Options for the entity model.
|
||||
*/
|
||||
stateChange: function (entityModel, state, options) {
|
||||
var to = state;
|
||||
switch (to) {
|
||||
case 'closed':
|
||||
this.set({
|
||||
isActive: false,
|
||||
inTempStore: false,
|
||||
isDirty: false
|
||||
});
|
||||
break;
|
||||
|
||||
case 'launching':
|
||||
break;
|
||||
|
||||
case 'opening':
|
||||
// Set the fields to candidate state.
|
||||
entityModel.get('fields').each(function (fieldModel) {
|
||||
fieldModel.set('state', 'candidate', options);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'opened':
|
||||
// The entity is now ready for editing!
|
||||
this.set('isActive', true);
|
||||
break;
|
||||
|
||||
case 'committing':
|
||||
// The user indicated they want to save the entity.
|
||||
var fields = this.get('fields');
|
||||
// For fields that are in an active state, transition them to
|
||||
// candidate.
|
||||
fields.chain()
|
||||
.filter(function (fieldModel) {
|
||||
return _.intersection([fieldModel.get('state')], ['active']).length;
|
||||
})
|
||||
.each(function (fieldModel) {
|
||||
fieldModel.set('state', 'candidate');
|
||||
});
|
||||
// For fields that are in a changed state, field values must first be
|
||||
// stored in PrivateTempStore.
|
||||
fields.chain()
|
||||
.filter(function (fieldModel) {
|
||||
return _.intersection([fieldModel.get('state')], Drupal.quickedit.app.changedFieldStates).length;
|
||||
})
|
||||
.each(function (fieldModel) {
|
||||
fieldModel.set('state', 'saving');
|
||||
});
|
||||
break;
|
||||
|
||||
case 'deactivating':
|
||||
var changedFields = this.get('fields')
|
||||
.filter(function (fieldModel) {
|
||||
return _.intersection([fieldModel.get('state')], ['changed', 'invalid']).length;
|
||||
});
|
||||
// If the entity contains unconfirmed or unsaved changes, return the
|
||||
// entity to an opened state and ask the user if they would like to
|
||||
// save the changes or discard the changes.
|
||||
// 1. One of the fields is in a changed state. The changed field
|
||||
// might just be a change in the client or it might have been saved
|
||||
// to tempstore.
|
||||
// 2. The saved flag is empty and the confirmed flag is empty. If
|
||||
// the entity has been saved to the server, the fields changed in
|
||||
// the client are irrelevant. If the changes are confirmed, then
|
||||
// proceed to set the fields to candidate state.
|
||||
if ((changedFields.length || this.get('fieldsInTempStore').length) && (!options.saved && !options.confirmed)) {
|
||||
// Cancel deactivation until the user confirms save or discard.
|
||||
this.set('state', 'opened', {confirming: true});
|
||||
// An action in reaction to state change must be deferred.
|
||||
_.defer(function () {
|
||||
Drupal.quickedit.app.confirmEntityDeactivation(entityModel);
|
||||
});
|
||||
}
|
||||
else {
|
||||
var invalidFields = this.get('fields')
|
||||
.filter(function (fieldModel) {
|
||||
return _.intersection([fieldModel.get('state')], ['invalid']).length;
|
||||
});
|
||||
// Indicate if this EntityModel needs to be reloaded in order to
|
||||
// restore the original values of its fields.
|
||||
entityModel.set('reload', (this.get('fieldsInTempStore').length || invalidFields.length));
|
||||
// Set all fields to the 'candidate' state. A changed field may have
|
||||
// to go through confirmation first.
|
||||
entityModel.get('fields').each(function (fieldModel) {
|
||||
// If the field is already in the candidate state, trigger a
|
||||
// change event so that the entityModel can move to the next state
|
||||
// in deactivation.
|
||||
if (_.intersection([fieldModel.get('state')], ['candidate', 'highlighted']).length) {
|
||||
fieldModel.trigger('change:state', fieldModel, fieldModel.get('state'), options);
|
||||
}
|
||||
else {
|
||||
fieldModel.set('state', 'candidate', options);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'closing':
|
||||
// Set all fields to the 'inactive' state.
|
||||
options.reason = 'stop';
|
||||
this.get('fields').each(function (fieldModel) {
|
||||
fieldModel.set({
|
||||
inTempStore: false,
|
||||
state: 'inactive'
|
||||
}, options);
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates a Field and Entity model's "inTempStore" when appropriate.
|
||||
*
|
||||
* Helper function.
|
||||
*
|
||||
* @param {Drupal.quickedit.EntityModel} entityModel
|
||||
* The model of the entity for which a field's state attribute has
|
||||
* changed.
|
||||
* @param {Drupal.quickedit.FieldModel} fieldModel
|
||||
* The model of the field whose state attribute has changed.
|
||||
*
|
||||
* @see Drupal.quickedit.EntityModel#fieldStateChange
|
||||
*/
|
||||
_updateInTempStoreAttributes: function (entityModel, fieldModel) {
|
||||
var current = fieldModel.get('state');
|
||||
var previous = fieldModel.previous('state');
|
||||
var fieldsInTempStore = entityModel.get('fieldsInTempStore');
|
||||
// If the fieldModel changed to the 'saved' state: remember that this
|
||||
// field was saved to PrivateTempStore.
|
||||
if (current === 'saved') {
|
||||
// Mark the entity as saved in PrivateTempStore, so that we can pass the
|
||||
// proper "reset PrivateTempStore" boolean value when communicating with
|
||||
// the server.
|
||||
entityModel.set('inTempStore', true);
|
||||
// Mark the field as saved in PrivateTempStore, so that visual
|
||||
// indicators signifying just that may be rendered.
|
||||
fieldModel.set('inTempStore', true);
|
||||
// Remember that this field is in PrivateTempStore, restore when
|
||||
// rerendered.
|
||||
fieldsInTempStore.push(fieldModel.get('fieldID'));
|
||||
fieldsInTempStore = _.uniq(fieldsInTempStore);
|
||||
entityModel.set('fieldsInTempStore', fieldsInTempStore);
|
||||
}
|
||||
// If the fieldModel changed to the 'candidate' state from the
|
||||
// 'inactive' state, then this is a field for this entity that got
|
||||
// rerendered. Restore its previous 'inTempStore' attribute value.
|
||||
else if (current === 'candidate' && previous === 'inactive') {
|
||||
fieldModel.set('inTempStore', _.intersection([fieldModel.get('fieldID')], fieldsInTempStore).length > 0);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reacts to state changes in this entity's fields.
|
||||
*
|
||||
* @param {Drupal.quickedit.FieldModel} fieldModel
|
||||
* The model of the field whose state attribute changed.
|
||||
* @param {string} state
|
||||
* The state of the associated field. One of
|
||||
* {@link Drupal.quickedit.FieldModel.states}.
|
||||
*/
|
||||
fieldStateChange: function (fieldModel, state) {
|
||||
var entityModel = this;
|
||||
var fieldState = state;
|
||||
// Switch on the entityModel state.
|
||||
// The EntityModel responds to FieldModel state changes as a function of
|
||||
// its state. For example, a field switching back to 'candidate' state
|
||||
// when its entity is in the 'opened' state has no effect on the entity.
|
||||
// But that same switch back to 'candidate' state of a field when the
|
||||
// entity is in the 'committing' state might allow the entity to proceed
|
||||
// with the commit flow.
|
||||
switch (this.get('state')) {
|
||||
case 'closed':
|
||||
case 'launching':
|
||||
// It should be impossible to reach these: fields can't change state
|
||||
// while the entity is closed or still launching.
|
||||
break;
|
||||
|
||||
case 'opening':
|
||||
// We must change the entity to the 'opened' state, but it must first
|
||||
// be confirmed that all of its fieldModels have transitioned to the
|
||||
// 'candidate' state.
|
||||
// We do this here, because this is called every time a fieldModel
|
||||
// changes state, hence each time this is called, we get closer to the
|
||||
// goal of having all fieldModels in the 'candidate' state.
|
||||
// A state change in reaction to another state change must be
|
||||
// deferred.
|
||||
_.defer(function () {
|
||||
entityModel.set('state', 'opened', {
|
||||
'accept-field-states': Drupal.quickedit.app.readyFieldStates
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case 'opened':
|
||||
// Set the isDirty attribute when appropriate so that it is known when
|
||||
// to display the "Save" button in the entity toolbar.
|
||||
// Note that once a field has been changed, there's no way to discard
|
||||
// that change, hence it will have to be saved into PrivateTempStore,
|
||||
// or the in-place editing of this field will have to be stopped
|
||||
// completely. In other words: once any field enters the 'changed'
|
||||
// field, then for the remainder of the in-place editing session, the
|
||||
// entity is by definition dirty.
|
||||
if (fieldState === 'changed') {
|
||||
entityModel.set('isDirty', true);
|
||||
}
|
||||
else {
|
||||
this._updateInTempStoreAttributes(entityModel, fieldModel);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'committing':
|
||||
// If the field save returned a validation error, set the state of the
|
||||
// entity back to 'opened'.
|
||||
if (fieldState === 'invalid') {
|
||||
// A state change in reaction to another state change must be
|
||||
// deferred.
|
||||
_.defer(function () {
|
||||
entityModel.set('state', 'opened', {reason: 'invalid'});
|
||||
});
|
||||
}
|
||||
else {
|
||||
this._updateInTempStoreAttributes(entityModel, fieldModel);
|
||||
}
|
||||
|
||||
// Attempt to save the entity. If the entity's fields are not yet all
|
||||
// in a ready state, the save will not be processed.
|
||||
var options = {
|
||||
'accept-field-states': Drupal.quickedit.app.readyFieldStates
|
||||
};
|
||||
if (entityModel.set('isCommitting', true, options)) {
|
||||
entityModel.save({
|
||||
success: function () {
|
||||
entityModel.set({
|
||||
state: 'deactivating',
|
||||
isCommitting: false
|
||||
}, {saved: true});
|
||||
},
|
||||
error: function () {
|
||||
// Reset the "isCommitting" mutex.
|
||||
entityModel.set('isCommitting', false);
|
||||
// Change the state back to "opened", to allow the user to hit
|
||||
// the "Save" button again.
|
||||
entityModel.set('state', 'opened', {reason: 'networkerror'});
|
||||
// Show a modal to inform the user of the network error.
|
||||
var message = Drupal.t('Your changes to <q>@entity-title</q> could not be saved, either due to a website problem or a network connection problem.<br>Please try again.', {'@entity-title': entityModel.get('label')});
|
||||
Drupal.quickedit.util.networkErrorModal(Drupal.t('Network problem!'), message);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deactivating':
|
||||
// When setting the entity to 'closing', require that all fieldModels
|
||||
// are in either the 'candidate' or 'highlighted' state.
|
||||
// A state change in reaction to another state change must be
|
||||
// deferred.
|
||||
_.defer(function () {
|
||||
entityModel.set('state', 'closing', {
|
||||
'accept-field-states': Drupal.quickedit.app.readyFieldStates
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case 'closing':
|
||||
// When setting the entity to 'closed', require that all fieldModels
|
||||
// are in the 'inactive' state.
|
||||
// A state change in reaction to another state change must be
|
||||
// deferred.
|
||||
_.defer(function () {
|
||||
entityModel.set('state', 'closed', {
|
||||
'accept-field-states': ['inactive']
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fires an AJAX request to the REST save URL for an entity.
|
||||
*
|
||||
* @param {object} options
|
||||
* An object of options that contains:
|
||||
* @param {function} [options.success]
|
||||
* A function to invoke if the entity is successfully saved.
|
||||
*/
|
||||
save: function (options) {
|
||||
var entityModel = this;
|
||||
|
||||
// Create a Drupal.ajax instance to save the entity.
|
||||
var entitySaverAjax = Drupal.ajax({
|
||||
url: Drupal.url('quickedit/entity/' + entityModel.get('entityID')),
|
||||
error: function () {
|
||||
// Let the Drupal.quickedit.EntityModel Backbone model's error()
|
||||
// method handle errors.
|
||||
options.error.call(entityModel);
|
||||
}
|
||||
});
|
||||
// Entity saved successfully.
|
||||
entitySaverAjax.commands.quickeditEntitySaved = function (ajax, response, status) {
|
||||
// All fields have been moved from PrivateTempStore to permanent
|
||||
// storage, update the "inTempStore" attribute on FieldModels, on the
|
||||
// EntityModel and clear EntityModel's "fieldInTempStore" attribute.
|
||||
entityModel.get('fields').each(function (fieldModel) {
|
||||
fieldModel.set('inTempStore', false);
|
||||
});
|
||||
entityModel.set('inTempStore', false);
|
||||
entityModel.set('fieldsInTempStore', []);
|
||||
|
||||
// Invoke the optional success callback.
|
||||
if (options.success) {
|
||||
options.success.call(entityModel);
|
||||
}
|
||||
};
|
||||
// Trigger the AJAX request, which will will return the
|
||||
// quickeditEntitySaved AJAX command to which we then react.
|
||||
entitySaverAjax.execute();
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate the entity 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 entity model.
|
||||
*/
|
||||
validate: function (attrs, options) {
|
||||
var acceptedFieldStates = options['accept-field-states'] || [];
|
||||
|
||||
// Validate state change.
|
||||
var currentState = this.get('state');
|
||||
var nextState = attrs.state;
|
||||
if (currentState !== nextState) {
|
||||
// Ensure it's a valid state.
|
||||
if (_.indexOf(this.constructor.states, nextState) === -1) {
|
||||
return '"' + nextState + '" is an invalid state';
|
||||
}
|
||||
|
||||
// Ensure it's a state change that is allowed.
|
||||
// Check if the acceptStateChange function accepts it.
|
||||
if (!this._acceptStateChange(currentState, nextState, options)) {
|
||||
return 'state change not accepted';
|
||||
}
|
||||
// If that function accepts it, then ensure all fields are also in an
|
||||
// acceptable state.
|
||||
else if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
|
||||
return 'state change not accepted because fields are not in acceptable state';
|
||||
}
|
||||
}
|
||||
|
||||
// Validate setting isCommitting = true.
|
||||
var currentIsCommitting = this.get('isCommitting');
|
||||
var nextIsCommitting = attrs.isCommitting;
|
||||
if (currentIsCommitting === false && nextIsCommitting === true) {
|
||||
if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
|
||||
return 'isCommitting change not accepted because fields are not in acceptable state';
|
||||
}
|
||||
}
|
||||
else if (currentIsCommitting === true && nextIsCommitting === true) {
|
||||
return 'isCommitting is a mutex, hence only changes are allowed';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if a state change can be accepted.
|
||||
*
|
||||
* @param {string} from
|
||||
* From state.
|
||||
* @param {string} to
|
||||
* To state.
|
||||
* @param {object} context
|
||||
* Context for the check.
|
||||
* @param {string} context.reason
|
||||
* The reason for the state change.
|
||||
* @param {bool} context.confirming
|
||||
* Whether context is confirming or not.
|
||||
*
|
||||
* @return {bool}
|
||||
* Whether the state change is accepted or not.
|
||||
*
|
||||
* @see Drupal.quickedit.AppView#acceptEditorStateChange
|
||||
*/
|
||||
_acceptStateChange: function (from, to, context) {
|
||||
var accept = true;
|
||||
|
||||
// In general, enforce the states sequence. Disallow going back from a
|
||||
// "later" state to an "earlier" state, except in explicitly allowed
|
||||
// cases.
|
||||
if (!this.constructor.followsStateSequence(from, to)) {
|
||||
accept = false;
|
||||
|
||||
// Allow: closing -> closed.
|
||||
// Necessary to stop editing an entity.
|
||||
if (from === 'closing' && to === 'closed') {
|
||||
accept = true;
|
||||
}
|
||||
// Allow: committing -> opened.
|
||||
// Necessary to be able to correct an invalid field, or to hit the
|
||||
// "Save" button again after a server/network error.
|
||||
else if (from === 'committing' && to === 'opened' && context.reason && (context.reason === 'invalid' || context.reason === 'networkerror')) {
|
||||
accept = true;
|
||||
}
|
||||
// Allow: deactivating -> opened.
|
||||
// Necessary to be able to confirm changes with the user.
|
||||
else if (from === 'deactivating' && to === 'opened' && context.confirming) {
|
||||
accept = true;
|
||||
}
|
||||
// Allow: opened -> deactivating.
|
||||
// Necessary to be able to stop editing.
|
||||
else if (from === 'opened' && to === 'deactivating' && context.confirmed) {
|
||||
accept = true;
|
||||
}
|
||||
}
|
||||
|
||||
return accept;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if fields have acceptable states.
|
||||
*
|
||||
* @param {Array} acceptedFieldStates
|
||||
* An array of acceptable field states to check for.
|
||||
*
|
||||
* @return {bool}
|
||||
* Whether the fields have an acceptable state.
|
||||
*
|
||||
* @see Drupal.quickedit.EntityModel#validate
|
||||
*/
|
||||
_fieldsHaveAcceptableStates: function (acceptedFieldStates) {
|
||||
var accept = true;
|
||||
|
||||
// If no acceptable field states are provided, assume all field states are
|
||||
// acceptable. We want to let validation pass as a default and only
|
||||
// check validity on calls to set that explicitly request it.
|
||||
if (acceptedFieldStates.length > 0) {
|
||||
var fieldStates = this.get('fields').pluck('state') || [];
|
||||
// If not all fields are in one of the accepted field states, then we
|
||||
// still can't allow this state change.
|
||||
if (_.difference(fieldStates, acceptedFieldStates).length) {
|
||||
accept = false;
|
||||
}
|
||||
}
|
||||
|
||||
return accept;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys the entity model.
|
||||
*
|
||||
* @param {object} options
|
||||
* Options for the entity model.
|
||||
*/
|
||||
destroy: function (options) {
|
||||
Drupal.quickedit.BaseModel.prototype.destroy.call(this, options);
|
||||
|
||||
this.stopListening();
|
||||
|
||||
// Destroy all fields of this entity.
|
||||
this.get('fields').reset();
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
sync: function () {
|
||||
// We don't use REST updates to sync.
|
||||
return;
|
||||
}
|
||||
|
||||
}, /** @lends Drupal.quickedit.EntityModel */{
|
||||
|
||||
/**
|
||||
* Sequence of all possible states an entity can be in during quickediting.
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
states: [
|
||||
// Initial state, like field's 'inactive' OR the user has just finished
|
||||
// in-place editing this entity.
|
||||
// - Trigger: none (initial) or EntityModel (finished).
|
||||
// - Expected behavior: (when not initial state): tear down
|
||||
// EntityToolbarView, in-place editors and related views.
|
||||
'closed',
|
||||
// User has activated in-place editing of this entity.
|
||||
// - Trigger: user.
|
||||
// - Expected behavior: the EntityToolbarView is gets set up, in-place
|
||||
// editors (EditorViews) and related views for this entity's fields are
|
||||
// set up. Upon completion of those, the state is changed to 'opening'.
|
||||
'launching',
|
||||
// Launching has finished.
|
||||
// - Trigger: application.
|
||||
// - Guarantees: in-place editors ready for use, all entity and field
|
||||
// views have been set up, all fields are in the 'inactive' state.
|
||||
// - Expected behavior: all fields are changed to the 'candidate' state
|
||||
// and once this is completed, the entity state will be changed to
|
||||
// 'opened'.
|
||||
'opening',
|
||||
// Opening has finished.
|
||||
// - Trigger: EntityModel.
|
||||
// - Guarantees: see 'opening', all fields are in the 'candidate' state.
|
||||
// - Expected behavior: the user is able to actually use in-place editing.
|
||||
'opened',
|
||||
// User has clicked the 'Save' button (and has thus changed at least one
|
||||
// field).
|
||||
// - Trigger: user.
|
||||
// - Guarantees: see 'opened', plus: either a changed field is in
|
||||
// PrivateTempStore, or the user has just modified a field without
|
||||
// activating (switching to) another field.
|
||||
// - Expected behavior: 1) if any of the fields are not yet in
|
||||
// PrivateTempStore, save them to PrivateTempStore, 2) if then any of
|
||||
// the fields has the 'invalid' state, then change the entity state back
|
||||
// to 'opened', otherwise: save the entity by committing it from
|
||||
// PrivateTempStore into permanent storage.
|
||||
'committing',
|
||||
// User has clicked the 'Close' button, or has clicked the 'Save' button
|
||||
// and that was successfully completed.
|
||||
// - Trigger: user or EntityModel.
|
||||
// - Guarantees: when having clicked 'Close' hardly any: fields may be in
|
||||
// a variety of states; when having clicked 'Save': all fields are in
|
||||
// the 'candidate' state.
|
||||
// - Expected behavior: transition all fields to the 'candidate' state,
|
||||
// possibly requiring confirmation in the case of having clicked
|
||||
// 'Close'.
|
||||
'deactivating',
|
||||
// Deactivation has been completed.
|
||||
// - Trigger: EntityModel.
|
||||
// - Guarantees: all fields are in the 'candidate' state.
|
||||
// - Expected behavior: change all fields to the 'inactive' state.
|
||||
'closing'
|
||||
],
|
||||
|
||||
/**
|
||||
* Indicates whether the 'from' state comes before the 'to' state.
|
||||
*
|
||||
* @param {string} from
|
||||
* One of {@link Drupal.quickedit.EntityModel.states}.
|
||||
* @param {string} to
|
||||
* One of {@link Drupal.quickedit.EntityModel.states}.
|
||||
*
|
||||
* @return {bool}
|
||||
* Whether the 'from' state comes before the 'to' state.
|
||||
*/
|
||||
followsStateSequence: function (from, to) {
|
||||
return _.indexOf(this.states, from) < _.indexOf(this.states, to);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
Drupal.quickedit.EntityCollection = Backbone.Collection.extend(/** @lends Drupal.quickedit.EntityCollection# */{
|
||||
|
||||
/**
|
||||
* @type {Drupal.quickedit.EntityModel}
|
||||
*/
|
||||
model: Drupal.quickedit.EntityModel
|
||||
});
|
||||
|
||||
}(_, jQuery, Backbone, Drupal));
|
348
web/core/modules/quickedit/js/models/FieldModel.js
Normal file
348
web/core/modules/quickedit/js/models/FieldModel.js
Normal file
|
@ -0,0 +1,348 @@
|
|||
/**
|
||||
* @file
|
||||
* A Backbone Model for the state of an in-place editable field in the DOM.
|
||||
*/
|
||||
|
||||
(function (_, Backbone, Drupal) {
|
||||
|
||||
'use strict';
|
||||
|
||||
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: 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);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys the field model.
|
||||
*
|
||||
* @param {object} options
|
||||
* Options for the field model.
|
||||
*/
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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: 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;
|
||||
}
|
||||
|
||||
}, /** @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: function (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));
|
Reference in a new issue