<< All Blog Posts
Flexible Tile Layouts in Plone Using Mosaic

Flexible Tile Layouts in Plone Using Mosaic

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:

  • declaration in zcml
  • field schema
  • class for logic
  • template
  • registry entry to add the tile to plone.app.tiles
  • registry entry for the tile itself
  • styles and javascript

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:

I recommend starting with the 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.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:

  • The name of the record for the tile uses an underscore instead of a dotted name
  • 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.xml
  • When read_only is true, the tile can’t be deleted from layout after it has been added.
  • When settings is true, an edit button will appear on the tile in the layout
  • rich_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!


Thanks for filling out the form! A Six Feet Up representative will be in contact with you soon.

Have a technical question? Ask a Sixie!