#142: Componentise the global content menu
- Proposed by
- Martin Aspeli
- Seconded by
- Alec Mitchell
- Proposal type
- User interface
- Assigned to release
- Repository branch
- plip142-componentised-content-menu
- State
- completed
Definitions
- (Global) Content Menu
- The green menu in the editable border on a content object, that shows the 'actions', 'add item' and 'state' drop-downs.
- Content provider
- A pluggable view component. By putting a statement like <div tal:replace="structure provider:plone.contentmenu"/> in main_template, and register an adapter to IContentProvider with the name "plone.contentmenu", the content-provider will be rendered in place of that statement. A contentprovider is a view class with a "render()" method, among other things.
- Adapter
- A Zope 3 component that allows any registered object (such as a content object being viewed) to be adapted to a given interface. Thus, other components can work in terms of a generic interface, and the component architecture will find and load an appropriate adapter that implements that interface for the context in which the adapter is working.
- Utility
- A stateless Zope 3 component. As its name implies, a utility typically performs generic functions. Utilities are registered by interface and optionally name. Thus, the utilities machinery in Zope 3 acts as a generic registry - you can easily look up all utilities implementing some given interface, for example.
Motivation
Consider a versioning solution. A natural way to enable version control would be to use a 'versions' sub-menu next to the 'state' sub-menu, and a staging solution, that wants to use a 'stage' menu to push an object to a different stage.
The only way either of these could add a drop-down to the content menu would be to customise global_contentmenu.pt. However, if they both did, they would conflict. Also, since this template is very complex, the chances of something breaking would be high.
We currently use CMF actions to enable new items in the 'actions' menu. However, actions are single-level by nature. There is no clean way to let an action define a whole menu.
Proposal
The content menu should be implemented with Zope 3 menus.
The top-level (horizontal) menu can be viewed as a set of sub-menu-items - that is, menu items that point to a sub-menu. Menu items in Zope 3 menus are simply adapters from (context, request) to, in this case, IContentMenuItem. Because of this, menus can be overriden or plugged in based on the context, or with an overrides.zcml.
The top-level menu itself should be plugged into the UI with a Zope 3 content-provider (see the zope.contentprovider package). Doing so also allows the inclusion of the menu GUI itself to be overriden by more specific adapters (again, a content-provider is just an adapter) or overrides.zcml.
Implementation
A content provider with the rendering logic, and an associated page template is registered in the "browser" package. This invokes a top-level menu called "contentMenu", registered as a normal menu in configure.zcml.
The contentMenu contains one menu-item for each of the sub-menus, actionsMenu, displayMenu, factoriesMenu and workflowMenu. Each of these in turn is a sub-menu-item. Furthermore, they are ordered in ascending order, but with gaps - 10, 20, 30 40 - to allow new menu items to be plugged in between them.
Because the menu items need customised logic, each will be implemented with a custom adapter sub-classing BrowserSubMenuItem.
Each sub-menu item points to a sub-menu by name. These sub-menus are again implemented with custom utilities, because they need additional logic. For example, the actions menu will look up actions in portal_actions, whilst the factories menu will look up addable types in the context.
The rendering page template then becomes a simple iteration of the menu items and sub-menu items in the contentMenu.
One direct benefit of this refactoring of menu logic from a page template to python code for aggregating menu items is that the menu can be unit tested. There is an incredible amount of logic in the current page template that can only be tested through-the-web.
Deliverables
A working implementation that ships with Plone.
Risks
Backwards compatability
- Existing overrides of the global_contentmenu may be broken by this
implementation. This shouldn't be necessary - the existing APIs may
need deprecation, but should not be removed immediately. This can be achieved by having the page template that the content-provider uses to render itself be looked up in the skin layers rather than placed in the browser package, at least until the deprecation period is over. This way, existing skin layers overriding global_contentmenu.pt would still be in effect.
Progress log
- Implemented/extended Zope 3 menu infrastructure to support Plone's menus
- Ported existing individual menus to this infrastructure
- Wrote lots of tests!
- Made a content provider to render the menu
Some more real-world testing is needed, but this work is probably at least beta quality.