Problem/Motivation

For background, see the parent issue #3196329: [META] Use cases covered by the decoupled menus initiative

Many Drupal sites have complex menu requirements that go beyond very basic needs like a simple one- or two-level menu that never changes across a site. There are an infinite number of possibilities that a non-decoupled Drupal site can handle today, but would be very difficult to handle in a decoupled architecture. Here is an example:

Imagine an ecommerce site that has a secondary menu that changes depending on the category of the product that the user is viewing. If the product is categorized under Clothing > Women > Shoes, a site builder might like for the secondary menu to show other menus in the clothing department (e.g. Women, Men, Kids, Accessories), but if the product is categorized under Books > Children, a site builder might like to show a menu for types of books (e.g. Best sellers, New releases, etc).

This issue is about finding an appropriate format for an HTTP API which can serve a menu based on this complex business logic that might be reconfigured by a content editor or site builder at any time.

Proposed resolution

Conditionally add JSON:API link objects to existing individual content endpoints (e.g. /jsonapi/node/article/{id}) based on conditions that are configured and evaluated on the back end.

By doing so, a front end would render the menu items given to it by the back end (or not render them if there are no links). Therefore, the complex business logic will not need to be encoded in the front end application. The FE renders them if they're in the response, or it doesn't.

This is is important to prevent the application having to be redeployed when business logic changes (i.e. a site builder can make changes without the help of a developer). It also means that developers will not need to duplicate business logic between front ends if they are writing both a web app and a native app, for example.

A JSON:API link object might look like this, if it were to include hierarchical info.

{
  "data": {
    "type": "node--book",
    "id": "some-uuid-here",
    "attributes": {
      "title": "Harry Potter and the Prisoner of Azkaban"
    },
    "links": {
      "self": {
        "href": "https://www.example.com/jsonapi/node/book/some-uuid-here"
      }
    }
  },
  "links": {
    "self": {
      "href": "https://www.example.com/the-second-best-book-of-all-time"
    },
    "drupal:menu:item:1": {
      "href": "https://www.example.com/shop/books/best-sellers",
      "title": "Best sellers",
      "meta": {
        "menu": "secondary",
        "children": ["drupal:menu:item:2"]
      }
    },
    "drupal:menu:item:2": {
      "href": "https://www.example.com/books/best-sellers/all-time",
      "title": "Best selling of all time",
      "meta": {
        "menu": "secondary"
      }
    },
    "drupal:menu:item:3": {
      "href": "https://www.example.com/books/new-releases",
      "title": "New releases",
      "meta": {
        "menu": "secondary"
      }
    }
  }
}

API changes

See above. Note that the proposed changes would be additive and backwards compatible.

Data model changes

None.

Comments

gabesullice created an issue. See original summary.

gabesullice’s picture

Issue summary: View changes

I've included my proposed API in the issue summary. Note that the use of title as a sibling of href is using version 1.1-rc3 of the JSON:API spec. However, as a spec editor, I'm confident that this will not change before 1.1 is finalized.

An advantage of using 1.1 link objects is our ability to use target attributes to give hints to the consumer about how the menu link should be treated. For example, let's imagine that a menu contains a link to a non-JSON:API route, such as /admin/content or /node/3/edit, the link can hint to the client that it cannot make a JSON:API request for it by using the type target attribute:

{
  "href": "/admin/content",
  "title": "Edit",
  "type": "text/html",
  "meta": {
    "menu": "shortcut",
    "hierarchy": ".001"
  }
}

Fully-decoupled consumers could choose to filter these links out and progressively decoupled apps could just to follow them by causing an actual browser navigation (e.g. window.location = link.href). This could be very useful in Drupal core for progressively moving to a more decoupled Admin UI, for example. It could also be handy in providing an authenticate link in an OAuth redirection.

We will be using the hreflang target attribute for a similar purpose in #2794431: [META] Formalize translations support.

Deciphered’s picture

It's worth looking at the attributes provided by the JSON:API Menu Items module for a comparison: https://demo-api.druxtjs.org/jsonapi/menu_items/main

