Routes

The Lupus CE Renderer module takes care of rendering routes of the format custom_elements via Drupal's regular routing system. That means a route can be defined as usual in Drupal, additionally define the _format to be custom_elements:

MODULE.routing.yml
  MODULE.listing:
      path: '/news'
      defaults:
        _title: 'News Listing'
        _controller: '\Drupal\MODULE\Controller\NewsController::buildNewsListing'
      requirements:
        _format: 'custom_elements'
        _permission: 'access content'

Next, the controller may simple return a custom element object:

Controller/News.php
  <?php
     class NewsController extends ControllerBase {
      public function buildNewsListing() {

        $articles[] = CustomElement::create('article-teaser')
          ->setAttribute('href', 'https://example.com/news/1')
          ->setAttribute('excerpt', 'The excerpt of the news entry.');

        $articles[] = CustomElement::create('article-teaser')
          ->setAttribute('href', 'https://example.com/news/2')
          ->setAttribute('excerpt', 'The excerpt of another news entry.');

        return CustomElement::create('teaser-listing')
          ->setSlotFromNestedElements('default', $articles);
      }
     }

The Lupus CE renderer module is taking care of rendering the element into either markup or JSON serialization:

/ce-api/news
{

    "title": "News Listing",
    "content_format": "json",
    "content": {
          "element": "teaser-listing",
          "props": {
                "title": "Latest news",
                "icon": "news"
          },
          "slots": {
                "default": [
                      {
                            "element": "article-teaser",
                            "props": {
                                  "href": "https://example.com/news/1",
                                  "excerpt": "The excerpt of the news entry."
                            }
                      },
                      {
                            "element": "article-teaser",
                            "props": {
                                  "href": "https://example.com/news/2",
                                  "excerpt": "The excerpt of another news entry."
                            }
                      }
                ]
          }
    }
}
/ce-api/news?_content_format=markup
{
    "title": "News Listing",
    "content_format": "markup",
    "content": "
      <teaser-listing title=\"Latest news\" icon=\"news\">
        <article-teaser to=\"https://example.com/news/1\" excerpt=\"The excerpt of the news entry.\" slot=\"default\"></article-teaser>
        <article-teaser to=\"https://example.com/news/2\" excerpt=\"The excerpt of another news entry.\" slot=\"default\"></article-teaser>
      </teaser-listing>"
}

Multiple routes at one path

Note that the routing system supports routes at the same path with varying formats, thus a html and a custom_elements formatted route can co-exist at the same path. This may be used to clone routes and to customize them as needed.


/**
 * Creates CE variants for user forms.
 */
class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    // Provide CE variants for user forms.
    $form_route_ids = ['user.login', 'user.pass', 'user.register'];
    foreach ($form_route_ids as $form_route_id) {
      $route = $collection->get($form_route_id);
      $ce_route = clone $route;
      $ce_route->setRequirement('_format', 'custom_elements');
      // Then customize the new route as needed:
      $form = $route->hasDefault('_entity_form') ? 'entity_form' : 'form';
      $ce_route->setDefault('_controller', "lupus_decoupled_form.controller.$form:getContentResult");
      // Add it to the route collection.
      $collection->add("lupus_decoupled.{$form_route_id}", $ce_route);
    }
  }

}