Work On Plone 6 Navigation Menus

Many websites need an automatically populated nested navigation menu, and the new Plone 6 frontend, Volto, did not provide one. I've implemented a fully featured version but am blocked by some flaws and seeking help to resolve them.

About

I came to Plone 6 looking for a good platform for a browsable info-sharing website. Currently Volto does not provide navigation by which you can discover and traverse a website below the top level, and there are no working addons to provide the functionality. For me, this is a showstopper. Starting with one of the malfunctioning addons, codesyntax/volto-menu-customization, I've implemented fully featured basic dropdown-menu navigation. However, I need help resolving a bug.

To convey the issue I've implemented two versions of an addon that implements the functionality, one demonstrating the problem and the other demonstrating an awkward workaround. I have a prototype website that includes the awkward workaround, where you can try out the navigation. The site also includes a copy of this exposition.

The version on the site that I am using, which I call "oblique", is not the one I advocate. Using it is too awkward and confusing. However, it is less intrusive on other use of the site, for reasons that will become clear below.

The Versions: Stubborn and Oblique

The quickest way to introduce the specifics is with the NPM addon packages for both variants. The versions of the addon each include a README.md that describes the general situation and their respective specific workaround:

The sidebar for both of those package pages have a link to a github repository with the code for both, each in a separate branch.

The Problem With My Implementation

  • Volto uses the React Semantic UI, which includes a decent Dropdown menus facility. Using that it's fairly easy to attach recursively populated dropdown menus to our default Volto navbar header entries.
  • What's complicated is associating the right link behavior with the dropdown menu container entries.
  • Inferring from the Volto NavItem objects (volto/NavItem.jsx at master · plone/volto), it looks like the link needs to be implemented as a React NavLink object (NavLink v6.6.2 | React Router).
  • Fortunately, Dropdowns implement an as= option that we can use to add NavLink functionality to the Dropdown container entry:
1          <Dropdown simple
2                    text={item.title}
3                    [...]
4                    as={NavLink}
5                    key={item.url} >
  • Doing this makes the menu items for the container entries traverse to the right place on selection, enabling customary navigation menus that people commonly use.
  • Unfortunately, the combination of NavLink and Dropdown causes the menus for selected pages to stay open, even on page refresh. You can't close the menus (and it's not a good option to require manually closing them, even if we found a way to do so).
    • I believe that the problem is because the NavLink objects signal being on the path of the currently visited page using the CSS class named "active", while Dropdown menus use that same class name to recognize and maintain active - open –state.
    • So when the NavLink functionality recognizes active state, because it's by definition on the path to the item that's being selected, it adopts the CSS active class and that causes the Dropdown to remain open.
    • This is an unfortunate collision in the use of a CSS class. I'm not sure that's causing the problem, but it makes sense to me that it would.

The Two Versions

The Stubborn Alternative

Whether or not the above analysis of the exact cause is correct, I have not found a way to prevent the menus from staying open when the NavLink functionality is added. You can see this behavior by including the @myriadicity/volto-navigation-dropdown-stubborn - npm (npmjs.com) addon in it Volto frontend.

Aside from the stubborn-open-menu behavior, I believe this functionality needs to be implemented as the navbar default behavior shipped with Volto. There are other rough edges – there is enough of a gap between where you hover to open the menu and the menu that it sometimes closes when moving the cursor to it. CSS tweaking would be needed to settle those kinds of things, but I expect that people with good CSS skills can take care of that. The more important piece is resolving the stubbornness.

The Oblique Alternative

One way to avoid the stubborn problem is to omit the NavLink functionality. That means people can't click on the primary entry for the container to visit it. Instead, we add a secondary entry for the container as the top, header-like item on its contents menu. Users can then select that secondary entry to visit the container. This is implemented in the @myriadicity/volto-navigation-dropdown-oblique - npm (npmjs.com) package, and what is currently running on my prototype instance.

It's not a good solution. Visitors will first be puzzled when they try to click on the primary entry for a container, as you would do on any regular navigation menu. If they notice the secondary entry they can click on that. Some won't notice the secondary entry and give up.

Trying Them and Wrap-up