Some of the additional data provide the ability for the frontend to deliver a functional menu that behaves as Drupal's backend expects it too.

{
  "type": "menu_link_content--menu_link_content",
  "id": "standard.front_page",
  "attributes": {
    "description": "",
    "enabled": true,
    "expanded": false,
    "menu_name": "main",
    "meta": [],
    "options": [],
    "parent": "",
    "provider": "demo_umami",
    "route": {
      "name": "<front>",
      "parameters": []
    },
    "title": "Home",
    "url": "/en",
    "weight": "0"
  }
},
gabesullice’s picture

Thanks for the comparison!

My concern with that serialization is that it's exposing a lot of back-end implementation detail and complexity. Relying on any of: provider, enabled, route names and parameters, or menu item IDs suggests that the front end is tightly coupled to the back end.

For example, if items which aren't enabled are not omitted by the back end, then the front end implementation necessarily becomes coupled the the idea that links can be enabled or disabled which means that the front-end implementation has to be more complicated and larger since it has to maintain the code to remove disabled menu items on its own[1].

expanded is the most notably absent value since parent and weight are implied by the hierarchy value in the proposed format[2].

I intentionally excluded expanded since I felt like it was encoding a presentation detail that ought to be the concern of the front end... but I don't feel strongly about that. I can understand why we would want to include it in order to give content editors more control over the front end presentation... In fact, it wouldn't take much to convince me that it should be included since the Decoupled Menu Initiative is about eliminating compromises when choosing to build a decoupled site. What do you think @Deciphered?

[1] Writing this made me go take a look at JSON:API Menu Item's implementation again. It looks like the back end is omitting disabled menu items, which makes enabled meaningless because it will always be true.

[2] The reason I proposed a hierarchy value instead of using parent and weight is because it makes it trivial to reconstruct the tree quickly and because using parent means you also need to have an id for every item. IOW, having the single hierarchy value means we can eliminate two other values and avoid writing recursive code to reconstruct a tree on the front end.

Deciphered’s picture

My opinion across the board is that any information that can be provided, should be provided. A decoupled application should be able to chose to use the data, just the same way a Drupal module can access and render the data.

For instance, if I made a Menu management feature in a NodeJS "backend", I would like to see if a menu link is enabled, and control that state.

I'm not going to argue `hierarchy` vs `parent`/`weight`, but I would like consistency where it's possible, so the data should be named the same way in the backend (PHP / Twig) as it is in the JSON:API.

mirom’s picture

I agree with Deciphered that we should expose all available data and enable users to choose which they really want to use. Maybe it would make sense to bring jsonapi_extras to the core at this point.
I would also return only enabled links and ofc those which are accessible for the current user. I think it works the same way for nodes, so we should stick to the same pattern everywhere.

e0ipso’s picture

Excuse my quick brain dump. Lately I have little time for elaborate responses.

I see 3 main topics:

Resource data vs link data

- If this is link data then the "type: text/html" makes sense.
- If this is resource data the consumer is in charge of knowing the business logic. For instance changing the window.location on the browser.

One works towards homogenization and minimizing astonishment by staying consistent with existing resource types. I think this (also) is what @mirom hints at with:

I think it works the same way for nodes, so we should stick to the same pattern everywhere.

The other one is more semantically correct and will likely open the door to more integrated techniques. However hypermedia links are transitive, they need an object to refer to. Right now there is no implicit context these links would refer to. It seems there is a missing discussion to be had if we want to nail semantics here.

Hierarchy

There is a gap in how to imply hierarchy: let's define the features we want to support then let's define how each approach will look like. That will help to decide better. For instance: "give me the menu subtree starting on the Link12 menu link item in a depth of 3, with 2 levels of depth".

Framework logic (including enabled)

We need to think beyond the anonymous use case for a JS app. That means that the back-end needs to fully own the back-end framework logic.

