Problem/Motivation

Today, themers don't know what to do with menus and breadcrumb structures.

Obviously, it is not a slot, because it is not possible to put any renderable in it : are too much processes in the templates which expect a strict data structure.

But there is no settings type able to host this data, so themers are using fields anyway, and that's bad because the display can break if we put a renderable (scalar, markup, renderable object, render array, or list of renderable) into this field.

Example: Bootstrap 5

https://git.drupalcode.org/project/ui_suite_bootstrap/-/blob/5.0.x/templ...

  fields:
    items:
      type: "render"
      label: "Menu items"
      description: "Full-height and lightweight navigation (including support for dropdowns)."
      preview:
        - title: "Home"
          url: "https://example.com"
        - title: "Library"
          url: "https://example.com"
          below:
            - title: "Sub 1"
              url: "https://example.com"
            - title: "Sub 2"
              url: "https://example.com"
        - title: "Data"
          url: "route:<nolink>"

https://git.drupalcode.org/project/ui_suite_bootstrap/-/tree/5.0.x/templ...

  fields:
    items:
      type: "render"
      label: "Items"
      preview:
        - text: "Home"
          url: "#"
        - text: "Library"
          url: "#"
        - text: "Data"

Example: Material 2

https://git.drupalcode.org/project/ui_suite_material/-/tree/2.0.x/templa...

  fields:
    items:
      type: render
      label: Menu items
      description: A renderable list component.
      preview:
        - type: pattern
          id: list
          fields:
            items:
              - title: Home
                url: "#"
                attributes: {}
              - title: Library
                url: "#"
                below:
                  - title: Sub 1
                    url: "#"
                  - title: Sub 2
                    url: "#"
              - title: Data
                url: "#"

https://git.drupalcode.org/project/ui_suite_material/-/tree/2.0.x/templa...

  fields:
    items:
      type: render
      label: Items
      preview:
        - text: Home
          url: "#"
        - text: Library
          url: "#"
        - text: Data

Proposed resolution

Create a new plugin: Drupal\ui_patterns_settings\Plugin\UIPatterns\SettingType\MenuSettingType which can host a menu-like structure common between menu.

Configuration form:

  • menu (select), pick one menu or the breadcrumb
  • level (quantity), Initial visibility level
  • depth (quantity): Number of levels to display. This maximum number includes the initial level and the final display is dependant of the pattern template.

The plugin is taking the menu tree, do some parameters processes with the level & the depth, and send the data structure to the pattern.

No more menu--main.html.twig, menu--footer.html.twig... presenter templates!!! :)

So, we are here considering the breadcrumb as a flat menu, because the dat structure are very similar, in order to allow using a menu pattern as breadcrumb and the other way around.

It will be also the opportunity of preprocessing the URL objects from the plugin, so the themer will be able to avoid doing it in preprocess hooks. Example: https://git.drupalcode.org/project/ui_suite_bootstrap/-/blob/5.0.x/src/H...

This feature may replace the planned ui_patterns_menu module.

Remaining tasks

Propose a MR.

Do we implement the "The menu is only visible if the menu link for the current page is at this level or below it. " rule from SystemMenuBlock ?

User interface changes

On Layout Builder:

LB

With ui_patterns_pattern_block:

API changes

Using this new plugin is optional, so it will have no impact on themes and site building not using it.

If a theme want to adopt it, they will need to:

  • replace menu items and breadcrumb fields to settings, which will have no impact on the template structure, so it will not break anything
  • update the structure of the breadcrumb pattern, to make it similar to the menu structure, so do the data conversion from the breadcrumb.html.twig presenter template
Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

pdureau created an issue. See original summary.

pdureau’s picture

Issue summary: View changes
pdureau’s picture

First commit: https://git.drupalcode.org/issue/ui_patterns_settings-3345071/-/commit/3...

Already working well. Tested with:

  • ui_pattern_settings 2.x
  • ui_suite_bootstrap 4.0.x
  • layout_builder 10.0.x
  • ui_patterns_pattern_block (with a change in info.yml)

Next steps:

  • Inetgration with breadcrumb
  • Do we need $this->handleInput([$form_id], $def, $form_type);
  • Why the pattern preview doesn't work anymore?
  • Do we implement the "The menu is only visible if the menu link for the current page is at this level or below it. " rule from SystemMenuBlock ?
  • Url objects preprocessing (and test with ui_suite_bootstrap 5.0.x)