I've been using both versions on my prototype site and can tolerate both. The stubborn version would quickly become untenable, though, as menus become longer and get in the way of site content and other stuff. So the oblique version is the one I'm regularly using. It's awkward but workable. I continue to frequently stumble, trying to select the primary container entries to visit the containers. Add to that the confusion it will cause on first encounter, and thwarting that it will cause some users, and it's clearly not a viable workaround for general use.

You can try the oblique versions on my prototype website by visiting do.myriadicity.net. To try the stubborn one you'll have to build a Plone 6 instance with the @myriadicity/volto-navigation-dropdown-stubborn - npm (npmjs.com) addon.

I believe that something like the stubborn option, without the non-closing bug, is necessary for Plone 6 uptake, and would welcome suggestions for resolving the problem. Feel free to fork my repository: kenmanheimer/volto-navigation-dropdown: Plone website navigation dropdown menus (github.com).

3 Likes

Hi,
unfortunately I can't really help you with your problem other than agreeing with you that this behavior (oblique variant) of the navigation is feeling faulty or strange and confusing for me too.
However, what I noticed is that many of the major websites that are also listed at the bottom of the Volto Github page (GitHub - plone/volto: React-based frontend for the Plone Content Management System) show this behavior.
But I've also seen this behavior on Plone 5.2 pages.

I can't find the place where I read that, but this behavior is probably justified by the fact that these "large" navigation areas are safer to use. Personally, I find the click to open the menu and another click to get to the first page of the sub-navigation very unusual and would be very happy to have a navigation that shows the "old" behavior.

They look like the kind of thing that people can implement using something like collective/volto-dropdownmenu: Volto addon for a customizable dropdown menu (github.com), by which you can arrange static dropdown menus for your site. I can see how those make sense for sites with carefully planned arrangements that are fairly static. I suppose if one thinks in terms of large, conventional institutional sites, they might not see the need for a navigation system that automatically adapts to growing and changing site organization. But I believe one of Plone's core strengths is versatility and dynamicism. It should be agile enough to handle more dynamic sites.

I guess the oblique version of my implementation – maybe with better distinction of the "header" entry for the container – could suffice. I hope, though, that we can find a solution so the stubborn form can be used without the stubbornness...

I’ve made significant progress, implementing proper behavior of automatically constructed navigation dropdown menus that are exposed on hover. It’s fully functional version except for some edge-case problems described below, and available as a Volto addon npm module: @myriadicity/volto-navigation-dropdown - npm (npmjs.com) (currently revision 0.2.2). You can also visit my prototype site with the current version installed at https://do.myriadicity.net/, though I can’t say for how long it will be available and/or configured as it currently is.

I’ve worked around the “stubborn” behavior by using standard menu tags with the dropdown classes, instead of the specialized dropdown tags (Dropdown, Dropdown.Menu, etc) combined with the as= attribute. It works without the “stubborn” persistence of the active menus and without having to resort to the “oblique” workaround. I think we’re most of the way there. This brings us to the point where we have to resolve some edge-case boundary conditions (all too literally, it so happens):

  1. Apparently the react Semantic UI simple dropdowns don’t account for page margins, so that the exposed menus can overlap with and even be completely over the edge of the page. When the full top-level navbar is displayed, i.e. not collapsed to the hamburger menu, then there is no way to scroll the page to see them.

    This can probably be resolved using the dropdown menu on* handler attributes, like onClick, to check the menu location and adjust it if out of bounds.

  2. Behaviors of the simple dropdown menus are a bit obtuse in mobile mode, and need to will need some tweaking as well.

    It may turn out to be best to use the implementation that I provided in the “opaque” strategy, or otherwise adjust the behavior for the mobile context.

I aim to work on these issues next chance I get, but would happily welcome contributions that resolve them! My ultimate aim is to submit a pull request for the GitHub - plone/volto: React-based frontend for the Plone Content Management System repository, to propose pushing the functionality to Volto proper, but in the meanwhile am fine with developing the functionality in the addon and making it available for anyone to use.

The npm module page has links to the code repository and the alternate implementations with funky problems.

I also revisited the codesyntax/volto-menu-customization implementation to see the progress they made. It still has just two levels hard wired in, which is a different choice than I made, but I see they’ve incorporated behavior conditioning according to the state of the hamburger menu and mobile platform, so figure there will be useful clues there for conditioning behavior in my volto-navigation-dropdown.