Imagine one was to add a module that alters menu items based on the day of the week the item was created (maybe it alters access, order, target, ...). Drupal should be responsible from removing items/resorting items/altering hrefs from the response on Wednesday. Dumping a "created_day: Wed" property and hoping for all consumers to follow suit is unacceptable and unrealistic (IMO). Bear in mind that you cannot force client updates in all client side technologies (phones, TVs, billboards, kiosks, in-seat entertainment systems, etc).

gabesullice’s picture

Thanks for trying to deconstruct the discussion into more manageable parts @e0ipso!

Resource data vs link data

One works towards homogenization and minimizing astonishment by staying consistent with existing resource types …

I think it works the same way for nodes, so we should stick to the same pattern everywhere.

I think a good analogy here is from Menus to Views. If we were to add an endpoint for Views, there are two ways that we might want to do that:

  1. Expose the View config entity and all its filters, relations, sorting rules, etc. so that a decoupled front end could reconstruct the configured query and then execute a JSON:API request for the same data.
  2. Execute the View and render a JSON:API representation of the result.

If we represent all menu items as resource objects, then I think we're choosing the former approach of exposing the data and expecting the decoupled front end to interpret it and to recreate the end result. This is an RPC-like approach.

If we represent all menu items as link objects, then I think we're choosing approach #2 which interpret the configuration on the back end and exposes the featureful result in a JSON:API format. My argument is ultimately that this approach simplifies consumer implementations and makes it easier to add new features and fix bugs. This is a more RESTful approach.

All that said, the question of whether we should represent menu items as resource objects or link objects presents a somewhat false premise: that it must be one or the other exclusively. In fact, we already expose most of the data because JSON:API exposes menu config entities as well as the menu_link_content entities. The only thing we do not expose is the menu items provided in code and in YAML. This makes me wonder if what is really lacking here is an API for plugin data, which, incidentally, is already available via the JSON-RPC module.

However hypermedia links are transitive, they need an object to refer to. Right now there is no implicit context these links would refer to. It seems there is a missing discussion to be had if we want to nail semantics here.

This is a good point. It is semantically wrong to put links on the menu config entity because it implies the links go from the config entity resource to the targeted resources. Perhaps we need to instead "attach" menu links to response documents using logic similar to the block placement system which presently attaches menus blocks to content pages (actually, this is similar to how the JSON:API Navigation sandbox works today).

This raises an extra argument for links, IMO, if the HTML page for /node/1 represents an article and has a primary menu block on it, both the article and the menu data are available in a single request. However, if we make separate endpoints for /jsonapi/node/article/{id} and menus, then one must instead make two requests: one for the article and one for the menu (or we must construct an artificial JSON:API relationship that can use the ?include parameter)

Hierarchy

There is a gap in how to imply hierarchy: let's define the features we want to support then let's define how each approach will look like. That will help to decide better. For instance: "give me the menu subtree starting on the Link12 menu link item in a depth of 3, with 2 levels of depth".

