Commit 1069bef4 authored by Dries Buytaert's avatar Dries Buytaert
Browse files

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
Loading
Loading
Loading
Loading
+61 −15
Changes for core/modules/edit/js/edit.js: 61 added lines, 15 removed lines.
Original line number Diff line number Diff line
@@ -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);

+2 −2
Changes for core/modules/edit/js/editors/formEditor.js: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -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
+10 −4
Changes for core/modules/edit/js/models/EntityModel.js: 10 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -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' }
    });
+5 −1
Changes for core/modules/edit/js/models/FieldModel.js: 5 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -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('/');
  }

}, {
+2 −2
Changes for core/modules/edit/js/views/EditorView.js: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -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
Loading