grimreaper’s picture

Thanks a lot @pdureau for your work!

update the structure of the breadcrumb pattern, to make it similar to the menu structure, so do the data conversion from the breadcrumb.html.twig presenter template

Either do the structure change in the breadcrumb.html.twig or a preprocess_hook if some variables or objects are not accessible or handleable in the Twig template.

No more menu--main.html.twig, menu--footer.html.twig... presenter templates!!! :)

Nice found, I was not aware of ui_patterns_pattern_block (I hope a release will come soon :)), but I think we will still require those presenter templates to have something working out-of-the-box for core menu blocks.

PS:

Please ensure that menu link attributes still works (#3338283-14: Bootstrap 5: dropdown is too complicated (outdated)), for active trail and or support of modules adding attributes/classes on ul / li / a.

pdureau’s picture

Is it possible to also manage pager (pagination) components with this plugin?

Like menu and breadcrumb, pagers have a strict structure of links and labels, so they belongs to props but are still managed as slots.

Sub-sub elements: items.first, items.previous, items.next, items.last, and each item inside items.pages contain the following elements:

  • href: URL with appropriate query parameters for the item.
  • attributes: A keyed list of HTML attributes for the item.
  • text: The visible text used for the item link, such as "‹ Previous" or "Next ›".

Source: https://api.drupal.org/api/drupal/core!modules!system!templates!pager.ht...
Other: https://api.drupal.org/api/drupal/core!modules!views!templates!views-min...

Example: https://git.drupalcode.org/project/ui_suite_bootstrap/-/blob/5.0.x/templ...

Only one field with a 2 level structure:

  fields:
    items:
      type: "array"
      label: "Pagination items."
      preview:
        first:
          href: "#"
          text: "« First"
        previous:
          href: "#"
          text: "‹ Previous"
        next:
          href: "#"
          text: "Next ›"
        last:
          href: "#"
          text: "Last »"
        pages:
          1:
            href: "#"
            text: "1"
          2:
            href: "#"
            text: "2"
          3:
            href: "#"
            text: "3"

Example: https://git.drupalcode.org/project/ui_suite_material/-/blob/2.0.x/templa...

One field for each first level, a flat structure inside each:

  fields:
    first:
      type: array
      label: First
      description: "Item for the first page; not present on the first page of results."
      preview: null
    previous:
      type: array
      label: Previous
      description: "Item for the previous page; not present on the first page of results."
      preview: null
    next:
      type: array
      label: Next
      description: "Item for the next page; not present on the last page of results."
      preview:
        href: "#"
        text: Page 5
    last:
      type: array
      label: Last
      description: "Item for the last page; not present on the last page of results."
      preview:
        href: "#"
        text: Page 10

Example: https://git.drupalcode.org/project/ui_suite_dsfr/-/blob/1.0.x/templates/...
Same as Bootstrap

  fields:
    items:
      type: array
      label: Items
      preview:
        pages:
          1:
            href: '#page1'
          2:
            href: '#page2'
          3:
            href: '#page3'
          4:
            href: '#page4'
          5:
            href: '#page5'
        first:
          href: '#first'
        last:
          href: '#last'
        next:
          href: '#next'
        previous:
          href: '#prev'

This will also be the opportunity to remove this preprocess hook:

$variables['current'] = (int) (string) $variables['current'];

https://git.drupalcode.org/project/ui_suite_bootstrap/-/blob/5.0.x/src/H...

More complicated example: https://git.drupalcode.org/project/ui_suite_dsfr/-/blob/1.0.x/ui_suite_d...

grimreaper’s picture

Hi,

About pager, if the structure is ok, why not. But I am surprised of that.

Also will the plugin will not be too complicated and would it not be better to split into 3 plugins (also to have different settings options)?
- menus
-- depth of items shown
-- starting level
- breadcrumb
-- add home link?
-- display current page as title?
- pager
-- number of items displayed?

Because also technically when loading the links it is 3 different services:
- menu
- breadcrumb builder
- pager manager

pdureau’s picture

I would prefer to try without splitting the plugin first. I know it will be complicated, but it seems better to keep complexity here, in a stable generic codebase, and offer to the templates builder a single data structure.

This data structure will be a list of link items, optionally multi-levels. In a perfect world, they make their templates with this single structure, without overthinking, and the site builder decide which components they use for menus, breadcrumbs and pagers.

pdureau’s picture

Status: Active » Needs work
grimreaper’s picture

Hi,

Thanks both for the work done here, code and review.

I have added my comments.

pager part not implemented yet or did I miss something?

I hope to be able to help @pdureau with the implementation if needed (not only for pager, I mean).

pdureau’s picture

Assigned: pdureau » Unassigned
Status: Needs work » Needs review

@christian

pdureau’s picture

Title: Add menu setting type » Add links setting type
Issue summary: View changes

"menu" setting type is renamed "links" because it targets also breadcrumbs and pagers

pdureau’s picture

Status: Needs review » Needs work

Some news about the current work

Pierre's proposal

MR: https://git.drupalcode.org/project/ui_patterns_settings/-/merge_requests...

Naming

The setting type is called "menu" but we want "links"

Data structure

We followed the Drupal menu structure https://api.drupal.org/api/drupal/core!modules!system!templates!menu.htm...

With addition of link_attributes property (extracted from Url::getOptions()):

attributes: HTML attributes for the menu item.
below: The menu item child items.
title: The menu link title.
link_attributes: HTML attributes for the link
url: The menu link URL as string
localized_options: Menu link localized options.
is_expanded: TRUE if the link has visible children within the current menu tree.
is_collapsed: TRUE if the link has children within the current menu tree that are not currently visible.
in_active_trail: TRUE if the link is in the active trail.

Same data structure for menus, links (for example language switcher), breadcrumb & pagers (system and views)
The plugin is doing the conversion. We took menu as base because it is the more capable.

Challenge

Cache management was tricky but we did it successfully with: PatternSettingBase::alterElement()

Twig filter

The controversial part. We introduced |convertToMenu() (TODO: rename to |normalizeLinks()) filter to do the conversion from presenter templates.

IMHO, it is OK to have such a filter because never used in components templates

Christian's rewrite

Branch: https://git.drupalcode.org/project/ui_patterns_settings/-/tree/ISSUE-334...

Naming

The setting type is called "link_lists", it is better than "menu" but IMHO "links" is even better.

New plugin type: SettingDataProvider

https://git.drupalcode.org/project/ui_patterns_settings/-/tree/ISSUE-334...

breadcrumb and menu are implemented with this plugin type, so the SettingType plugin is lighter

That's great because it is kind of similar to the target architecture of Ui Patterns 2 (source plugins more powerful and compatible with props) .

Issues

The Twig filter is missing. I know it is not nice but we need to find a way to accomodate presenter template. Maybe we can introduce a public static method in the setting type plugin, which will be easy to use in preprocess hooks inside each theme.

Where is the code related to pagers and links ?

The component cache is no longer invalidated when a menu change.

pdureau’s picture

Assigned: Unassigned » pdureau

pdureau’s picture

Status: Needs work » Needs review

A new MR based on Christian's rewrite: https://git.drupalcode.org/project/ui_patterns_settings/-/merge_requests/16

The Twig filter is not here anymore, so front developers will need to do preprocess in order to convert the links structure.

Examples:

use Drupal\ui_patterns_settings\Plugin\UiPatterns\SettingType\LinksSettingType;

function ui_suite_bootstrap_preprocess_breadcrumb(array &$variables): void {
  $variables["breadcrumb"] = LinksSettingType::normalize($variables["breadcrumb"]);
}

function ui_suite_bootstrap_preprocess_pager(array &$variables): void {
  $variables["items"] = LinksSettingType::normalize($variables["items"]);
}

function ui_suite_bootstrap_preprocess_links(array &$variables): void {
  $variables["links"] = LinksSettingType::normalize($variables["links"]);
}

...

I think it is OK to have such preprocess because they are:

  • one line only, no business
  • not targeting patterns
pdureau’s picture

Before we merge, I would like to discuss a bit about the links data structure.

We decided, and implemented, that:

  • this data structure will be the same as the one in menu.html.twig, with only one property more: link_attributes, because it is the more capable
  • we transform pager, mini pager, links and breadcrumb structure to the expected data structure

However, #3227824: Move the linkset functionality from the decoupled menus contributed module to core's system module tell us about an IETF's proposed linkset format already used by JSON API to avoid creating too many drupalism.

Example:

{
  "linkset":
    [
      { "anchor": "http://example.net/bar",
        "next": [
              {"href":     "http://example.com/foo",
               "type":     "text/html",
               "hreflang": [ "en" , "de" ],
               "title":    "Next chapter",
               "title*":   [ { "value": "nachstes Kapitel" , "language" : "de" } ]
              }
        ]
      }
    ]
}

However, expected properties are not easily mappable:

  • attributes: HTML attributes for the menu item. >> ???
  • below: The menu item child items. >> The linkset media type is not hierarchical. LinksetController::toLinkTargetObjects is creating a weird hierarchy structure instead
  • title: The menu link title. >> title
  • link_attributes: HTML attributes for the link >> All properties except the ones defined. See LinksetController::processCustomLinkAttributes
  • url: The menu link URL as string >> href

And additional properties, sometimes used by components authors, are not mappable at all:

  • localized_options: Menu link localized options. >> ???
  • is_expanded: TRUE if the link has visible children within the current menu tree. >> ???
  • is_collapsed: TRUE if the link has children within the current menu tree that are not currently visible. >> ???
  • in_active_trail: TRUE if the link is in the active trail. >> ???

Christian.wiedemann made their first commit to this issue’s fork.

christian.wiedemann’s picture

Status: Needs review » Fixed

Thanks!

pdureau’s picture

Assigned: pdureau » Unassigned
Status: Fixed » Needs work

hi,

We have a little issue with previews, so i am reopening the ticket.

In this setting type, the data we are storing in the config is not the same as the data we are sending to the component.

For example:

Stored in config Send to render element
menu: "main"
level: 1
depth: 3
    - title: foo
      url: /path
      below: {}
    - title: bar
      url: #anchor
      below: {}

We have a the PatternSettingTypeInterface::settingsPreprocess() method which is doing this job of converting.

However, sometimes, we don't want this conversion. We want the raw data. For example, when we take it from the preview in the definition YML file.

Today, there is no data returned, empty array, so I am proposing this fix:

--- a/src/Plugin/ComplexSettingTypeBase.php
+++ b/src/Plugin/ComplexSettingTypeBase.php
@@ -95,7 +95,7 @@ abstract class ComplexSettingTypeBase extends PatternSettingTypeBase implements
       $this->provider = $instance;
       return $instance->getData($value['configuration'][$provider_id]['config'] ?? []);
     }
