core/modules/edit/edit.module | 1 -
core/modules/edit/edit.routing.yml | 7 ++
core/modules/edit/js/edit.js | 96 ++++++++++++++------
.../edit/lib/Drupal/edit/Ajax/MetadataCommand.php | 27 ------
.../edit/lib/Drupal/edit/EditController.php | 30 +++---
.../edit/lib/Drupal/edit/Tests/EditLoadingTest.php | 78 ++++++++++++----
6 files changed, 151 insertions(+), 88 deletions(-)
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index 5b11560..c29672e 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -52,7 +52,6 @@ function edit_page_build(&$page) {
$page['#attached']['js'][] = array(
'type' => 'setting',
'data' => array('edit' => array(
- 'metadataURL' => url('edit/metadata'),
'fieldFormURL' => url('edit/form/!entity_type/!id/!field_name/!langcode/!view_mode'),
'context' => 'body',
)),
diff --git a/core/modules/edit/edit.routing.yml b/core/modules/edit/edit.routing.yml
index b2c0b43..50aa2c9 100644
--- a/core/modules/edit/edit.routing.yml
+++ b/core/modules/edit/edit.routing.yml
@@ -5,6 +5,13 @@ edit_metadata:
requirements:
_permission: 'access in-place editing'
+edit_attachments:
+ pattern: '/edit/attachments'
+ defaults:
+ _controller: '\Drupal\edit\EditController::attachments'
+ requirements:
+ _permission: 'access in-place editing'
+
edit_field_form:
pattern: '/edit/form/{entity_type}/{entity}/{field_name}/{langcode}/{view_mode_id}'
defaults:
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
index 36c884f..102fef7 100644
--- a/core/modules/edit/js/edit.js
+++ b/core/modules/edit/js/edit.js
@@ -234,37 +234,73 @@ function fetchMissingMetadata (callback) {
var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
fieldsMetadataQueue = [];
- $(window).ready(function () {
- var id = 'edit-load-metadata';
- // Create a temporary element to be able to use Drupal.ajax.
- var $el = $('
').appendTo('body');
- // Create a Drupal.ajax instance to load the form.
- Drupal.ajax[id] = new Drupal.ajax(id, $el, {
- url: drupalSettings.edit.metadataURL,
- event: 'edit-internal.edit',
- submit: { 'fields[]': fieldIDs },
- // No progress indicator.
- progress: { type: null }
- });
- // Implement a scoped editMetaData AJAX command: calls the callback.
- Drupal.ajax[id].commands.editMetadata = function (ajax, response, status) {
+ $.ajax({
+ url: Drupal.url('edit/metadata'),
+ type: 'POST',
+ data: { 'fields[]' : fieldIDs },
+ dataType: 'json',
+ success: function(results) {
// Store the metadata.
- _.each(response.data, function (fieldMetadata, fieldID) {
+ _.each(results, function (fieldMetadata, fieldID) {
Drupal.edit.metadata.add(fieldID, fieldMetadata);
});
- // Clean-up.
- delete Drupal.ajax[id];
- $el.remove();
callback(fieldElementsWithoutMetadata);
- };
- // This will ensure our scoped editMetadata AJAX command gets called.
- $el.trigger('edit-internal.edit');
+ }
});
}
}
/**
+ * Loads missing in-place editor's attachments (JavaScript and CSS files).
+ *
+ * Missing in-place editors are those whose fields are actively being used on
+ * the page but don't have
+ *
+ * @param Function callback
+ * Callback function to be called when the missing in-place editors (if any)
+ * have been inserted into the DOM. i.e. they may still be loading.
+ */
+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);
+ if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
+ missingEditors.push(metadata.editor);
+ }
+ });
+ missingEditors = _.uniq(missingEditors);
+ if (missingEditors.length === 0) {
+ callback();
+ }
+
+ // @todo Simplify this once https://drupal.org/node/1533366 lands.
+ var id = 'edit-load-editors';
+ // Create a temporary element to be able to use Drupal.ajax.
+ var $el = $('').appendTo('body');
+ // Create a Drupal.ajax instance to load the form.
+ Drupal.ajax[id] = new Drupal.ajax(id, $el, {
+ url: Drupal.url('edit/attachments'),
+ event: 'edit-internal.edit',
+ submit: { 'editors[]': missingEditors },
+ // No progress indicator.
+ progress: { type: null }
+ });
+ // Implement a scoped insert AJAX command: calls the callback after all AJAX
+ // command functions have been executed (hence the deferred calling).
+ var realInsert = Drupal.ajax.prototype.commands.insert;
+ Drupal.ajax[id].commands.insert = function (ajax, response, status) {
+ _.defer(function() { callback(); });
+ realInsert(ajax, response, status);
+ };
+ // Trigger the AJAX request, which will should return AJAX commands to insert
+ // any missing attachments.
+ $el.trigger('edit-internal.edit');
+}
+
+/**
* Attempts to set up a "Quick edit" link and corresponding EntityModel.
*
* @param Object contextualLink
@@ -323,14 +359,16 @@ function initializeEntityContextualLink (contextualLink) {
});
fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
- // Set up contextual link view.
- var $links = $(contextualLink.el).find('.contextual-links');
- var contextualLinkView = new Drupal.edit.ContextualLinkView($.extend({
- el: $('').prependTo($links),
- model: entityModel,
- appModel: Drupal.edit.app.model
- }, options));
- entityModel.set('contextualLinkView', contextualLinkView);
+ // Set up contextual link view after loading any missing in-place editors.
+ loadMissingEditors(function () {
+ var $links = $(contextualLink.el).find('.contextual-links');
+ var contextualLinkView = new Drupal.edit.ContextualLinkView($.extend({
+ el: $('').prependTo($links),
+ model: entityModel,
+ appModel: Drupal.edit.app.model
+ }, options));
+ entityModel.set('contextualLinkView', contextualLinkView);
+ });
return true;
}
diff --git a/core/modules/edit/lib/Drupal/edit/Ajax/MetadataCommand.php b/core/modules/edit/lib/Drupal/edit/Ajax/MetadataCommand.php
deleted file mode 100644
index 5f291ca..0000000
--- a/core/modules/edit/lib/Drupal/edit/Ajax/MetadataCommand.php
+++ /dev/null
@@ -1,27 +0,0 @@
-request->get('fields');
if (!isset($fields)) {
throw new NotFoundHttpException();
@@ -66,15 +64,25 @@ public function metadata(Request $request) {
$metadata[$field] = $metadataGenerator->generate($entity, $instance, $langcode, $view_mode);
}
- $response->addCommand(new MetaDataCommand($metadata));
+ return new JsonResponse($metadata);
+ }
- // Determine in-place editors and ensure their attachments are loaded.
- $editors = array();
- foreach ($metadata as $edit_id => $field_metadata) {
- if (isset($field_metadata['editor'])) {
- $editors[] = $field_metadata['editor'];
- }
+ /**
+ * Returns AJAX commands to load in-place editors' attachments.
+ *
+ * Given a list of in-place editor IDs as POST parameters, render AJAX
+ * commands to load those in-place editors.
+ *
+ * @return \Drupal\Core\Ajax\AjaxResponse
+ * The Ajax response.
+ */
+ public function attachments(Request $request) {
+ $response = new AjaxResponse();
+ $editors = $request->request->get('editors');
+ if (!isset($editors)) {
+ throw new NotFoundHttpException();
}
+
$editorSelector = $this->container->get('edit.editor.selector');
$elements['#attached'] = $editorSelector->getEditorAttachments($editors);
drupal_process_attached($elements);
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
index 3d83d83..df7f51a 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
@@ -110,35 +110,36 @@ function testUserWithPermission() {
$this->assertRaw('data-edit-entity="node/1"');
$this->assertRaw('data-edit-id="node/1/body/und/full"');
- // Retrieving the metadata should result in a 200 response, containing:
- // 1. a settings command with useless metadata: AjaxController is dumb
- // 2. an insert command that loads the required in-place editors
- // 3. a metadata command with correct per-field metadata
+ // Retrieving the metadata should result in a 200 JSON response.
+ $htmlPageDrupalSettings = $this->drupalSettings;
$response = $this->retrieveMetadata(array('node/1/body/und/full'));
$this->assertResponse(200);
- $ajax_commands = drupal_json_decode($response);
- $this->assertIdentical(3, count($ajax_commands), 'The metadata HTTP request results in three AJAX commands.');
+ $expected = array(
+ 'node/1/body/und/full' => array(
+ 'label' => 'Body',
+ 'access' => TRUE,
+ 'editor' => 'form',
+ 'aria' => 'Entity node 1, field Body',
+ )
+ );
+ $this->assertIdentical(drupal_json_decode($response), $expected, 'The metadata HTTP request answers with the correct JSON response.');
+ // Restore drupalSettings to build the next requests; simpletest wipes them
+ // after a JSON response.
+ $this->drupalSettings = $htmlPageDrupalSettings;
+ // Retrieving the attachments should result in a 200 response, containing:
+ // 1. a settings command with useless metadata: AjaxController is dumb
+ // 2. an insert command that loads the required in-place editors
+ $response = $this->retrieveAttachments(array('form'));
+ $ajax_commands = drupal_json_decode($response);
+ $this->assertIdentical(2, count($ajax_commands), 'The attachments HTTP request results in two AJAX commands.');
// First command: settings.
$this->assertIdentical('settings', $ajax_commands[0]['command'], 'The first AJAX command is a settings command.');
-
// Second command: insert libraries into DOM.
$this->assertIdentical('insert', $ajax_commands[1]['command'], 'The second AJAX command is an append command.');
$command = new AppendCommand('body', '' . "\n");
$this->assertIdentical($command->render(), $ajax_commands[1], 'The append command contains the expected data.');
- // Third command: actual metadata.
- $this->assertIdentical('editMetadata', $ajax_commands[2]['command'], 'The third AJAX command is an Edit metadata command.');
- $command = new MetadataCommand(array(
- 'node/1/body/und/full' => array(
- 'label' => 'Body',
- 'access' => TRUE,
- 'editor' => 'form',
- 'aria' => 'Entity node 1, field Body'
- )
- ));
- $this->assertIdentical($command->render(), $ajax_commands[2], 'The Edit metadata command contains the expected metadata.');
-
// Retrieving the form for this field should result in a 200 response,
// containing only an editFieldForm command.
$response = $this->retrieveFieldForm('node/1/body/und/full');
@@ -188,6 +189,43 @@ protected function retrieveMetadata($ids) {
}
/**
+ * Retrieves AJAX commands to load attachments for the given in-place editors.
+ *
+ * @param array $editors
+ * An array of in-place editor ids.
+ *
+ * @return string
+ * The response body.
+ */
+ protected function retrieveAttachments($editors) {
+ // Build POST values.
+ $post = array();
+ for ($i = 0; $i < count($editors); $i++) {
+ $post['editors[' . $i . ']'] = $editors[$i];
+ }
+
+ // Serialize POST values.
+ foreach ($post as $key => $value) {
+ // Encode according to application/x-www-form-urlencoded
+ // Both names and values needs to be urlencoded, according to
+ // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
+ $post[$key] = urlencode($key) . '=' . urlencode($value);
+ }
+ $post = implode('&', $post);
+
+ // Perform HTTP request.
+ return $this->curlExec(array(
+ CURLOPT_URL => url('edit/attachments', array('absolute' => TRUE)),
+ CURLOPT_POST => TRUE,
+ CURLOPT_POSTFIELDS => $post . $this->getAjaxPageStatePostData(),
+ CURLOPT_HTTPHEADER => array(
+ 'Accept: application/vnd.drupal-ajax',
+ 'Content-Type: application/x-www-form-urlencoded',
+ ),
+ ));
+ }
+
+ /**
* Retrieve field form from the server. May also result in additional
* JavaScript settings and CSS/JS being loaded.
*
@@ -207,7 +245,7 @@ protected function retrieveFieldForm($field_id) {
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $post . $this->getAjaxPageStatePostData(),
CURLOPT_HTTPHEADER => array(
- 'Accept: application/json',
+ 'Accept: application/vnd.drupal-ajax',
'Content-Type: application/x-www-form-urlencoded',
),
));