5.2.5. Breadcrumbs
How the breadcrumbs in the pathbar are constructed.
Plone 2.5 actually ships with two implementations of the breadcrumbs. Both can be used as the view that implements CMFPlone.browser.interfaces.INavigationBreadcrumbs. The older, currently disabled method is found in CMFPlone.browser.navigation.CatalogNavigationBreadcrumbs, and, as it name implies, uses a catalog query to find the breadcrumbs.
The newer method is found in CMFPlone.browser.navigation.PhysicalNavigationBreadcrumbs. This walks up the acquisition parent hierarchy to find each parent. The astute reader will remember that waking objects like this is generally bad. However, some benchmarking revealed that since the direct parents of the context are quite likely to be in the ZODB cache, and since there are not likely to be very many of them, the cost of traversing to these objects is smaller than the cost of constructing and executing a catalog query.
By being based on real objects, the breadcrumbs can make more extensive use of views. The breadcrumbs are constructed in reverse order, starting at the depeest node. The parents are prepended to the breadcrumbs trail of this node by recursively doing a view lookup on the parent, like so:
def breadcrumbs(self):
context = utils.context(self)
request = self.request
container = utils.parent(context)
...
view = getMultiAdapter((container, request), name='breadcrumbs_view')
base = tuple(view.breadcrumbs())
...
rootPath = getNavigationRoot(context)
itemPath = '/'.join(context.getPhysicalPath())
...
if not utils.isDefaultPage(context, request) and not rootPath.startswith(itemPath):
base += ({'absolute_url': item_url,
'Title': utils.pretty_title_or_id(context, context),
},)
return base
The traversal is stopped at the navigation root by the following degenerate view:
class RootPhysicalNavigationBreadcrumbs(utils.BrowserView):
implements(INavigationBreadcrumbs)
def breadcrumbs(self):
# XXX Root never gets included, it's hardcoded as 'Home' in
# the template. We will fix and remove the hardcoding and fix
# the tests.
context = utils.context(self)
return ()
Then, the two views are registered in CMFPlone/browser/configure.zcml as follows:
<browser:page
for="*"
name="breadcrumbs_view"
class=".navigation.PhysicalNavigationBreadcrumbs"
permission="zope.Public"
allowed_attributes="breadcrumbs"
/>
<browser:page
for=".interfaces.INavigationRoot"
name="breadcrumbs_view"
class=".navigation.RootPhysicalNavigationBreadcrumbs"
permission="zope.Public"
allowed_attributes="breadcrumbs"
/>
This pattern is quite elegant, and allows for more specific implementations to be plugged based on the interface of the object, rather than having to substitute it globally.