Issue #1981760 by Wim Leers, nod_: Edit should use the new drupalContextualLinkAdded event to cleanly instead of hackily insert its 'Quick edit' contextual link.
parent
948c90fc42
commit
cbdaaaf21e
|
@ -6,22 +6,22 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Drupal.edit = Drupal.edit || {};
|
Drupal.edit = { metadataCache: {}, contextualLinksQueue: [] };
|
||||||
Drupal.edit.metadataCache = Drupal.edit.metadataCache || {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach toggling behavior and in-place editing.
|
* Attach toggling behavior and in-place editing.
|
||||||
*/
|
*/
|
||||||
Drupal.behaviors.edit = {
|
Drupal.behaviors.edit = {
|
||||||
attach: function(context) {
|
attach: function (context) {
|
||||||
var $context = $(context);
|
var $context = $(context);
|
||||||
var $fields = $context.find('[data-edit-id]');
|
var $fields = $context.find('[data-edit-id]');
|
||||||
|
|
||||||
// Initialize the Edit app.
|
// Initialize the Edit app.
|
||||||
$('body').once('edit-init', Drupal.edit.init);
|
$('body').once('edit-init', Drupal.edit.init);
|
||||||
|
|
||||||
var annotateField = function(field) {
|
function annotateField (field) {
|
||||||
if (_.has(Drupal.edit.metadataCache, field.editID)) {
|
var hasField = _.has(Drupal.edit.metadataCache, field.editID);
|
||||||
|
if (hasField) {
|
||||||
var meta = Drupal.edit.metadataCache[field.editID];
|
var meta = Drupal.edit.metadataCache[field.editID];
|
||||||
|
|
||||||
field.$el.addClass((meta.access) ? 'edit-allowed' : 'edit-disallowed');
|
field.$el.addClass((meta.access) ? 'edit-allowed' : 'edit-disallowed');
|
||||||
|
@ -31,21 +31,19 @@ Drupal.behaviors.edit = {
|
||||||
.attr('aria-label', meta.aria)
|
.attr('aria-label', meta.aria)
|
||||||
.addClass('edit-field edit-type-' + ((meta.editor === 'form') ? 'form' : 'direct'));
|
.addClass('edit-field edit-type-' + ((meta.editor === 'form') ? 'form' : 'direct'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return hasField;
|
||||||
};
|
}
|
||||||
|
|
||||||
// Find all fields in the context without metadata.
|
// Find all fields in the context without metadata.
|
||||||
var fieldsToAnnotate = _.map($fields.not('.edit-allowed, .edit-disallowed'), function(el) {
|
var fieldsToAnnotate = _.map($fields.not('.edit-allowed, .edit-disallowed'), function (el) {
|
||||||
var $el = $(el);
|
var $el = $(el);
|
||||||
return { $el: $el, editID: $el.attr('data-edit-id') };
|
return { $el: $el, editID: $el.attr('data-edit-id') };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fields whose metadata is known (typically when they were just modified)
|
// Fields whose metadata is known (typically when they were just modified)
|
||||||
// can be annotated immediately, those remaining must be requested.
|
// can be annotated immediately, those remaining must be requested.
|
||||||
var remainingFieldsToAnnotate = _.reduce(fieldsToAnnotate, function(result, field) {
|
var remainingFieldsToAnnotate = _.reduce(fieldsToAnnotate, function (result, field) {
|
||||||
if (!annotateField(field)) {
|
if (!annotateField(field)) {
|
||||||
result.push(field);
|
result.push(field);
|
||||||
}
|
}
|
||||||
|
@ -56,7 +54,7 @@ Drupal.behaviors.edit = {
|
||||||
Drupal.edit.app.findEditableProperties($context);
|
Drupal.edit.app.findEditableProperties($context);
|
||||||
|
|
||||||
if (remainingFieldsToAnnotate.length) {
|
if (remainingFieldsToAnnotate.length) {
|
||||||
$(window).ready(function() {
|
$(window).ready(function () {
|
||||||
var id = 'edit-load-metadata';
|
var id = 'edit-load-metadata';
|
||||||
// Create a temporary element to be able to use Drupal.ajax.
|
// Create a temporary element to be able to use Drupal.ajax.
|
||||||
var $el = jQuery('<div id="' + id + '" class="element-hidden"></div>').appendTo('body');
|
var $el = jQuery('<div id="' + id + '" class="element-hidden"></div>').appendTo('body');
|
||||||
|
@ -64,13 +62,14 @@ Drupal.behaviors.edit = {
|
||||||
Drupal.ajax[id] = new Drupal.ajax(id, $el, {
|
Drupal.ajax[id] = new Drupal.ajax(id, $el, {
|
||||||
url: drupalSettings.edit.metadataURL,
|
url: drupalSettings.edit.metadataURL,
|
||||||
event: 'edit-internal.edit',
|
event: 'edit-internal.edit',
|
||||||
submit: { 'fields[]' : _.pluck(remainingFieldsToAnnotate, 'editID') },
|
submit: { 'fields[]': _.pluck(remainingFieldsToAnnotate, 'editID') },
|
||||||
progress: { type : null } // No progress indicator.
|
// No progress indicator.
|
||||||
|
progress: { type: null }
|
||||||
});
|
});
|
||||||
// Implement a scoped editMetaData AJAX command: calls the callback.
|
// Implement a scoped editMetaData AJAX command: calls the callback.
|
||||||
Drupal.ajax[id].commands.editMetadata = function(ajax, response, status) {
|
Drupal.ajax[id].commands.editMetadata = function (ajax, response, status) {
|
||||||
// Update the metadata cache.
|
// Update the metadata cache.
|
||||||
_.each(response.data, function(metadata, editID) {
|
_.each(response.data, function (metadata, editID) {
|
||||||
Drupal.edit.metadataCache[editID] = metadata;
|
Drupal.edit.metadataCache[editID] = metadata;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -80,6 +79,12 @@ Drupal.behaviors.edit = {
|
||||||
// Find editable fields, make them editable.
|
// Find editable fields, make them editable.
|
||||||
Drupal.edit.app.findEditableProperties($context);
|
Drupal.edit.app.findEditableProperties($context);
|
||||||
|
|
||||||
|
// Metadata cache has been updated, try to set up more contextual
|
||||||
|
// links now.
|
||||||
|
Drupal.edit.contextualLinksQueue = _.filter(Drupal.edit.contextualLinksQueue, function (data) {
|
||||||
|
return !Drupal.edit.setUpContextualLink(data);
|
||||||
|
});
|
||||||
|
|
||||||
// Delete the Drupal.ajax instance that called this very function.
|
// Delete the Drupal.ajax instance that called this very function.
|
||||||
delete Drupal.ajax[id];
|
delete Drupal.ajax[id];
|
||||||
|
|
||||||
|
@ -93,34 +98,98 @@ Drupal.behaviors.edit = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Drupal.edit.init = function() {
|
/**
|
||||||
|
* Detect contextual links on entities annotated by Edit; queue these to be
|
||||||
|
* processed.
|
||||||
|
*/
|
||||||
|
$(document).on('drupalContextualLinkAdded', function (event, data) {
|
||||||
|
if (data.$region.is('[data-edit-entity]')) {
|
||||||
|
var contextualLink = {
|
||||||
|
entity: data.$region.attr('data-edit-entity'),
|
||||||
|
$el: data.$el,
|
||||||
|
$region: data.$region
|
||||||
|
};
|
||||||
|
// Set up contextual links for this, otherwise queue it to be set up later.
|
||||||
|
if (!Drupal.edit.setUpContextualLink(contextualLink)) {
|
||||||
|
Drupal.edit.contextualLinksQueue.push(contextualLink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to set up a "Quick edit" contextual link.
|
||||||
|
*
|
||||||
|
* @param Object contextualLink
|
||||||
|
* An object with the following properties:
|
||||||
|
* - entity: an Edit entity identifier, e.g. "node/1" or "custom_block/5".
|
||||||
|
* - $el: a jQuery element pointing to the contextual links for this entity.
|
||||||
|
* - $region: a jQuery element pointing to the contextual region for this
|
||||||
|
* entity.
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
* Returns true when a contextual the given contextual link metadata can be
|
||||||
|
* removed from the queue (either because the contextual link has been set up
|
||||||
|
* or because it is certain that in-place editing is not allowed for any of
|
||||||
|
* its fields).
|
||||||
|
* Returns false otherwise.
|
||||||
|
*/
|
||||||
|
Drupal.edit.setUpContextualLink = function (contextualLink) {
|
||||||
|
// Check if the user has permission to edit at least one of them.
|
||||||
|
function hasFieldWithPermission (editIDs) {
|
||||||
|
var i, meta = Drupal.edit.metadataCache;
|
||||||
|
for (i = 0; i < editIDs.length; i++) {
|
||||||
|
var editID = editIDs[i];
|
||||||
|
if (_.has(meta, editID) && meta[editID].access === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the metadata for all given editIDs exists.
|
||||||
|
function allMetadataExists (editIDs) {
|
||||||
|
var editIDsWithMetadata = _.intersection(editIDs, _.keys(Drupal.edit.metadataCache));
|
||||||
|
return editIDs.length === editIDsWithMetadata.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the Edit IDs of all fields within this entity.
|
||||||
|
var editIDs = [];
|
||||||
|
contextualLink.$region
|
||||||
|
.find('[data-edit-id^="' + contextualLink.entity + '/"]')
|
||||||
|
.each(function () {
|
||||||
|
editIDs.push($(this).attr('data-edit-id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// The entity for the given contextual link contains at least one field that
|
||||||
|
// the current user may edit in-place; instantiate ContextualLinkView.
|
||||||
|
if (hasFieldWithPermission(editIDs)) {
|
||||||
|
new Drupal.edit.views.ContextualLinkView({
|
||||||
|
el: $('<li class="quick-edit"><a href=""></a></li>').prependTo(contextualLink.$el),
|
||||||
|
model: Drupal.edit.app.model,
|
||||||
|
entity: contextualLink.entity
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// There was not at least one field that the current user may edit in-place,
|
||||||
|
// even though the metadata for all fields within this entity is available.
|
||||||
|
else if (allMetadataExists(editIDs)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Drupal.edit.init = function () {
|
||||||
// Instantiate EditAppView, which is the controller of it all. EditAppModel
|
// Instantiate EditAppView, which is the controller of it all. EditAppModel
|
||||||
// instance tracks global state (viewing/editing in-place).
|
// instance tracks global state (viewing/editing in-place).
|
||||||
var appModel = new Drupal.edit.models.EditAppModel();
|
var appModel = new Drupal.edit.models.EditAppModel();
|
||||||
var app = new Drupal.edit.EditAppView({
|
|
||||||
el: $('body'),
|
|
||||||
model: appModel
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add "Quick edit" links to all contextual menus where editing the full
|
|
||||||
// node is possible.
|
|
||||||
// @todo Generalize this to work for all entities.
|
|
||||||
$('ul.contextual-links li.node-edit')
|
|
||||||
.before('<li class="quick-edit"></li>')
|
|
||||||
.each(function() {
|
|
||||||
// Instantiate ContextualLinkView.
|
|
||||||
var $editContextualLink = $(this).prev();
|
|
||||||
var editContextualLinkView = new Drupal.edit.views.ContextualLinkView({
|
|
||||||
el: $editContextualLink.get(0),
|
|
||||||
model: appModel,
|
|
||||||
entity: $editContextualLink.parents('[data-edit-entity]').attr('data-edit-entity')
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// For now, we work with a singleton app, because for Drupal.behaviors to be
|
// For now, we work with a singleton app, because for Drupal.behaviors to be
|
||||||
// able to discover new editable properties that get AJAXed in, it must know
|
// able to discover new editable properties that get AJAXed in, it must know
|
||||||
// with which app instance they should be associated.
|
// with which app instance they should be associated.
|
||||||
Drupal.edit.app = app;
|
Drupal.edit.app = new Drupal.edit.EditAppView({
|
||||||
|
el: $('body'),
|
||||||
|
model: appModel
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
})(jQuery, _, Backbone, Drupal, drupalSettings);
|
})(jQuery, _, Backbone, Drupal, drupalSettings);
|
||||||
|
|
|
@ -12,8 +12,16 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
|
||||||
|
|
||||||
entity: null,
|
entity: null,
|
||||||
|
|
||||||
events: {
|
events: function () {
|
||||||
'click': 'onClick'
|
// Prevents delay and simulated mouse events.
|
||||||
|
function touchEndToClick (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.target.click();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'click a': 'onClick',
|
||||||
|
'touchEnd a': touchEndToClick
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +54,7 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
var that = this;
|
var that = this;
|
||||||
var updateActiveEntity = function() {
|
function updateActiveEntity () {
|
||||||
// The active entity is the current entity, i.e. stop editing the current
|
// The active entity is the current entity, i.e. stop editing the current
|
||||||
// entity.
|
// entity.
|
||||||
if (that.model.get('activeEntity') === that.entity) {
|
if (that.model.get('activeEntity') === that.entity) {
|
||||||
|
@ -57,7 +65,7 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
|
||||||
else {
|
else {
|
||||||
that.model.set('activeEntity', that.entity);
|
that.model.set('activeEntity', that.entity);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// If there's an active editor, attempt to set its state to 'candidate', and
|
// If there's an active editor, attempt to set its state to 'candidate', and
|
||||||
// only then do what the user asked.
|
// only then do what the user asked.
|
||||||
|
@ -67,7 +75,7 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
|
||||||
if (activeEditor) {
|
if (activeEditor) {
|
||||||
var editableEntity = activeEditor.options.widget;
|
var editableEntity = activeEditor.options.widget;
|
||||||
var predicate = activeEditor.options.property;
|
var predicate = activeEditor.options.property;
|
||||||
editableEntity.setState('candidate', predicate, { reason: 'stop or switch' }, function(accepted) {
|
editableEntity.setState('candidate', predicate, { reason: 'stop or switch' }, function (accepted) {
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
updateActiveEntity();
|
updateActiveEntity();
|
||||||
}
|
}
|
||||||
|
@ -88,7 +96,7 @@ Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
|
||||||
render: function () {
|
render: function () {
|
||||||
var activeEntity = this.model.get('activeEntity');
|
var activeEntity = this.model.get('activeEntity');
|
||||||
var string = (activeEntity !== this.entity) ? Drupal.t('Quick edit') : Drupal.t('Stop quick edit');
|
var string = (activeEntity !== this.entity) ? Drupal.t('Quick edit') : Drupal.t('Stop quick edit');
|
||||||
this.$el.html('<a href="">' + string + '</a>');
|
this.$el.find('a').text(string);
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue