Custom navigation in a Dexterity/Diazo project
Background
We were tasked with creating a corporate site for Lime Pictures, a media company based in Liverpool. They had already worked on their design internally, and come up with something very stylized, and quite unlike Plone out of the box.
As several other blog posts have already shown, transforming Plone page into something that looks entirely different is quite easy. With this site though, it behaves quite differently too. The navigation is based around a grid, each row being a different section of the site. The first row is special, and shows pages that aren't part of any particular section. This table gives an idea of where each cell in the grid goes.
/ |
/about-us |
/contact |
/hollyoaks |
/hollyoaks/hollyoaks-later |
/hollyoaks/hollyoaks-music-show |
/conker-media |
/conker-media/the-well |
/conker-media/shelfstackers |
As most websites start, HTML mockups were created of the various page types. We then defined some Dexterity types that had all the fields required to populate the pages, and Diazo rules to turn these into CMS powered pages. Whilst we can theme the Plone navigation to look different, there's no navigation that behaves like the above.
Weaving in custom navigation with Diazo
Whilst custom navigation can be included using portlets, registering a portlet for each page to get our navigation isn't necessary with Diazo. Normally when you theme with Diazo, you work against the HTML from Plone. However, the "href" attribute available on most directives allows us to reference any URL and include segments from that as well. For example:-
<append css:theme="div.grid-background"
css:content="div.grid-background div.footer"
href="@@menu_view" />
This calls @@menu_view for the current page, and gets the contents of the footer from that. We just need to register menu_view in our product's configure.zcml and we're away. You will notice that Diazo is just selecting div.footer from @@menu_view, this is particularly handy as:-
- We can make this view generate several bits of useful information for us, not just the footer. plone.app.theming will only fetch @@menu_view once so combining our custom logic into one view makes good sense.
- Content not selected by the theme is never visible, so is ripe for stuffing with debug when you visit the view directly. For example, CSS classes can be pulled out with javascript and displayed below the elements.
plone.app.contentlisting
However, in throwing away existing navigation portlets we've exposed us to a lot of Python/ZPT to potentially write. There's lots of easy mistakes to make like making private content still visible by using contentValues() too. Fortunately plone.app.contentlisting takes a lot of sting out of this. This is a set of helpers to deal with the lists of objects in templates, and gives a common interface to objects regardless of if they are catalog brains or real objects.
Back to our site. @@menu_view has functions in Python to get lists of notes which the template will simply iterate through and format HTML for each. For example, the function to generate the left hand column is:-
def getSections(self):
"""Return all sections (top level objects that go in the left-hand column)"""
sections = [n.getObject() for n
in self.navRoot.restrictedTraverse('@@folderListing')()
if getattr(n,'is_special','')=='menu' and n.isVisibleInNav()]
sections.insert(0,self.navRoot) # Navigation root is the first item
return sections
Security is dealt with for me by plone.app.contentlisting, and isVisibleInNav() ensures that the view honours common Plone options from hiding from navigation.
Separating the logic of what is shown where to how it is shown makes for robust automated tests, which if you have a content profile to import as discussed earlier, is really easy.