I would like to support the association of parents with children and subtrees and depth, but I would not like to support expansion. IMO, the first three are functional features and expansion is purely presentational (I don't think JSON:API should be concerned with presentational data).

Framework logic

Dumping a "created_day: Wed" property and hoping for all consumers to follow suit is unacceptable and unrealistic (IMO). Bear in mind that you cannot force client updates in all client side technologies (phones, TVs, billboards, kiosks, in-seat entertainment systems, etc).

I think this is an incredibly important point. We may be building this API to support a JavaScript library we maintain, but the API needs to be as functional as possible for non-JS consumers. We cannot build components for every device, so I think we need to keep as much business logic as possible on the back end so that it doesn't need to recreated for each consumer-type.

Deciphered’s picture

I think a good analogy here is from Menus to Views. If we were to add an endpoint for Views, there are two ways that we might want to do that

As someone who has an implementation of Decoupled Views, you actually need both endpoints, the View resource tells the Vue component how to handle the data provided by the JSON:API Views endpoint.

We cannot build components for every device, so I think we need to keep as much business logic as possible on the back end so that it doesn't need to recreated for each consumer-type.

You should keep back the same sort of business logic that would would keep back from Twig. The frontend component should be able to consume the required config and content to offer a similar experience to that of Twig in Drupal. But that should go for both frontend and backend Twig, as it would be ideal if we could use the output to display a menu as well as manage a menu.

gabesullice’s picture

@Deciphered, I think we have different use cases/architectures in mind as we think about what to expose and how to expose it. You shared your opinion about how things should be:

My opinion across the board is that any information that can be provided, should be provided. A decoupled application should be able to chose to use the data, just the same way a Drupal module can access and render the data… You should keep back the same sort of business logic that would would keep back from Twig. The frontend component should be able to consume the required config and content to offer a similar experience to that of Twig in Drupal. But that should go for both frontend and backend Twig, as it would be ideal if we could use the output to display a menu as well as manage a menu.

Can you explain why you believe these things should be the way you say they should be? I'm afraid that we will fundamentally disagree that "any information that can be provided, should be provided," for this initiative, at this moment, unless I'm missing some compelling motivations.

Let me try to explain why I think we should not expose everything:

A major motivation for this initiative was to re-empower content editors even in decoupled scenarios. Drupal's status quo means that when you choose to build a decoupled site, your content editors sacrifice many of the controls they're used to in a "coupled" Drupal-rendered site (e.g. the ability to order/disable menu items or use the block system).

If we take the approach that we should expose the config so that the a consumer can read it and interpret it, then we're saying that the consumer has to read it and interpret it. This means that a consumer might not notice the enabled field or postpone implementing it to save time, which would disempower the content editor because that field would no longer have any effect.

As the author of Druxt, this distinction probably seems irrelevant. Druxt handles the enabled field properly, so it's a non-issue. Moreover, when we ship a core JavaScript library for menus, it can contain the framework logic that correctly handles the enabled field. That's entirely logical.

However, since this initiative is no longer called the JS menu component initiative, I think that means that means that our HTTP API needs to consider non-JS use cases. In those cases, Drupal core will not have the luxury of shipping platform-specific libraries to handle its framework logic correctly and Drupal cannot rely on projects like Druxt to interpret the HTTP API as it intended.

In other words, the more framework logic we expect consumers to write, the more logic those implementations will fail to implement or implement incorrectly. And the more framework logic we've forced them to learn (e.g. they'll ask "what does provider even mean? Is it something I need to handle?"). Therefore, I think we need to take a very simple, read-only approach that leaves fewer decisions for consumers to make. I think we want to lower the cognitive overhead and let the consumers maintain less complicated code.

Providing all the information that can be provided and letting the consumer choose how to use that information runs counter to this idea and it will lead to disempowered content editors whenever the consumer chooses not to respect the information it have been provided.

larowlan’s picture

One thing I'd like to see here in the meta is entity data if it is available

e.g. entity_type and entity_id

