Closed (fixed)
Project:
Decoupled Menus Initiative
Component:
Decision
Priority:
Normal
Category:
Task
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
22 Dec 2020 at 08:00 UTC
Updated:
17 May 2022 at 13:34 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
nod_Comment #3
justafish@_nod I was just reading the app-shell article you linked to in #3186628 and I'm not clear what is required here that would differ from e.g. a fully client-side React application, as both would need the same data if the menu is to be rendered on the fly. The article linked mentions -
which I would expect to be rendered the usual way with Twig if not client-side
Comment #4
gabesulliceBackground
I think a primary challenge here is to avoid making a lot of unnecessary work for ourselves by trying to bend Drupal so much that it breaks. What do I mean?
I think we should avoid the existing APIs for
menu_link_contententities. I think our endpoint should be a read-only endpoint to fetch "menu elements" (since that is similar to what Drupal calls them (see MenuLinkTreeElement)). Menu elements are the individual items in a menu that are supposed to be rendered as links. There are many ways to create these in Drupal:mymodule.links.menu.yml)./core/modules/user/src/Plugin/Menu/LoginLogoutMenuLink.php).menu_link_contentmodule and create entities with the same name).The existing JSON:API endpoints for
menu_link_contententities are only related to #3 and those endpoints were never really intended to be used for rendering a menu. Rather, they're a consequence of JSON:API providing endpoints for all entities of any type. Those endpoints might work if you're trying to recreate an admin UI for customizing a menu, but they're going to unwieldy if all you want is to render menu items.If we try to retroactively make those JSON:API endpoints work, we're going to have lots of extra work for ourselves. E.g. the "view" operation for the menu link content entity type requires the
administer menupermission, because it's about viewing the entity itself, not the menu element that it provides configuration for. That means creating new permissions and/or changing access control handlers. It will also mean trying to weave menu elements that come YAML and PHP into those responses, which will cause BC concerns and force us to write extra logic to block requests that would have been fine before (e.g. rejectPATCHrequests for plugins, but notmenu_link_contententities) and that means we'll have to really mess with the JSON:API module's guts. Let's not do that 😅Proposal
Let's create an entirely new endpoint at
/jsonapi/menu-tree/{menu_machine_name}.Why?
menu_machine_nameinstead of UUID, it will be easier to consume. There is no easy way to find a menu's UUID for non-Drupalists (you have to export config and find the right YAML file). The machine name is right there in the UI (see attached screenshot)/jsonapiwe can hook into the existing JSON:API configuration for base URLs (so you someone could configure it to be/apiif they wanted to)What would the response body look like?
Well, because it would use the JSON:API format, we would already have a headstart on this one. An example response document is below.
Things to note:
datais a single object, not an array of objects. This is so that we have a place to put the menu title (e.g. "Main navigation")typefields aremenuTreeandmenuTreeElement. This is in contrast to typical JSON:API types, e.g.menu--menu, to highlight that these are not typical JSON:API entities (not perfect, but it's a start).descriptionattribute is from the "Administrative summary" field on the attached screenshot.weightattribute. That's because JSON arrays preserve order and the JSON:API spec allows us to give meaning to relationship order. That means we can encode the weight right into the JSON structure itself.enabledfield. That's because I think we should omit these (and their children) automatically. That's business logic that Drupal should handle for the developer.expandedfield. That's because this endpoint is for the global and static use case described in this issue.childrenrelationship field on both themenuTreeandmenuTreeElement. By treating them as relationships, the JSON:API spec allows us to include them by default. However, in the future, we could add support for theincludequery parameter. That would be neat because a developer could use?include=to only get the navigation info,?include=childrento only get the first level of depth,?include=children.children.childrento get three levels of depth, or omit the parameter altogether to get all levels of depth.locationlink). This is because menu link targets often use URIs likeinternal:/node/1, which are not useful to a front end developer. By using a link, we signal that it's an interpreted field and that it can be used "as-is" since it's pointing to a real URL.Known drawbacks
locationis a made-up string. i.e. it's not an registered link relation.I think we can justify these drawbacks though. (1) The size of is mitigated because this is intended to serve an application in a single request. You just get it once and that's it. (2) This can be mitigated by using a JSON:API client. The point is to avoid bikeshedding the structure too much. (3) This is kind of unavoidable since providing menu elements as "data" instead of "hypermedia" goes against the way links are really supposed to be used on the web (our implementation for the stateful, page to page use case can be the "pure" hypermedia design)
Example response body
Comment #5
nod_That looks really really good :) And the arguments make a lot of sense. I like that we use the menu machine name because that's way more friendly than the uuid you're right. Agreed about enabled and expanded attributes.
I'm not sure about the description though, it's supposed to be privileged information, you should have the administer menu permission to view it and we'd be leaking admin data adding it to the response.
The hierarchy thing a small lib could do the job on our end to make it easier to consume.
One question I have is for contrib modules that add images or attributes to menu links where would this data be in the tree? under a generic 'extra' key or they get to choose where they add informations?
Comment #6
gabesulliceAh, yes. Good point. Let's not include it.
Yep! I think it would be cool if the library could output the same object whether you get it from this endpoint or from the data we add to a response for #3186628: (use case #2) Decide on an API response format for menu data. That would mean you could switch between methods without changing your menu component.
I think we could put them under
attributesandrelationships. Do you see a problem with it that I'm missing?Comment #7
nod_nope, no problems, we simply need to give guidelines for contrib to put what where, they might not be familiar with json:api structure :)
a lib that would remove the differences between the 2 apis sounds great, yay for convergence.
Comment #8
nod_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.
Comment #9
decipheredA decoupled application providing an administrative experience will need description.
Otherwise, this looks like a viable approach.
Comment #10
nod_Comment #11
nod_Comment #12
nod_Comment #13
gabesulliceX-post from #3196342-2: Create an endpoint for getting all menu elements for a single menu :
Comment #14
gabesulliceComment #15
nod_for those watching at home, there is a patch for an implementation of this in the related issue
Comment #16
brianperrySince this use case has been implemented in the contrib module, and also ported to core in the following issue: https://www.drupal.org/project/drupal/issues/3227824 I'm changing the status here to fixed.