Mosaic (plone.app.mosaic) provides the ability for Plone site editors to build custom layouts without having to edit templates or build custom views.
As a developer, you can construct the building blocks (tiles) that are available to the editors in the site. But the process for creating new tiles isn’t very straightforward.
For complex custom tiles (tiles with a schema and logic), these are the pieces you need:
Here's an example of how you'll create an Accordion Tile in Plone 5.2 running on Python 3. This tile will be using Bootstrap’s Collapse component so that a block heading is displayed with or without a background image. Clicking on the block heading will expand the tile to show rich text content:
Note: this post was written with plone.app.mosaic = 2.2.1
and plone.jsonserializer = 0.9.9
I recommend starting with the ZCML:
<include package="plone.tiles" file="meta.zcml" />
<plone:tile
name="mysite.accordion"
title="Accordion"
for="*"
add_permission="cmf.ModifyPortalContent"
permission="zope2.View"
class=".tiles.Accordion"
schema=".tiles.IAccordion"
/>
The name
will be referenced later in the registry.xml. class
and schema
point to the pieces you will set up next.
You'll build the tile’s schema. In tiles.py
:
from plone.app.standardtile import _PMF as _
from plone.app.textfield import RichText
from plone.app.z3cform.widget import RelatedItemsFieldWidget
from plone.autoform import directives as form
from plone.supermodel.model import Schema
from zope import schema
class IAccordion(Schema):
title = schema.TextLine(
title=_(u"Accordion title"),
required=True,
)
body = RichText(
title=_(u"Accordion body"),
description=_(u"Text to appear within the accordion panel"),
required=True,
)
image = schema.Choice(
title=_(u"Select an existing image"),
required=False,
vocabulary='plone.app.vocabularies.Catalog',
)
form.widget(
'image',
RelatedItemsFieldWidget,
vocabulary='plone.app.vocabularies.Catalog',
pattern_options={
'recentlyUsed': True,
'selectableTypes': ['Image'],
}
)
The image
field uses the RelatedItemsFieldWidget
, which will allow you to select an image from the Plone site.
Then the class:
from plone.i18n.normalizer.interfaces import IIDNormalizer
from plone.tiles import Tile
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from zope.component import getUtility
class Accordion(Tile):
template = ViewPageTemplateFile('templates/accordion.pt')
def __call__(self):
self.update()
return self.template()
def update(self):
self.title = self.data.get('title', '')
self.body = self.data.get('body', '')
self.tile_id = getUtility(IIDNormalizer).normalize(self.title)
img_uid = self.data.get('image', None)
if img_uid is None:
self.image_url = None
else:
self.image_url = api.content.get(UID=img_uid).absolute_url()
The update()
is doing a couple custom things. One is to create a custom tile_id that will be used by the JS for expanding and collapsing the tiles. (Note, you can also use the tile’s id
for this). Second is to get the image’s url by using the UID that is stored in the field.
Now that the building blocks are in place, you'll need to make the tile addable in a Mosaic layout. Add the following into your registry.xml
<?xml version="1.0"?>
<registry>
<record name="plone.app.tiles">
<value purge="false">
<element>mysite.accordion</element>
</value>
</record>
<records prefix="plone.app.mosaic.app_tiles.mysite_accordion"
interface="plone.app.mosaic.interfaces.ITile">
<value key="name">mysite.accordion</value>
<value key="label">Accordion</value>
<value key="category">advanced</value>
<value key="tile_type">app</value>
<value key="default_value"></value>
<value key="read_only">false</value>
<value key="settings">true</value>
<value key="favorite">true</value>
<value key="rich_text">false</value>
<value key="weight">10</value>
</records>
</registry>
Some important pieces to note here:
category
is the ID of the section where the tile will appear in the Insert menu. You can make a custom category by also registering it in the registry.xmlread_only
is true, the tile can’t be deleted from layout after it has been added.settings
is true, an edit button will appear on the tile in the layoutrich_text
determines whether a minimal editor will show up on the layout’s edit screen (without clicking edit on the tile). I’ve set it to false here since the body is hidden by default.Make sure to import the registry settings after making these changes.
Finally, you can put the template together as follows:
<html><body>
<div tal:define="image view/image_url;
noimage python:not image and 'noimage' or '';
id view/tile_id"
class="accordion-block ${noimage}">
<div class="panel-group" aria-multiselectable="true" role="tablist">
<div class="panel panel-default">
<a aria-controls="collapse-${id}" aria-expanded="false" class="collapsed"
data-toggle="collapse" href="#collapse-${id}" role="button">
<div class="panel-heading" id="heading-${id}" role="tab"
style="background-image: url('${image}');">
<h4 class="panel-title">
<span tal:replace="view/title" />
<span class="fa fa-plus right" aria-hidden="true"></span>
<span class="fa fa-minus right" aria-hidden="true"></span>
</h4>
</div>
</a>
<div aria-labelledby="heading-${id}" class="panel-collapse collapse" id="collapse-${id}" role="tabpanel">
<div class="panel-body">
<div tal:replace="structure view/body/output" />
</div>
</div>
</div>
</div>
</div>
</body></html>
Two very important elements here, which can be easy to miss, are the html
and body
tags! Mosaic will not display the template on the layout edit screen if these are missing.
This template code is made to work with Bootstrap’s Collapse component. Make sure to import the Bootstrap code, or wire up your own styles and interactions.
With all these pieces in place, you should have an addable Accordion tile in your Mosaic pages!