Commit 2280ad5d authored by catch's avatar catch

Issue #2083387 by Gábor Hojtsy, Wim Leers: Make it possible to use in-place...

Issue #2083387 by Gábor Hojtsy, Wim Leers: Make it possible to use in-place editing on entities rendered by a custom render pipeline (e.g. Views).
parent 86876b30
...@@ -28,6 +28,54 @@ function hook_edit_editor_alter(&$editors) { ...@@ -28,6 +28,54 @@ function hook_edit_editor_alter(&$editors) {
$editors['editor']['class'] = 'Drupal\advanced_editor\Plugin\edit\editor\AdvancedEditor'; $editors['editor']['class'] = 'Drupal\advanced_editor\Plugin\edit\editor\AdvancedEditor';
} }
/**
* Returns a renderable array for the value of a single field in an entity.
*
* To integrate with in-place field editing when a non-standard render pipeline
* is used (field_view_field() is not sufficient to render back the field
* following in-place editing in the exact way it was displayed originally),
* implement this hook.
*
* Edit module integrates with HTML elements with data-edit-id attributes. For
* example: data-edit-id="node/1/<field-name>/und/<module-name>-<custom-id>".
* After the editing is complete, this hook is invoked on the module with
* the custom render pipeline identifier (last part of data-edit-id) to
* re-render the field. Use the same logic used when rendering the field for
* the original display.
*
* The implementation should take care of invoking the prepare_view steps. It
* should also respect field access permissions.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity containing the field to display.
* @param string $field_name
* The name of the field to display.
* @param string $view_mode_id
* View mode ID for the custom render pipeline this field view was destined
* for. This is not a regular view mode ID for the Entity/Field API render
* pipeline and is provided by the renderer module instead. An example could
* be Views' render pipeline. In the example of Views, the view mode ID would
* probably contain the View's ID, display and the row index. Views would
* know the internal structure of this ID. The only structure imposed on this
* ID is that it contains dash separated values and the first value is the
* module name. Only that module's hook implementation will be invoked. Eg.
* 'views-...-...'.
* @param string $langcode
* (Optional) The language code the field values are to be shown in.
*
* @return
* A renderable array for the field value.
*
* @see field_view_field()
*/
function hook_edit_render_field(Drupal\Core\Entity\EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
return array(
'#prefix' => '<div class="example-markup">',
'field' => field_view_field($entity, $field_name),
'#suffix' => '</div>',
);
}
/** /**
* @} End of "addtogroup hooks". * @} End of "addtogroup hooks".
*/ */
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManager; use Drupal\Core\Entity\EntityManager;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\field\FieldInfo; use Drupal\field\FieldInfo;
use Drupal\edit\MetadataGeneratorInterface; use Drupal\edit\MetadataGeneratorInterface;
use Drupal\edit\EditorSelectorInterface; use Drupal\edit\EditorSelectorInterface;
...@@ -67,6 +68,13 @@ class EditController extends ContainerAware implements ContainerInjectionInterfa ...@@ -67,6 +68,13 @@ class EditController extends ContainerAware implements ContainerInjectionInterfa
*/ */
protected $fieldInfo; protected $fieldInfo;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/** /**
* Constructs a new EditController. * Constructs a new EditController.
* *
...@@ -80,13 +88,16 @@ class EditController extends ContainerAware implements ContainerInjectionInterfa ...@@ -80,13 +88,16 @@ class EditController extends ContainerAware implements ContainerInjectionInterfa
* The entity manager. * The entity manager.
* @param \Drupal\field\FieldInfo $field_info * @param \Drupal\field\FieldInfo $field_info
* The field info service. * The field info service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/ */
public function __construct(TempStoreFactory $temp_store_factory, MetadataGeneratorInterface $metadata_generator, EditorSelectorInterface $editor_selector, EntityManager $entity_manager, FieldInfo $field_info) { public function __construct(TempStoreFactory $temp_store_factory, MetadataGeneratorInterface $metadata_generator, EditorSelectorInterface $editor_selector, EntityManager $entity_manager, FieldInfo $field_info, ModuleHandlerInterface $module_handler) {
$this->tempStoreFactory = $temp_store_factory; $this->tempStoreFactory = $temp_store_factory;
$this->metadataGenerator = $metadata_generator; $this->metadataGenerator = $metadata_generator;
$this->editorSelector = $editor_selector; $this->editorSelector = $editor_selector;
$this->entityManager = $entity_manager; $this->entityManager = $entity_manager;
$this->fieldInfo = $field_info; $this->fieldInfo = $field_info;
$this->moduleHandler = $module_handler;
} }
/** /**
...@@ -98,7 +109,8 @@ public static function create(ContainerInterface $container) { ...@@ -98,7 +109,8 @@ public static function create(ContainerInterface $container) {
$container->get('edit.metadata.generator'), $container->get('edit.metadata.generator'),
$container->get('edit.editor.selector'), $container->get('edit.editor.selector'),
$container->get('entity.manager'), $container->get('entity.manager'),
$container->get('field.info') $container->get('field.info'),
$container->get('module_handler')
); );
} }
...@@ -219,7 +231,25 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view ...@@ -219,7 +231,25 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view
// The form submission saved the entity in tempstore. Return the // The form submission saved the entity in tempstore. Return the
// updated view of the field from the tempstore copy. // updated view of the field from the tempstore copy.
$entity = $this->tempStoreFactory->get('edit')->get($entity->uuid()); $entity = $this->tempStoreFactory->get('edit')->get($entity->uuid());
$output = field_view_field($entity, $field_name, $view_mode_id, $langcode);
// Render the field. If the view mode ID is not an Entity Display view
// mode ID, then the field was rendered using a custom render pipeline,
// that is: not the Entity/Field API render pipeline.
// An example could be Views' render pipeline. In the example of Views,
// the view mode ID would probably contain the View's ID, display and the
// row index.
$entity_view_mode_ids = array_keys(entity_get_view_modes($entity->entityType()));
if (in_array($view_mode_id, $entity_view_mode_ids)) {
$output = field_view_field($entity, $field_name, $view_mode_id, $langcode);
}
else {
// Each part of a custom (non-Entity Display) view mode ID is separated
// by a dash; the first part must be the module name.
$mode_id_parts = explode('-', $view_mode_id, 2);
$module = reset($mode_id_parts);
$args = array($entity, $field_name, $view_mode_id, $langcode);
$output = $this->moduleHandler->invoke($module, 'edit_render_field', $args);
}
$response->addCommand(new FieldFormSavedCommand(drupal_render($output))); $response->addCommand(new FieldFormSavedCommand(drupal_render($output)));
} }
......
...@@ -294,7 +294,7 @@ public function testUserWithPermission() { ...@@ -294,7 +294,7 @@ public function testUserWithPermission() {
/** /**
* Tests that Edit doesn't make pseudo fields or computed fields editable. * Tests that Edit doesn't make pseudo fields or computed fields editable.
*/ */
function testPseudoFields() { public function testPseudoFields() {
\Drupal::moduleHandler()->install(array('edit_test')); \Drupal::moduleHandler()->install(array('edit_test'));
$this->drupalLogin($this->author_user); $this->drupalLogin($this->author_user);
...@@ -304,6 +304,48 @@ function testPseudoFields() { ...@@ -304,6 +304,48 @@ function testPseudoFields() {
$this->assertNoRaw('data-edit-id="node/1/edit_test_pseudo_field/und/default"'); $this->assertNoRaw('data-edit-id="node/1/edit_test_pseudo_field/und/default"');
} }
/**
* Tests that Edit works with custom render pipelines.
*/
public function testCustomPipeline() {
\Drupal::moduleHandler()->install(array('edit_test'));
$custom_render_url = 'edit/form/node/1/body/und/edit_test-custom-render-data';
$this->drupalLogin($this->editor_user);
// Request editing to render results with the custom render pipeline.
$post = array('nocssjs' => 'true') + $this->getAjaxPageStatePostData();
$response = $this->drupalPost($custom_render_url, 'application/vnd.drupal-ajax', $post);
$ajax_commands = drupal_json_decode($response);
// Prepare form values for submission. drupalPostAJAX() is not suitable for
// handling pages with JSON responses, so we need our own solution here.
$form_tokens_found = preg_match('/\sname="form_token" value="([^"]+)"/', $ajax_commands[0]['data'], $token_match) && preg_match('/\sname="form_build_id" value="([^"]+)"/', $ajax_commands[0]['data'], $build_id_match);
$this->assertTrue($form_tokens_found, 'Form tokens found in output.');
if ($form_tokens_found) {
$post = array(
'form_id' => 'edit_field_form',
'form_token' => $token_match[1],
'form_build_id' => $build_id_match[1],
'body[0][summary]' => '',
'body[0][value]' => '<p>Fine thanks.</p>',
'body[0][format]' => 'filtered_html',
'op' => t('Save'),
);
// Submit field form and check response. Should render with the custom
// render pipeline.
$response = $this->drupalPost($custom_render_url, 'application/vnd.drupal-ajax', $post);
$this->assertResponse(200);
$ajax_commands = drupal_json_decode($response);
$this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.');
$this->assertIdentical('editFieldFormSaved', $ajax_commands[0]['command'], 'The first AJAX command is an editFieldFormSaved command.');
$this->assertTrue(strpos($ajax_commands[0]['data'], 'Fine thanks.'), 'Form value saved and printed back.');
$this->assertTrue(strpos($ajax_commands[0]['data'], '<div class="edit-test-wrapper">') !== FALSE, 'Custom render pipeline used to render the value.');
}
}
/** /**
* Tests Edit on a node that was concurrently edited on the full node form. * Tests Edit on a node that was concurrently edited on the full node form.
*/ */
......
...@@ -37,3 +37,15 @@ function edit_test_entity_view_alter(&$build, EntityInterface $entity, EntityDis ...@@ -37,3 +37,15 @@ function edit_test_entity_view_alter(&$build, EntityInterface $entity, EntityDis
); );
} }
} }
/**
* Implements hook_edit_render_field().
*/
function edit_test_edit_render_field(Drupal\Core\Entity\EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
return array(
'#prefix' => '<div class="edit-test-wrapper">',
'field' => field_view_field($entity, $field_name),
'#suffix' => '</div>',
);
}
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