Entity response controller

Created on 1 April 2024, 8 months ago
Updated 6 June 2024, 6 months ago

Problem/Motivation

From the brainstorm session we had in 🌱 1.1 Goals for HTMX Active .

Original thought

Create an HTMX Node sub-module that provides an htmx route to render a given node in a given view mode

Reworded the request to reduce the specificity and leave room for technical discussion

Technical Implementation details

  • We need a service that can deliver reliable HTML responses for Drupal's entities.
  • For example: Give me the HTML for a node so I can hx-swap it in.
  • The service could be expanded to handle more than nodes with a /{route-prefix}/{entity_type}/{entity_id} syntax
  • Need to talk through and handle any security implications of such a route. Likely the same scenario as general entity access

Remaining tasks

Let's talk through the implications and utility of such a feature.

Additional questions to answer:
What's a better solution here:
A service that we write controllers / routes to consume?
OR
Extend the rendering pipeline to provide support for rendering less that full page of HTML. Not sure what to call it. AI chatbots suggest HtmlFragment. So... HtmlFragmentRender.

User interface changes

Perhaps new administration pages controlling access
Perhaps we handle that with permissions instead

API changes

New route
New reusable service?

Data model changes

None

✨ Feature request
Status

Fixed

Version

1.0

Component

Drupal Code

Created by

πŸ‡ΊπŸ‡ΈUnited States cosmicdreams Minneapolis/St. Paul

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @cosmicdreams
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    We are already providing a service to return a slimmer response: πŸ“Œ Provide a means to return a simple html response on routes Fixed
    The simple page provided by core only renders any system messages and the main content of the page. If we render and return the entity alone, that will be the main content of the page, and so that feature is solved by this work.

    We may be able to generalize to a single route, but I would want to be sure that we can account for:

    1. entity access
    2. view modes

    I know that if we focus an implementation on a particular entity type, that we can hand off access checks back to core. If we have to build an access solution to provide a general route, then I would favor entity specific solutions, with possibly a base controller in the main module.

  • πŸ‡ΊπŸ‡ΈUnited States cosmicdreams Minneapolis/St. Paul

    This EventSubscriber seems to be responsible for what you're describing above:

    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\htmx\EventSubscriber;
    
    use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
    use Drupal\Core\Render\RenderEvents;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    
    /**
     * Drupal should return a simple page for any route with our route option set.
     *
     * @code
     * route.name:
     *  ...
     *  options:
     *    _htmx_route: true
     * @endcode
     */
    class HtmxPageDisplayVariantSubscriber implements EventSubscriberInterface {
    
      /**
       * Selects the simple page display variant.
       *
       * @param \Drupal\Core\Render\PageDisplayVariantSelectionEvent $event
       *   The event to process.
       */
      public function onSelectPageDisplayVariant(PageDisplayVariantSelectionEvent $event): void {
        $routeMatch = $event->getRouteMatch();
        $htmxRoute = $routeMatch->getRouteObject()->getOption('_htmx_route') ?? FALSE;
        if ($htmxRoute) {
          $event->setPluginId('simple_page');
        }
      }
    
      /**
       * {@inheritdoc}
       */
      public static function getSubscribedEvents(): array {
        // We want to run after BlockPageDisplayVariantSubscriber.
        $events[RenderEvents::SELECT_PAGE_DISPLAY_VARIANT][] = ['onSelectPageDisplayVariant', -100];
        return $events;
      }
    
    }
    

    The documentation calls out it's function by requiring developers who are aiming to use the route to include a route parameter to opt-into using this logic.

    So this appears to be an underlying mechanism one could use to create your own routes that deliver HtmlFragments for entities.

    What is the difference between this implementation and what I'm describing in Problem/Motivation section of the original issue description?

    In a sub-module that can be optionally turned on, we can provide an example implementation of this service by providing a route that can render an {entity_type}/{entity_id}. That would be a common use case for the service. Perhaps an excessive one.

    Better yet, we could find a way to integrate with Views in providing a way to return a result as an HtmlFragment.

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Looks like we can use a dynamic entity based on the docblock from `\Drupal\Core\Entity\EntityAccessCheck`

      /**
       * Checks access to the entity operation on the given route.
       *
       * The route's '_entity_access' requirement must follow the pattern
       * 'slug.operation'. Typically, the slug is an entity type ID, but it can be
       * any slug defined in the route. The route match parameter corresponding to
       * the slug is checked to see if it is entity-like, that is: implements
       * EntityInterface. Available operations are: 'view', 'update', 'create', and
       * 'delete'.
       *
       * For example, this route configuration invokes a permissions check for
       * 'update' access to entities of type 'node':
       * @code
       * pattern: '/foo/{node}/bar'
       * requirements:
       *   _entity_access: 'node.update'
       * @endcode
       * And this will check 'delete' access to a dynamic entity type:
       * @code
       * example.route:
       *   path: foo/{entity_type}/{example}
       *   requirements:
       *     _entity_access: example.delete
       *   options:
       *     parameters:
       *       example:
       *         type: entity:{entity_type}
       * @endcode
       *
       * ...
       */
    

    Route path: `/htmx/{entity_type}/{entity_id}/{view_mode}`

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    The best approach I think is a controller that renders using `\Drupal\Core\Entity\EntityViewBuilder` and it's decendents.

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • πŸ‡ΊπŸ‡ΈUnited States cosmicdreams Minneapolis/St. Paul

    Ah yes, view_mode, that will be important. With the addition of that, the route is what I was thinking of too.

    In addition to this, I also think views integration would be awesome and maybe not that hard to implement. If we have a working Renderer of HtmlFragments then we can just call it for the View.

    So far our build list includes:

    To build

    A service that produces HtmlFragments of an entity. Arguments: entity_type, entity_id, view_mode
    A controller that accepts our arguments and uses the service, delivers responses
    A route that requires arguments of entity_type and entity_id. Optionally includes view_mode (default_value: 'default')

    Future work

    Reuse the service in a Views plugin so we can create a View that delivers an HTMX-compatible response.

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    We don't need to build a service. Core has these services and we can use them in our controller. Less to do and maintain is a win!!

  • Assigned to fathershawn
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Merge request !6Resolve #3437404 "Entity response" β†’ (Merged) created by fathershawn
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Added with test. All tests passing.

  • Pipeline finished with Skipped
    6 months ago
    #180488
  • Status changed to Fixed 6 months ago
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024