-    return [];
+    return $value;
   }
 
   /**

Is it too naive?

pdureau’s picture

Other issue.

Those 2 imports are missing from LinksSettingType :

use Drupal\Core\Url;
use Drupal\Core\Template\Attribute;

And we need to use toUriString() instead of toString()

So, this hook is not working properly:

function template_preprocess_menu(array &$variables): void {
  $variables["items"] = LinksSettingType::normalize($variables["items"]);
}

Also, now we have used this setting type in Bootstrap 4, Bootstrap 5, Material Design, Mozilla Protocol, DSFR... it appears we may not need convertPagerToMenu() & convertMiniPagerToMenu()

pdureau’s picture

Assigned: Unassigned » pdureau

Other feedback.

While working on #3369769: Bootstrap 5: Convert menu, breadcrumb and pagers to the links prop type, we found some changes to do related to the menu API:

Enter <nolink> to display link text only. Enter <button> to display keyboard-accessible link text only.

Source: /admin/structure/menu/manage/{menu_id}/add

Let's also check what is provided by https://www.drupal.org/project/menu_link_attributes integration

I will propose a MR

pdureau’s picture

(removed)

pdureau’s picture

Assigned: pdureau » Unassigned
Status: Needs work » Needs review
pdureau’s picture

Tested with:

  • pdureau committed 207d6352 on 8.x-2.x
    Issue #3345071 by pdureau: Fix links setting type
    
pdureau’s picture

Status: Needs review » Fixed

Shipped in 2.2-alpha1

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

christian.wiedemann’s picture