I.e. if the route uri uses the entity scheme (entity://) it would be good to pass on that information as it might help frontend code decide how to resolve the URL

mirom’s picture

@gabe, I think that what you're suggesting diverges from the common practice for other JSON:API endpoints. Could you please provide any other argument besides "decoupled app developer needs to be safeguarded?". If the Drupal developer doesn't want to expose certain data then it should be quite easy to do using jsonapi_extras.

e0ipso’s picture

You should keep back the same sort of business logic that would would keep back from Twig.

I don't think this is a fair comparison. Twig is highly coupled with the server. When you print a render array in Twig, that sparks a drupal process that returns to Twig, which may lead to going back to the render pipelines, back to Twig, etc.

In addition to that I would say that the Twig integration for Drupal had deep understanding of how Drupal works. I think that asking that of all the front-end developer, all of the time, is not the path to success.

I also think that Drupal should enable writing low cognitive load consumers.


However, I also believe that this should not be in detriment of kick ass front-end frameworks that leverage deep understanding of how Drupal works. In this realm, I believe that @Deciphered is paving the way with his ecosystem of modules to expose (read only) more of the Drupal internals.

@Deciphered, do you feel comfortable with me saying that Druxt's use case is less common than the average project needing a tvOS app, a terminal integration, or any other low cognitive load consumer? If so, is there anything core could be doing for you (and others with the same goals as you) to make your life easier?

larowlan’s picture

Quoting myself from slack

I think so long as there’s a PHP API to expand the surface of the data its probably moot. e.g. if gabe’s are the defaults, but stuart's can be accommodated with a drop-in module - then we’re all happy right

mirom’s picture

I think so long as there’s a PHP API to expand the surface of the data its probably moot

We already have that API, it's called Entity API. Why should we introduce new API because we want single (now) endpoint to work differently as the rest. Or are we going to modify all existing endpoint not to expose eg published which is exactly the same as enabled for menus?

I think that asking that of all the front-end developer, all of the time, is not the path to success.

I do agree with this, but you can do this using jsonapi_extras or we can do it in our component.

I would like every one of you to think about a situation that there will be new cool programming language allowing you to do xyz - are we going to change our interface again to conform that or are we going to provide component which can consume our existing API and output data usable by this new language?

larowlan’s picture

We already have that API, it's called Entity API

For clarity, menu links aren't entities, they're plugins.

Which is why we're discussing this here.

jsonapi_menu_items cheats and pretends that all menu links are content entities. It works, but its a hack.

mirom’s picture

For clarity, menu links aren't entities, they're plugins.

Correct, excuse my ignorance.

In that case, every plugin provides parameters and one of those parameters can be an entity, correct? Right now in my mind, I'm working with a common use case - As a content editor, I want to provide an icon for my menu item. In Drupal, I would implement it using menu_item_extras. How would I implement that using our API given that I will know only URL, title and hierarchy? Are we going to provide at least some menu link content entity id? Is there some concept in Plugin API similar to 3rd party settings in entity API we could leverage for this? Besides, we already have all this information because a plugin (MenuLinkContent) has already loaded that for us, given that title is coming from the entity.

larowlan’s picture

Are we going to provide at least some menu link content entity id? Is there some concept in Plugin API similar to 3rd party settings in entity API we could leverage for this?

Menu link content entities are already available - but I agree with you, we should provide that reference data if we can.

Given how json api works, this would be under related.

So in a scenario where you have a menu link provided by a menu link content entity that links to a node.

Could we provide both the menu link content and the node under 'relationships' @gabesullice?

I think that would make the links more useful, as you can chain direct to the items they reference or are defined by

gabesullice’s picture

EDIT This reply was written after #13. I haven't yet read the later comments.


Could you please provide any other argument besides "decoupled app developer needs to be safeguarded?".

@mirom, I feel like I must be doing a really terrible job of explaining my position if you can summarize everything I've written as "safeguarding" decoupled app developers". It's like saying that a developer writing a PHP class with protected or private properties is just trying to "safeguard" its users.

@gabe, I think that what you're suggesting diverges from the common practice for other JSON:API endpoints.

The existing JSON:API endpoints do one thing: they serialize entities.

A menu isn't a collection of entities though. A menu is a set of links. My position is that we should render menu links... as JSON:API links.

It's unfortunate that in Drupal 8 and above, some links are created by creating menu_link_content entities and so there is an existing JSON:API endpoint for them because there are endpoints for all entities (I regret this, BTW).*** I think that it's this existing endpoint that is giving you the impression that I'm trying to diverge from common practice.

If you start with the question "how should we expose all menu items?" then it seems reasonable to make "fake" entities for menu items that aren't provided by menu_link_content entities and add them in with the other ones, but that would probably be a bigger change to common practices and would open a huge can of worms.

However, if you start with the question "what are menus and how should we add them to JSON:API?" then we get a pretty straightforward answer: menu links should be added as JSON:API links to JSON:API responses just like menu links are added as HTML links to HTML responses.

*** In case it's not clear, I want to keep those endpoints and I do not want to change them. My proposal is purely additive.

gabesullice’s picture

Could we provide both the menu link content and the node under 'relationships' @gabesullice?

This wouldn't work because I'm proposing that we should "render" menus as link objects, which don't have attributes or relationships.

I think this is the key distinction you might be missing @larowlan. The biggest difference is not what properties to show or not show, it's about how JSON:API treats menu links altogether. I.e. should it treat them more like content (and therefore resource objects) or should it treat them more like navigational hypermedia (and therefore link objects)?

This also has implications to how I want to reply to you here:

I.e. if the route uri uses the entity scheme (entity://) it would be good to pass on that information as it might help frontend code decide how to resolve the URL

If we treat them as hyperlinks, then the way we should signal "how to resolve the URL" is through link relation types (which I've added to the JSON:API 1.1 spec itself specifically so that Drupal can use them 😊)

larowlan’s picture

then the way we should signal "how to resolve the URL" is through link relation types

that sounds perfect, chance you could spell out how that might work/look?

gabesullice’s picture

Sure! Let's imagine we wanted to add a shortcut link to a frequently edited node (a landing page, for example) in a way that makes it super simple for a JS rendered web-app to direct the user to the Drupal-rendered edit page. That could look like this:

{
  "href": "/node/3/edit",
  "title": "Edit",
  "type": "text/html",
  "rel": "edit"
  "meta": {
    "menu": "shortcut",
    "hierarchy": ".001"
  }
}

This link tells the consumer "don't try to fetch this! It's HTML! Just link to it with an <a> element". I know, type is a target attribute not a link rel, so, let's say that the consumer has been "taught" that edit links should all use have target="_blank". That's one admittedly contrived example :) it would make more sense for contextual links, but if we do this for menus, it will be an easy pattern to replicate for contextual links.

nod_’s picture

Let's frame this discussion a little bit more. We also talked about the fact that we have different types of menus. We have a main menu that people probably use to build an "app shell" type of thing, and we have "contextual" menus or menu-like links like contextual menus, book navigation, thinkgs like menu_block. The two use cases are very different and nothing says we should only have 1 endpoint that deals with the two use cases.

From what I understood here, @Deciphered is concerned about the app shell use-case and @gabesullice is concerned about the "contextual" menu use case, for which json:api is apparently best suited.

Anecdotally the people we talked to (drupalcon/slack) have the app shell use case coming to mind as the first thing they expect to be able to do, while drupal devs who already worked on several decoupled website very quickly want a solution for having contextual links in the API.

so 2 use cases, 2 solutions, and let's scope this one as the "contextual" menu or menu-like use case which is best suited for json:api.

nod_’s picture

gabesullice’s picture

@nod_, thanks for jumping in! I'm a bit confused though.

Can you explain the distinction? I'm worried about splitting the discussion along lines that aren't clear to me.

mirom’s picture

@gabe, sorry, it seemed the easiest way for me to summarize thoughts. I really appreciate all your hard work. After your comment, it's more clear to me.

One thing isn't clear to me. You're saying that you want to provide a list of links that can be rendered at the frontend, is that correct? So if I continue with my use case from #17, then how can I render that icon at the frontend? @nod_ mentions app shell approach, so how in this approach would a frontend developer add that icon and allow an editor to change it?

nod_’s picture

I think we talk too much about the tech and not enough about the use-cases. "Decide on an API response format for menu data" but there is no talk about why we need the API response at all. So I think we should go back to that for a moment.

Originally we talked about working from people feedback and basically coding things to help simplify an menu integration in a decoupled setup. Here we have very little mention on how the api will be used. The app shell thing might not be the right boundary, but it's a use-case that will come up that I think we should support. An other use case is to display contextual links for a given entity, another one is to show the book navigation, etc. we should agree on a few scenarios first then we can see which approach is relevant to which problem.

@mirom, I think we're not there yet. Your use case assume there is already a menu response, at the moment we're more at the level of why we need the menu in the API at all (and I think we went a little ahead of ourselves talking about the response format first).

gabesullice’s picture

No worries, thanks.

One thing isn't clear to me. You're saying that you want to provide a list of links that can be rendered at the frontend, is that correct?

Yeah, pretty much correct :) *

So if I continue with my use case from #17, then how can I render that icon at the frontend?

I went and read the Menu Item Extra module's code and it looks like it's providing a new, fieldable entity type and even replacing the menu form. So, I don't think it's something we would want to directly support in core.**

However, I can think of two ways to handle it in contrib:

  1. menu_item_extras could alter the JSON:API link and something to the meta property of the link object (it's already altering the way menu links are rendered in Twig. This is close to what @larowlan is saying in #14 and what you were suggesting when you said Are we going to provide at least some menu link content entity id?
  2. JSON:API will continue to support the /jsonapi/menu_link_content/menu_link_content endpoint that serializes those content entities. For this tightly coupled use case, I think the front end could use that directly.

Perhaps a combination of 1 and 2 would work. menu_item_extras could add the URL of the menu content entity to the link's meta.

* I want to provide a list of menu links. In most cases, they will be rendered on a front end, but they should also be able to navigate non-rendered things, like a command line client or a phone menu.

** I'd also argue that adding icons via menu items on the backend is beginning to break the front end/back end idea about decoupled architectures, which usually separate presentation (e.g. icons) from content and functionality. Presentation being a front end concern and the rest a back end concern.

pdureau’s picture

IMHO, the most important part is to keep the site building abilities from the backoffice while using a JS framework on front-end, to not force developer to abandon or recreate many of the features that Drupal provides out-of-the-box.

To achieve that we need to expose to front not the menu & menu items entities, but the menu & menu items render arrays. For example, as JSON serialization of those arrays. And the JS framework need to map each render element to one or many of its components.

Because render arrays are built very late in the Drupal cycle, we benefit from all Drupal features executed server side.

Moreover, it would be nice to have single unified data structure for rendering, which can be pushed internally to the Twig-based render API, and externally for JS-based rendering.

However, Render API is not in a good shape today, and we will need to clean it deeply to expose something tidy and efficient to front-end. There is a core issue about that: https://www.drupal.org/project/ideas/issues/2702061

(this comment was already posted here : https://www.drupal.org/project/ideas/issues/3170260#comment-13971182 before I realized the activity has moved here)

gabesullice’s picture

@pdureau, for better or for worse, I think render arrays are too coupled to presentation logic and they also have weird functional things, like the #access property and #pre_render callbacks, which we wouldn't want to send over HTTP. I like to think that we "render" JSON:API responses the same way that we render HTML responses.

I agree with you about keeping site-building abilities. My proposal about rendering menu links tries its best to do that.

Today, the pipeline is roughly like this:

Menu item entities
                  \
        Menu link plugins -> Menu tree tree elements -> Render array -> HTML
                  /
YAML-defined links

and my proposal would look something like this:

Menu item entities
                  \
        Menu link plugins -> Menu link tree elements -> JSON:API
                  /
YAML-defined links

I think that as long as we preserve the enabled/disabled information, hierarchy, order, and replicate the visibility rules that menu blocks have for JSON:API, then we really will have all the same back-office capabilities as before, even in a decoupled context since putting together the menu link tree is where it all that config gets brought together (except for the visibility conditions).

Deciphered’s picture

@pdureau / @gabesullice,

Druxt Entity and Druxt Schema is a good example of how you can deal with a "Render array", as the component loads both the Entity resource and a Drupal Entity View mode schema and uses that to render the individual "Fields", providing Formatter information and display settings.

In that case, the Schema is derived from the JSON resources:
- entity_view_display--entity_view_display
- field_config--field_config
- field_storage_config--field_storage_config

I also agree that both Twig and JSON:API should have a common content/configuration pipeline, at some point in the future at least.

pdureau’s picture

@gabesullice

for better or for worse, I think render arrays are too coupled to presentation logic and they also have weird functional things, like the #access property and #pre_render callbacks, which we wouldn't want to send over HTTP.

I totally agree, that why we need to clean the render arrays structure built and expected by the render API before thinking of exposing them to JSON:API.

Because I dream about a component-based rendering, I don't expect drupalisms like #prefix, #suffix, #pre_render, #post_render, #theme_wrappers, #sorted... to be part of this cleaned Render API.

Once the API cleaned, some specific keys may need to be altered on runtime before sending them to JSON:API. For example:

  • #lazy_builder keys are transformed to HTTP Callbacks, like what the Big Pipe module is already doing.
  • #cache keys are transformed to HTTP cache header or something else useful in front-side

@Deciphered : thank you, I will have a look

gabesullice’s picture

As @nod_ suggested in #23, we created a meta issue to "frame" the two different ways one might want to consume menu data. You can read the summary here: #3196329: [META] Use cases covered by the decoupled menus initiative

In short, we agreed that we need two approaches on the back end to cater to two different ways of consuming menu data.

  1. For those for whom their menus are global and static (i.e. appear independent of context and do not change from page to page), I made a proposal in #3189459-4: (use case #1) Provide an API response for "stateless menu" use case and we can discuss it there.
  2. For those who have complex and contextual needs (i.e. only show this menus for users with a particular role) and want site builders to have more control over menu placement, we can use this issue. I think the issue summary needs to be updated on this one so that it's more tightly scoped. The proposal is also not ideal for the use case in mind since the IS proposal suggests additional endpoints.
gabesullice’s picture

I've updated the issue summary to better reflect the problem that this issue is trying to solve. I also added a more fleshed out proposed resolution.

Some things to note:

  1. The links are on the "top-level" object, not the node object.
  2. Hierarchy is expressed via the children meta property, this is so that it has at least some symmetry with the proposal in #3189459-4: (use case #1) Provide an API response for "stateless menu" use case. The array values are equivalent to the links object key (e.g. drupal:menu:item:1)
  3. The href is a URL alias, not a JSON:API-specific URL. This is because #3196329: [META] Use cases covered by the decoupled menus initiative proposes to solve URL aliases by using Accept header negotiation (see that issue for an issue link).
  4. The link key drupal:menu:item:2 is not ideal. Ideally, we would use a valid link relation type.
  5. The title key is only "available" in JSON:API 1.1 (Drupal has only implemented 1.0 so far), but since yours truly gets to help decide what is considered valid JSON:API format, I promise this is fine ;)
  6. You will have to decide for yourself the best book of all time is.
  7. This format is verbose. But it's no more verbose than any other HTML would be. In fact, this is a drawback of all RESTful systems that we've known about since the very beginning...
    • The trade-off, though, is that a uniform interface degrades efficiency, since information is transferred in a standardized form rather than one which is specific to an application's needs. The REST interface is designed to be efficient for large-grain hypermedia data transfer, optimizing for the common case of the Web, but resulting in an interface that is not optimal for other forms of architectural interaction.Roy Fielding
    • A future performance optimization could be to allow site builders to opt into document links as separate, linked "linkset" resources. This feature could follow this IETF draft: Linkset: Media Types and a Link Relation Type for Link Sets
  8. This proposal can also serve global/static menu use case too; there's no inherent reason it wouldn't work. It's simply more verbose to send so much data on every request if it's not strictly needed and may not be the intuitive approach for developers who are not bought into the "Drupal way" (if this becomes one of Drupal's "Ways" of course 😉).
nod_’s picture

I'd like to timebox this discussion between now and monday, feb 22nd.

To keep things clear, if there are disagreement on the use cases, please discuss them first on the following issue: #3196329: [META] Use cases covered by the decoupled menus initiative. Implementation details should be discussed only if there is an agreement on the exact use case we're talking about, otherwise it will never end :)

I know it's clear but just in case: we need to agree on what we're trying to solve (the use case) before we talk about how we're going to solve the thing.

I'm happy to make time for a video/audio/chat/email/drawings meeting with hours suitable for any timezone necessary over the coming weeks if that can help facilitate discussion.

nod_’s picture

Title: Decide on an API response format for menu data » (use case #2) Decide on an API response format for menu data

Trying to clarify things a bit