Commit 1069bef4 authored by Dries's avatar Dries

Issue #2141055 by Wim Leers: When multiple instances of the same entity on one...

Issue #2141055 by Wim Leers: When multiple instances of the same entity on one page, only the first can be edited.
parent 568fef4c
......@@ -55,11 +55,35 @@ var fieldsAvailableQueue = [];
*/
var contextualLinksQueue = [];
/**
* Tracks how many instances exist for each unique entity. Contains key-value
* pairs:
* - String entityID
* - Number count
*/
var entityInstancesTracker = {};
Drupal.behaviors.edit = {
attach: function (context) {
// Initialize the Edit app once per page load.
$('body').once('edit-init', initEdit);
// Process each entity element: identical entities that appear multiple
// times will get a numeric identifier, starting at 0.
$(context).find('[data-edit-entity-id]').once('edit').each(function (index, entityElement) {
var entityID = entityElement.getAttribute('data-edit-entity-id');
if (!entityInstancesTracker.hasOwnProperty(entityID)) {
entityInstancesTracker[entityID] = 0;
}
else {
entityInstancesTracker[entityID]++;
}
// Set the calculated entity instance ID for this element.
var entityInstanceID = entityInstancesTracker[entityID];
entityElement.setAttribute('data-edit-entity-instance-id', entityInstanceID);
});
// Process each field element: queue to be used or to fetch metadata.
// When a field is being rerendered after editing, it will be processed
// immediately. New fields will be unable to be processed immediately, but
......@@ -123,6 +147,7 @@ $(document).on('drupalContextualLinkAdded', function (event, data) {
if (data.$region.is('[data-edit-entity-id]')) {
var contextualLink = {
entityID: data.$region.attr('data-edit-entity-id'),
entityInstanceID: data.$region.attr('data-edit-entity-instance-id'),
el: data.$el[0],
region: data.$region[0]
};
......@@ -176,13 +201,20 @@ function processField (fieldElement) {
var metadata = Drupal.edit.metadata;
var fieldID = fieldElement.getAttribute('data-edit-field-id');
var entityID = extractEntityID(fieldID);
// Figure out the instance ID by looking at the ancestor [data-edit-entity-id]
// element's data-edit-entity-instance-id attribute.
var entityInstanceID = $(fieldElement)
.closest('[data-edit-entity-id="' + entityID + '"]')
.get(0)
.getAttribute('data-edit-entity-instance-id');
// Early-return if metadata for this field is missing.
if (!metadata.has(fieldID)) {
fieldsMetadataQueue.push({
el: fieldElement,
fieldID: fieldID,
entityID: entityID
entityID: entityID,
entityInstanceID: entityInstanceID
});
return;
}
......@@ -193,13 +225,13 @@ function processField (fieldElement) {
// If an EntityModel for this field already exists (and hence also a "Quick
// edit" contextual link), then initialize it immediately.
if (Drupal.edit.collections.entities.where({ id: entityID }).length > 0) {
initializeField(fieldElement, fieldID);
if (Drupal.edit.collections.entities.where({ entityID: entityID, entityInstanceID: entityInstanceID }).length > 0) {
initializeField(fieldElement, fieldID, entityID, entityInstanceID);
}
// Otherwise: queue the field. It is now available to be set up when its
// corresponding entity becomes in-place editable.
else {
fieldsAvailableQueue.push({ el: fieldElement, fieldID: fieldID, entityID: entityID });
fieldsAvailableQueue.push({ el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID });
}
}
......@@ -210,17 +242,24 @@ function processField (fieldElement) {
* The field's DOM element.
* @param String fieldID
* The field's ID.
* @param String entityID
* The field's entity's ID.
* @param String entityInstanceID
* The field's entity's instance ID.
*/
function initializeField (fieldElement, fieldID) {
var entityId = extractEntityID(fieldID);
var entity = Drupal.edit.collections.entities.where({ id: entityId })[0];
function initializeField (fieldElement, fieldID, entityID, entityInstanceID) {
var entity = Drupal.edit.collections.entities.where({
entityID: entityID,
entityInstanceID: entityInstanceID
})[0];
$(fieldElement).addClass('edit-field');
// The FieldModel stores the state of an in-place editable entity field.
var field = new Drupal.edit.FieldModel({
el: fieldElement,
id: fieldID,
fieldID: fieldID,
id: fieldID + '[' + entity.get('entityInstanceID') + ']',
entity: entity,
metadata: Drupal.edit.metadata.get(fieldID),
acceptStateChange: _.bind(Drupal.edit.app.acceptEditorStateChange, Drupal.edit.app)
......@@ -282,8 +321,7 @@ function loadMissingEditors (callback) {
var loadedEditors = _.keys(Drupal.edit.editors);
var missingEditors = [];
Drupal.edit.collections.fields.each(function (fieldModel) {
var id = fieldModel.id;
var metadata = Drupal.edit.metadata.get(id);
var metadata = Drupal.edit.metadata.get(fieldModel.get('fieldID'));
if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
missingEditors.push(metadata.editor);
}
......@@ -325,8 +363,11 @@ function loadMissingEditors (callback) {
*
* @param Object contextualLink
* An object with the following properties:
* - String entity: an Edit entity identifier, e.g. "node/1" or
* - String entityID: an Edit entity identifier, e.g. "node/1" or
* "custom_block/5".
* - String entityInstanceID: an Edit entity instance identifier, e.g. 0, 1
* or n (depending on whether it's the first, second, or n+1st instance of
* this entity).
* - DOM el: element pointing to the contextual links placeholder for this
* entity.
* - DOM region: element pointing to the contextual region for this entity.
......@@ -355,8 +396,11 @@ function initializeEntityContextualLink (contextualLink) {
return fieldIDs.length === metadata.intersection(fieldIDs).length;
}
// Find all fields for this entity and collect their field IDs.
var fields = _.where(fieldsAvailableQueue, { entityID: contextualLink.entityID });
// Find all fields for this entity instance and collect their field IDs.
var fields = _.where(fieldsAvailableQueue, {
entityID: contextualLink.entityID,
entityInstanceID: contextualLink.entityInstanceID
});
var fieldIDs = _.pluck(fields, 'fieldID');
// No fields found yet.
......@@ -369,7 +413,9 @@ function initializeEntityContextualLink (contextualLink) {
else if (hasFieldWithPermission(fieldIDs)) {
var entityModel = new Drupal.edit.EntityModel({
el: contextualLink.region,
id: contextualLink.entityID,
entityID: contextualLink.entityID,
entityInstanceID: contextualLink.entityInstanceID,
id: contextualLink.entityID + '[' + contextualLink.entityInstanceID + ']',
label: Drupal.edit.metadata.get(contextualLink.entityID, 'label')
});
Drupal.edit.collections.entities.add(entityModel);
......@@ -383,7 +429,7 @@ function initializeEntityContextualLink (contextualLink) {
// Initialize all queued fields within this entity (creates FieldModels).
_.each(fields, function (field) {
initializeField(field.el, field.fieldID);
initializeField(field.el, field.fieldID, contextualLink.entityID, contextualLink.entityInstanceID);
});
fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
......
......@@ -66,7 +66,7 @@ Drupal.edit.editors.form = Drupal.edit.EditorView.extend({
var fieldModel = this.fieldModel;
// Generate a DOM-compatible ID for the form container DOM element.
var id = 'edit-form-for-' + fieldModel.id.replace(/\//g, '_');
var id = 'edit-form-for-' + fieldModel.id.replace(/[\/\[\]]/g, '_');
// Render form container.
var $formContainer = this.$formContainer = $(Drupal.theme('editFormContainer', {
......@@ -91,7 +91,7 @@ Drupal.edit.editors.form = Drupal.edit.EditorView.extend({
// Load form, insert it into the form container and attach event handlers.
var formOptions = {
fieldID: fieldModel.id,
fieldID: fieldModel.get('fieldID'),
$el: this.$el,
nocssjs: false,
// Reset an existing entry for this entity in the TempStore (if any) when
......
......@@ -15,6 +15,12 @@ Drupal.edit.EntityModel = Backbone.Model.extend({
// entities in the DOM to EntityModels in memory.
el: null,
// An entity ID, of the form "<entity type>/<entity ID>", e.g. "node/1".
entityID: null,
// An entity instance ID. The first intance of a specific entity (i.e. with
// a given entity ID) is assigned 0, the second 1, and so on.
entityInstanceID: null,
// The unique ID of this entity instance on the page, of the form "<entity
// type>/<entity ID>[entity instance ID]", e.g. "node/1[0]".
id: null,
// The label of the entity.
label: null,
......@@ -250,7 +256,7 @@ Drupal.edit.EntityModel = Backbone.Model.extend({
// signifying just that may be rendered.
fieldModel.set('inTempStore', true);
// Remember that this field is in TempStore, restore when rerendered.
fieldsInTempStore.push(fieldModel.id);
fieldsInTempStore.push(fieldModel.get('fieldID'));
fieldsInTempStore = _.uniq(fieldsInTempStore);
entityModel.set('fieldsInTempStore', fieldsInTempStore);
}
......@@ -258,7 +264,7 @@ Drupal.edit.EntityModel = Backbone.Model.extend({
// 'inactive' state, then this is a field for this entity that got
// rerendered. Restore its previous 'inTempStore' attribute value.
else if (fieldState === 'candidate' && fieldModel.previous('state') === 'inactive') {
fieldModel.set('inTempStore', _.intersection([fieldModel.id], fieldsInTempStore).length > 0);
fieldModel.set('inTempStore', _.intersection([fieldModel.get('fieldID')], fieldsInTempStore).length > 0);
}
break;
......@@ -275,7 +281,7 @@ Drupal.edit.EntityModel = Backbone.Model.extend({
// 'inactive' state, then this is a field for this entity that got
// rerendered. Restore its previous 'inTempStore' attribute value.
else if (fieldState === 'candidate' && fieldModel.previous('state') === 'inactive') {
fieldModel.set('inTempStore', _.intersection([fieldModel.id], fieldsInTempStore).length > 0);
fieldModel.set('inTempStore', _.intersection([fieldModel.get('fieldID')], fieldsInTempStore).length > 0);
}
// Attempt to save the entity. If the entity's fields are not yet all in
......@@ -337,7 +343,7 @@ Drupal.edit.EntityModel = Backbone.Model.extend({
var $el = $('#edit-entity-toolbar').find('.action-save'); // This is the span element inside the button.
// Create a Drupal.ajax instance to save the entity.
var entitySaverAjax = new Drupal.ajax(id, $el, {
url: Drupal.url('edit/entity/' + entityModel.id),
url: Drupal.url('edit/entity/' + entityModel.get('entityID')),
event: 'edit-save.edit',
progress: { type: 'none' }
});
......
......@@ -20,6 +20,10 @@ Drupal.edit.FieldModel = Backbone.Model.extend({
// 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.edit.EntityModel. Its "fields" attribute, which is a
// FieldCollection, is automatically updated to include this FieldModel.
......@@ -107,7 +111,7 @@ Drupal.edit.FieldModel = Backbone.Model.extend({
* An entity ID: a string of the format `<entity type>/<id>`.
*/
getEntityID: function () {
return this.id.split('/').slice(0, 2).join('/');
return this.get('fieldID').split('/').slice(0, 2).join('/');
}
}, {
......
......@@ -179,7 +179,7 @@ Drupal.edit.EditorView = Backbone.View.extend({
save: function () {
var fieldModel = this.fieldModel;
var editorModel = this.model;
var backstageId = 'edit_backstage-' + this.fieldModel.id.replace(/[\/\_\s]/g, '-');
var backstageId = 'edit_backstage-' + this.fieldModel.id.replace(/[\/\[\]\_\s]/g, '-');
function fillAndSubmitForm (value) {
var $form = $('#' + backstageId).find('form');
......@@ -193,7 +193,7 @@ Drupal.edit.EditorView = Backbone.View.extend({
}
var formOptions = {
fieldID: this.fieldModel.id,
fieldID: this.fieldModel.get('fieldID'),
$el: this.$el,
nocssjs: true,
// Reset an existing entry for this entity in the TempStore (if any) when
......
......@@ -26,7 +26,7 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
this.$root = this.$el;
// Generate a DOM-compatible ID for the form container DOM element.
this._id = 'edit-toolbar-for-' + this.model.id.replace(/\//g, '_');
this._id = 'edit-toolbar-for-' + this.model.id.replace(/[\/\[\]]/g, '_');
this.model.on('change:state', this.stateChange, this);
},
......
......@@ -34,7 +34,7 @@ Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
initialize: function (options) {
Drupal.edit.EditorView.prototype.initialize.call(this, options);
var metadata = Drupal.edit.metadata.get(this.fieldModel.id, 'custom');
var metadata = Drupal.edit.metadata.get(this.fieldModel.get('fieldID'), 'custom');
this.textFormat = drupalSettings.editor.formats[metadata.format];
this.textFormatHasTransformations = metadata.formatHasTransformations;
this.textEditor = Drupal.editors[this.textFormat.editor];
......@@ -160,7 +160,7 @@ Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
* @see \Drupal\editor\Ajax\GetUntransformedTextCommand
*/
_getUntransformedText: function (callback) {
var fieldID = this.fieldModel.id;
var fieldID = this.fieldModel.get('fieldID');
// Create a Drupal.ajax instance to load the form.
var textLoaderAjax = new Drupal.ajax(fieldID, this.$el, {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment