Changes to the Plone portlets infrastructure
A list of things we may want to do to simplify the portlets infrastructure and make it more useful.
Alexander Limi recently wrote a series of blog posts where lays out a vision for the future of Plone's UI. Two key themes run through these suggestions:
- Reduce the number of concepts; whilst
- Base the UI on standard, re-usable components
I very much see plone.portlets as the basis of many of the changes he proposes. This includes:
- Portlets are generally renamed to "widgets" in the UI to signify wider applicability. (Other names that have been suggested include "gadgets", "boxes", "blocks" or "tiles" - I don't particularly care).
- We write standard portlets for things like folder listings, Collection-style canned searches, image/file asset re-use and so on. These can be re-used in various parts of the UI.
- Every Page becomes capable of holding a list of associated portlets. These are represented by placeholder images in the raw markup (and managed via a Kupu GUI - see Alex's screencast), and are rendered as inline portlets via a transform when the page itself is shown.
- Portlets may have multiple representations (renderers), depending on where they're shown. The same portlet would be able to look different when shown in the page body than when assigned to a column.
We have also discussed some general changes in the way portlets are managed and assigned to locations. (You may want to read my previous blog post about how they work now) Briefly:
- We introduce a new global portlet category analogous to "group" and "content type" portlets, called "site-wide" portlets. These will be managed via the Plone control panel. They can be blocked/shown on a folder-by-folder basis, much like group and content type portlets are now. By default, they show up on every content item.
- Contextual portlets (those assigned to content items rather than one of the global categories) will not be shown in subfolders by default.
- For each contextual portlet assignment, the user can choose to enable "inheritance" into subfolders.
The aim here is to move away from the idea of needing portlets to "inherit" from the site root in order to get "global" or site-wide portlets, and to make it easier to control where portlets are shown. Currently, users need to do too much thinking about where things are assigned and how they are blocked/unblocked. It is also too difficult currently to let one portlet inherit whilst blocking others. By moving to a model where you opt into inheritance rather than opt out of it, and where this is done per-portlet rather than per-category for contextual portlets, the user is much more in control.
Next, we want to simplify a few aspects of the plone.portlets package:
- Ditch the notion of a separate IPortletDataProvider and IPortletAssignment, which was intended to allow a portlet assignment to delegate to another object to provide data for the portlet renderer. This didn't really work in practice.
- Deprecate/ignore the IPortletContext. This is a "plugin" adapter that allows us to have a generic portlet retriever algorithm in plone.portlets, without depending on Zope 2/Plone concepts such as aq_parent, user objects or groups. However, it makes the algorithm in the standard IPortletRetriever unnecessarily complex and generic. It'd be much better to just write one or more Plone-specific IPortletRetriever adapters as required.
- Unify and generalise the namespace traversal adapters for ++contextportlets++, ++groupportlets++ and so on. We should be able to have a single version, ++portlets++.
- Make it possible to render a portlet using traversal only. That is, if you traverse to ../++portlets++global/myassignment/@@view (in a tal:replace or a URL), this should render the portlet using the standard portlet renderer. This makes it possible to do all kinds of clever things with traversal to identify and render one or more portlets. In particular, it should mean that to render a bunch of portlets, you only need to know their paths, rather than having to fetch the assignment object and adapt it to the renderer.
Finally, we want to change the way portlet managers are rendered. The current implementation is based on the notion of portlets as "viewlets plus persistent assignments/configuration". Therefore, you render a portlet manager using a provider: expression, exactly as you'd render a viewlet manager (or another zope.contentprovider content provider), e.g.
<div tal:content="structure provider:my.portletmanager" />
This will look up a portlet manager renderer, which will ask the portlet retriever to find the portlet assignments to render, look up the renderer (view) for each one, and render them in a tal:repeat loop inside a particular template.
This approach led us to have a separate portlet manager for the left column and right column, and four different ones for four dashboard columns. In hindsight, this isn't a very good idea. It'd be much better if a portlet manager represented the type of location where portlets may be rendered.
It is already possible to vary the implementation of the portlet renderer based on the type of portlet manager it's shown in, but this currently isn't used much. It should be, though. And it should be easier to move portlets around (e.g. with drag-and-drop) inside a portlet manager, e.g. between the four columns of the dashboard or the left/right columns of the site. And of course, it should be easy to add new columns or change the layout on the fly, especially on the dashboard.
So, we may have portlet managers like:
- Portal header section
- Portal columns
- Portal footer
- Main body text (inline portlets)
Each of these would have a unique name and probably a unique marker interface or class.
Then, we should add the notion of a layout to the portlet manager. The layout is just a grid-based mapping of where each portlet should be rendered (stored in an annotation or the portlet manager local utility). Thus, the "portal columns" portlet manager could have a layout of two columns (left, right) and an arbitrary number of rows. The dashboard manager may have four columns and unlimited rows. The header may have two rows and four columns. The footer may be similar. We could then render bits of the layout individually, e.g.
<div tal:replace="structure python:portlets.render_grid(manager='plone.header', row=0)" />
I'm not quite sure about the optimal API here. We need ways to say "render one column", "render one row", "render one cell" and so on. We probably also want ways to apply a filter, e.g. render this grid, but omit this one portlet. It shouldn't be too hard to come up with a sensible API though.
Finally, we should think about whether it makes sense to manage viewlets using the portlets GUI. Perhaps we should think of viewlets as "widgets without persistent configuration" and portlets as "widgets with persistent configuration". This way, we could have a default configuration where the Plone search box viewlet was rendered in the "plone.header" portlet manager's top-right grid cell to begin with, but make it possible to move it to another grid cell or another portlet manager if desired. This also suggests that we should unify the way that portlets and viewlets are written and registered. I'd quite like to tackle that in the context of making Grok-like directives for portlets.
This requires a bit more thought, and we certainly don't want to make it harder to write and register viewlets, but it probably makes sense to unify viewlets and portlets to reduce confusion for new users.