5 Likes

I have a volto project and want to try volto-navigation-dropdown, but install failed.
Please help.

yarn add @myriadicity/volto-navigation-dropdown
➤ YN0000: ┌ Resolution step
➤ YN0035: │ @kenmanheimer/volto-navigation-dropdown@npm:*: The remote server failed to provide the requested resource
➤ YN0035: │   Response Code: 404 (Not Found)
➤ YN0035: │   Request Method: GET
➤ YN0035: │   Request URL: https://registry.yarnpkg.com/@kenmanheimer%2fvolto-navigation-dropdown
➤ YN0000: └ Completed in 1s 32ms
➤ YN0000: Failed with errors in 1s 33ms
npm install @myriadicity/volto-navigation-dropdown
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: @reach/router@1.3.4
npm WARN Found: react@17.0.2
npm WARN node_modules/react
npm WARN   peer react@"^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" from decorate-component-with-props@1.2.1
npm WARN   node_modules/decorate-component-with-props
npm WARN     decorate-component-with-props@"^1.0.2" from draft-js-inline-toolbar-plugin@2.0.3
npm WARN     node_modules/draft-js-inline-toolbar-plugin
npm WARN     2 more (draft-js-plugins-editor, @plone/volto)
npm WARN   127 more (draft-js-block-breakout-plugin, ...)
npm WARN
npm WARN Could not resolve dependency:
npm WARN peer react@"17.0.2" from react-dom@17.0.2
npm WARN node_modules/react-dom
npm WARN   peer react-dom@">=15.0.0" from draft-js-block-breakout-plugin@2.0.1
npm WARN   node_modules/draft-js-block-breakout-plugin
npm WARN   86 more (react-animate-height, react-colorful, ...)
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: @reach/router@1.3.4
npm WARN Found: react@17.0.2
npm WARN node_modules/react
npm WARN   peer react@"^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" from decorate-component-with-props@1.2.1
npm WARN   node_modules/decorate-component-with-props
npm WARN     decorate-component-with-props@"^1.0.2" from draft-js-inline-toolbar-plugin@2.0.3
npm WARN     node_modules/draft-js-inline-toolbar-plugin
npm WARN     2 more (draft-js-plugins-editor, @plone/volto)
npm WARN   127 more (draft-js-block-breakout-plugin, ...)
npm WARN
npm WARN Could not resolve dependency:
npm WARN peer react@"17.0.2" from react-dom@17.0.2
npm WARN node_modules/react-dom
npm WARN   peer react-dom@">=15.0.0" from draft-js-block-breakout-plugin@2.0.1
npm WARN   node_modules/draft-js-block-breakout-plugin
npm WARN   86 more (react-animate-height, react-colorful, ...)
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: @reach/router@1.3.4
npm WARN Found: react@17.0.2
npm WARN node_modules/react
npm WARN   peer react@"^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" from decorate-component-with-props@1.2.1
npm WARN   node_modules/decorate-component-with-props
npm WARN     decorate-component-with-props@"^1.0.2" from draft-js-inline-toolbar-plugin@2.0.3
npm WARN     node_modules/draft-js-inline-toolbar-plugin
npm WARN     2 more (draft-js-plugins-editor, @plone/volto)
npm WARN   127 more (draft-js-block-breakout-plugin, ...)
npm WARN
npm WARN Could not resolve dependency:
npm WARN peer react@"17.0.2" from react-dom@17.0.2
npm WARN node_modules/react-dom
npm WARN   peer react-dom@">=15.0.0" from draft-js-block-breakout-plugin@2.0.1
npm WARN   node_modules/draft-js-block-breakout-plugin
npm WARN   86 more (react-animate-height, react-colorful, ...)
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: @reach/router@1.3.4
npm WARN Found: react@17.0.2
npm WARN node_modules/react
npm WARN   peer react@"^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" from decorate-component-with-props@1.2.1
npm WARN   node_modules/decorate-component-with-props
npm WARN     decorate-component-with-props@"^1.0.2" from draft-js-inline-toolbar-plugin@2.0.3
npm WARN     node_modules/draft-js-inline-toolbar-plugin
npm WARN     2 more (draft-js-plugins-editor, @plone/volto)
npm WARN   127 more (draft-js-block-breakout-plugin, ...)
npm WARN
npm WARN Could not resolve dependency:
npm WARN peer react@"17.0.2" from react-dom@17.0.2
npm WARN node_modules/react-dom
npm WARN   peer react-dom@">=15.0.0" from draft-js-block-breakout-plugin@2.0.1
npm WARN   node_modules/draft-js-block-breakout-plugin
npm WARN   86 more (react-animate-height, react-colorful, ...)
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: @reach/router@1.3.4
npm WARN Found: react@17.0.2
npm WARN node_modules/react
npm WARN   peer react@"^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" from decorate-component-with-props@1.2.1
npm WARN   node_modules/decorate-component-with-props
npm WARN     decorate-component-with-props@"^1.0.2" from draft-js-inline-toolbar-plugin@2.0.3
npm WARN     node_modules/draft-js-inline-toolbar-plugin
npm WARN     2 more (draft-js-plugins-editor, @plone/volto)
npm WARN   127 more (draft-js-block-breakout-plugin, ...)
npm WARN
npm WARN Could not resolve dependency:
npm WARN peer react@"17.0.2" from react-dom@17.0.2
npm WARN node_modules/react-dom
npm WARN   peer react-dom@">=15.0.0" from draft-js-block-breakout-plugin@2.0.1
npm WARN   node_modules/draft-js-block-breakout-plugin
npm WARN   86 more (react-animate-height, react-colorful, ...)
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: @reach/router@1.3.4
npm WARN Found: react@17.0.2
npm WARN node_modules/react
npm WARN   peer react@"^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" from decorate-component-with-props@1.2.1
npm WARN   node_modules/decorate-component-with-props
npm WARN     decorate-component-with-props@"^1.0.2" from draft-js-inline-toolbar-plugin@2.0.3
npm WARN     node_modules/draft-js-inline-toolbar-plugin
npm WARN     2 more (draft-js-plugins-editor, @plone/volto)
npm WARN   127 more (draft-js-block-breakout-plugin, ...)
npm WARN
npm WARN Could not resolve dependency:
npm WARN peer react@"17.0.2" from react-dom@17.0.2
npm WARN node_modules/react-dom
npm WARN   peer react-dom@">=15.0.0" from draft-js-block-breakout-plugin@2.0.1
npm WARN   node_modules/draft-js-block-breakout-plugin
npm WARN   86 more (react-animate-height, react-colorful, ...)
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: file-loader@4.3.0
npm WARN Found: webpack@5.76.1
npm WARN node_modules/webpack
npm WARN   peer webpack@">=2" from babel-loader@8.3.0
npm WARN   node_modules/babel-loader
npm WARN     babel-loader@"^8.0.6" from razzle@4.2.18
npm WARN     node_modules/razzle
npm WARN     8 more (@storybook/builder-webpack5, ...)
npm WARN   39 more (circular-dependency-plugin, copy-webpack-plugin, ...)
npm WARN
npm WARN Could not resolve dependency:
npm WARN peer webpack@"^4.0.0" from file-loader@4.3.0
npm WARN node_modules/file-loader
npm WARN   file-loader@"^4.3.0" from razzle@4.2.18
npm WARN   node_modules/razzle
npm WARN   1 more (url-loader)
npm WARN
npm WARN Conflicting peer dependency: webpack@4.47.0
npm WARN node_modules/webpack
npm WARN   peer webpack@"^4.0.0" from file-loader@4.3.0
npm WARN   node_modules/file-loader
npm WARN     file-loader@"^4.3.0" from razzle@4.2.18
npm WARN     node_modules/razzle
npm WARN     1 more (url-loader)
npm WARN ERESOLVE overriding peer dependency
npm WARN While resolving: stylelint-order@5.0.0
npm WARN Found: stylelint@15.10.3
npm WARN node_modules/stylelint
npm WARN   peer stylelint@">=11" from stylelint-config-idiomatic-order@9.0.0
npm WARN   node_modules/stylelint-config-idiomatic-order
npm WARN     stylelint-config-idiomatic-order@"9.0.0" from @plone/volto@17.15.2
npm WARN     node_modules/@plone/volto
npm WARN     2 more (volto-mrcportal, the root project)
npm WARN   6 more (stylelint-config-sass-guidelines, ...)
npm WARN
npm WARN Could not resolve dependency:
npm WARN peer stylelint@"^14.0.0" from stylelint-order@5.0.0
npm WARN node_modules/stylelint-order
npm WARN   stylelint-order@"^5.0.0" from stylelint-config-idiomatic-order@9.0.0
npm WARN   node_modules/stylelint-config-idiomatic-order
npm WARN
npm WARN Conflicting peer dependency: stylelint@14.16.1
npm WARN node_modules/stylelint
npm WARN   peer stylelint@"^14.0.0" from stylelint-order@5.0.0
npm WARN   node_modules/stylelint-order
npm WARN     stylelint-order@"^5.0.0" from stylelint-config-idiomatic-order@9.0.0
npm WARN     node_modules/stylelint-config-idiomatic-order
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR!
npm ERR! While resolving: ts-jest@26.5.6
npm ERR! Found: typescript@5.2.2
npm ERR! node_modules/typescript
npm ERR!   peer typescript@">= 2.7" from fork-ts-checker-webpack-plugin@6.5.3
npm ERR!   node_modules/fork-ts-checker-webpack-plugin
npm ERR!     fork-ts-checker-webpack-plugin@"^6.0.4" from @storybook/builder-webpack5@6.5.15
npm ERR!     node_modules/@storybook/builder-webpack5
npm ERR!       dev @storybook/builder-webpack5@"^6.5.15" from the root project
npm ERR!     fork-ts-checker-webpack-plugin@"^6.0.4" from @storybook/core-common@6.5.16
npm ERR!     node_modules/@storybook/core-common
npm ERR!       @storybook/core-common@"6.5.16" from @storybook/addon-essentials@6.5.16
npm ERR!       node_modules/@storybook/addon-essentials
npm ERR!         dev @storybook/addon-essentials@"^6.3.0" from the root project
npm ERR!       7 more (@storybook/addon-controls, @storybook/react, ...)
npm ERR!     2 more (@storybook/core-common, @storybook/core-common)
npm ERR!   peer typescript@">= 4.3.x" from react-docgen-typescript@2.2.2
npm ERR!   node_modules/react-docgen-typescript
npm ERR!     react-docgen-typescript@"^2.1.1" from @storybook/react-docgen-typescript-plugin@1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0
npm ERR!     node_modules/@storybook/react-docgen-typescript-plugin
npm ERR!       @storybook/react-docgen-typescript-plugin@"1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0" from @storybook/react@6.5.16
npm ERR!       node_modules/@storybook/react
npm ERR!         dev @storybook/react@"^6.3.0" from the root project
npm ERR!   7 more (cosmiconfig, cosmiconfig, ts-api-utils, ts-loader, ...)
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer typescript@">=3.8 <5.0" from ts-jest@26.5.6
npm ERR! node_modules/ts-jest
npm ERR!   dev ts-jest@"^26.4.2" from the root project
npm ERR!
npm ERR! Conflicting peer dependency: typescript@4.9.5
npm ERR! node_modules/typescript
npm ERR!   peer typescript@">=3.8 <5.0" from ts-jest@26.5.6
npm ERR!   node_modules/ts-jest
npm ERR!     dev ts-jest@"^26.4.2" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
  • Volto 17.15.2
  • Plone 6.0.10

Sorry about the slow slow response. At some point in the past half year the frontend migrated to a more recent version of a core package, I think it was typescript. This meant that any packages that used older features, particularly some import features, needed to be upgraded, and more insidiously, dependencies on packages that used those old features (and were bound to older versions of the underlying package) led to incompatibilities like what you report. I'm not sure that's the problem, but think it's likely.

To identify the problem you could make a fresh cookiecutter build and gradually include your old dependencies until you identify where the conflict is. By adding volto-navigation-dropdown early in the process you should see that it is compatible with the current build environment. (The last time I did a build was at the end of March, but I don't think there have been any major upheavals in the build base since then.)