This guide provides an overview of Diazo theming in Plone.
A "theme" makes a website (in this case, one powered by Plone) take on a particular look and feel.
Diazo (formerly known as XDV) is a technology that can be used to theme websites. It is not specific to Plone per se, but has been created by the Plone community and, as of Plone 4.3, provides the default way to apply a theme to a Plone site. You can learn more about Diazo at http://diazo.org.
Diazo themes may be a little different to themes you have created in other systems, and indeed to themes you may have created for earlier versions of Plone. A Diazo theme is really about transforming some content - in this case the output from "vanilla" Plone - into a different set of HTML markup by applying a set of rules to combine a static mock-up of the end result you want with the dynamic content coming from Plone.
In comparison, the previous way to theme a Plone site (like the way many other content management systems are themed) relies on selectively overriding the templates and scripts that Plone uses to build a page with different versions that produce different HTML markup. The latter approach can be more powerful, certainly, but also requires much deeper knowledge of Plone's internals and command of server-side technologies such as Zope Page Templates and even Python. Diazo themes, in contrast, are easy to understand for web designers and non- developers alike.
A Diazo theme consists of three elements:
One or more HTML mockups, also referred to as theme files, that represent the desired look and feel.
These will contain placeholders for content that is to be provided by the Plone content management system. Mockups usually reference CSS, JavaScript and image files by relative path. The most common way to create a theme is to usedesktop software like Dreamweaver or a text editor to create the relevant markup, styles and scripts, and test the theme locally in a web browser.
The content that is being themed. In this case, that is the output from Plone.
A rules file, which defines how the placeholders in the theme (i.e. the HTML mockup) should be replaced by relevant markup in the content.
The rules file uses XML syntax (similar to HTML). Here is a very simple example:
<?xml version="1.0" encoding="UTF-8"?> <rules xmlns="http://namespaces.plone.org/diazo" xmlns:css="http://namespaces.plone.org/diazo/css" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">` <theme href="theme.html" /> <replace css:content-children="#content" css:theme-children="#main" /> </rules>
Here, we are replacing the contents (child nodes) of placeholder element with HTML id
main
in the theme file (theme.html
, found in the same directory as therules.xml
file, as referneced by the<theme />
rule) with the contents (children) of the element with the HTML idcontent
in the markup generated by Plone.When this theme is applied, the result will look very much like the static HTML file
theme.html
(and its referenced CSS, JavaScript and image files), except the placeholder that is identified by the node in the theme with idmain
will be filled by Plone's main content area.
Plone ships with an example theme called, appropriately, Example theme, which
uses the venerable Twitter Bootstrap
to build a simple yet functional theme exposing most of Plone's core
functionality. You are advised to study it - in particular the rules.xml
file - to learn more about how Diazo themes work.
TODO
TODO: Describe how to enable a theme in the control panel
TODO: Describe actions: cloning and creating anew
TODO: Describe uploading a theme - with or without rules.xml file to start with
TODO: Describe purpose and UI
TODO: Describe purpose and UI - filesystem and ZODB themes
TODO: Describe the available settings at a high level
The remainder of this guide contains reference materials useful for theme builders.
To build and test a theme, you must first create a static HTML mockup of the look and feel you want, and then build a rules file to describe how Plone's content mapes to the placeholders in this mockup.
The mockup can be created anywhere using whatever tool you feel most comfortable building web pages in. To simplify integration with Plone, you are recommended to make sure it uses relative links for resources like CSS, JavaScript and image files, so that it can be opened in a web browser from a local file. Plone will convert these relative links to the appropriate absolute paths automatically, ensuring the theme works no matter which URL the user is viewing.
There are several ways to get the theme into Plone:
- On the filesystem
If you used an installer or a standard "buildout" to set up your Plone site,
you should have a directory called resources
in the root of your Plone
installation (this is created using the resources
option to the buildout
recipe plone.recipe.zope2instance
. See
http://pypi.python.org/pypi/plone.recipe.zope2instance for more details.)
You can find (or create) a theme
directory inside this directory, which is
used to contain themes. Each theme needs its own directory with a unique name.
Create one (e.g. resources/theme/mytheme
) and put your HTML files and any
references resources inside this directory. You can use subdirectories if you
wish, but you are recommended to keep the basic theme HTML files at the top
of the theme directory.
You will also need a rules file called rules.xml
. If you haven't got one
yet, start with an empty one:
<?xml version="1.0" encoding="UTF-8"?> <rules xmlns="http://namespaces.plone.org/diazo" xmlns:css="http://namespaces.plone.org/diazo/css" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">` <theme href="theme.html" /> <replace css:content-children="#content" css:theme-children="#main" /> </rules>
Provided you are running Zope in debug mode (e.g. you start it up with
bin/instance fg
), changes to the theme and rules should take effect
immediately. You can preview or enable the theme through the Themes control
panel, and then iteratively modify the rules.xml
file or the theme mockup
as you wish.
- Through the web
If you prefer (or do not have filesystem access), you can create themes entirely through the Plone control panel, either by duplicating an existing theme, or starting from scratch with a near-empty theme.
TODO: Describe specific UI to do this
Once a theme has been created, you can modify it through the in-Plone theme file manager or rule builder. See below for more details.
- As a zip file
Themes can be downloaded from Plone as Zip files, which can then be uploaded into other sites.
TODO: Describe specific UI to do this
In fact, you can create valid theme zip archives by compressing a theme
directory on the filesystem using a standard compression tool such as 7-Zip or
Winzip (for Windows) or the built-in Compress action in the Mac OS X Finder.
Just make sure you compress exactly one folder that contains all the theme files
and the rules.xml
file. (Do not compress the contents of the folder
directly: when unpacked, the zip file should produce exactly one folder which
in turn contains all the relevant files).
- In a Python package (programmers only)
If you are creating a Python package containing Plone customisations that you intend to install into your site, you can let it register a theme for installation into the site.
To do this, place a directory called e.g. theme
at the top of the package,
next to the Zope configure.zcml
file, and add a <plone:static />
declaration to the configure.zcml
file:
<configure xmlns:plone="http://namespaces.plone.org/plone" xmlns="http://namespaces.zope.org/zope"> ... <plone:static name="mytheme" directory="theme" type="theme" /> ... </configure>
Notice the declaration of the plone
namespace at the top element. Place the
theme files and the rules.xml
file into the theme
directory.
If your package has a GenericSetup profile, you can automatically enable the
theme upon installation of this profile by adding a theme.xml
file in the
profiles/default
directory, containing e.g.:
<theme> <name>mytheme</name> <enabled>true</enabled> </theme>
It is possible to give additional information about a theme by placing a file
called manifest.cfg
next to the rules.xml
file at the top of a theme
directory.
This file may look like this:
[theme] title = My theme description = A test theme
As shown here, the manifest file can be used to provide a more user friendly
title and a longer description for the theme, for use in the control panel.
Only the [theme]
header is required - all other keys are optional.
You can also set:
rules = myrules.xml
to use a different rule file name than rules.xml
, and:
prefix = /some/prefix
to change the absolute path prefix (see Advanced settings).
Extensions to the Diazo theming engine can add support for additional blocks of configurable parameters.
The following is a short summary of the Diazo rules syntax. See http://diazo.org for more details and further examples.
Each rule is represented by an XML tag that operates on one or more HTML elements in the content and/or theme. The elements to operate on are indicated using attributes of the rules known as selectors.
The easiest way to select elements is to use a CSS expression selector, such as
css:content="#content"
or css:theme="#main .content"
. Any valid CSS 3
expression (including pseudo-selectors like :first-child
may be used.
The standard selectors operate on the element(s) that are matched. If you want
to operate on the children of the matched element instead, use css:theme-
children="..."
or css:content-children="..."
instead.
If you cannot construct a suitable CSS 3 expression, you can use XPath
expressions such as. content="/head/link"
or theme="//div[@id='main']"
(note the lack of a css:
prefix when using XPath expressions). The two
approaches are equivalent, and you can mix and match freely, but you cannot
have e.g. both a css:theme
and a theme
attribute on a single rule.
By default, every rule is executed, though rules that do not match any elements
in the theme are ignored. You can make a rule, set of rules or theme reference
(see below) conditional upon an element appearing in the content by adding an
attribute to the rule like css:if-content="#some-element"
(to use an XPath
expression instead, drop the css:
prefix). If no elements match the
expression, the rule is ignored.
Tip: if a<replace />
rule matches an element in the theme but not in the content, the theme node will be dropped (replaced with nothing). If you do not want this behavior and you are unsure if the content will contain the relevant element(s), you can usecss:if-content
conditional rule. Since this is a common scenario, there is a shortcut:css:if-content=""
means "use the expression from thecss:content
attribute".
Similarly, you can construct a condition based on the path of the current
request by using an attribute like if-path="/news"
(note that there is no
css:if-path
). If the path starts with a slash, it will match from the root
of the Plone site. If it ends with a slash, it will match to the end of the URL.
You can set an absolute path by using a leading and a trailing slash.
Finally, you can use arbitrary XPath expressions against any defined variable
using an attribute like if="$host = 'localhost'"
. By default, the variables
url
, scheme
, host
and base
are available, representing the
current URL. Themes may define additional variables in their manifests.
The various rule types are summarized below.
<rules> ... </rules>
Wraps a set of rules. Must be used as the root element of the rules file. Nested
<rules />
can be used with a condition to apply a
single condition to a set of rules.
When used as the root element of the rules file, the various XML namespaces must be declared:
<rules xmlns="http://namespaces.plone.org/diazo" xmlns:css="http://namespaces.plone.org/diazo/css" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> ... </rules>
<theme href="theme.html" /> <theme href="news.html" if-path="/news" /> <notheme if="$host = 'admin.example.org'" />
Choose the theme file to be used. The href
is a path relative to the rules
file. If multiple <theme />
elements are present, at most one may be given
without a condition. The first theme with a condition that is true will be used,
with the unconditional theme, if any, used as a fallback.
<notheme />
can be used to specify a condition under which no theme<should be used.notheme />
takes precedence overtheme />`` .Tip: To ensure you do not accidentally style non-Plone pages, add a condition likecss:if-condition="#visual-portal-wrapper"
to the last theme listed, and do not have any unconditional themes.
<replace css:content="#content" css:theme="#main" />
Replaces the matched element(s) in the theme with the matched element(s) from the content.
<before css:content-children="#portal-column-one" css:theme-children="#portlets" /> <after css:content-children="#portal-column-two" css:theme-children="#portlets" />
Inserts the matched element(s) from the content before or after the matched
element(s) in the theme. By using theme-children
, you can insert the
matched content element(s) as the first (prepend) or last (append) element(s)
inside the matched theme element(s).
<drop css:content=".documentByLine" /> <drop theme="/head/link" /> <drop css:theme="#content *" attributes="onclick onmouseup" /> <strip css:content="#parent-fieldname-text" />
Remove element(s) from the theme or content. Note that unlike most other rules,
a <drop />
or <strip />
rule can operate on the theme
or
content
, but not both. <drop />
removes the matched element(s) and
any children, whereas <strip />
removes the matched element(s), but leaves
any children in place.
<drop />
may be given a whitespace-separated list of attributes
to
drop. In this case, the matched element(s) themselves will not be removed. Use
attributes="*"
to drop all attributes.
<merge attributes="class" css:content="body" css:theme="body" /> <copy attributes="class" css:content="#content" css:theme="#main" />
These rules operate on attributes. <merge />
will add the contents of the
named attribute(s) in the theme to the value(s) of any existing attributes with
the same name(s) in the content, separated by whitespace. It is mainly used to
merge CSS classes.
<copy />
will copy attributes from the matched element(s) in the content
<to the matched element(s) in the theme, fully replacing any attributes with
<the same name that may already be in the theme.
The attributes
attribute can contain a whitespace-separated list of
attributes, or the special value *
to operate on all attributes of the
matched theme element.
Instead of selecting markup to insert into the theme from the content, you can place markup directly into the rules file, as child nodes of the relevant rule element:
<after css:theme="head"> <style type="text/css"> body > h1 { color: red; } </style> </after>
This also works on the content, allowing you to modify it on the fly before any rules are applied:
<replace css:content="#portal-searchbox input.searchButton"> <button type="submit"> <img src="images/search.png" alt="Search" /> </button> </replace>
In addition to including static HTML in this manner, you can use XSLT
instructions that operate on the content. You can even use css:
selectors
directly in the XSLT.:
<replace css:theme="#details"> <dl id="details"> <xsl:for-each css:select="table#details > tr"> <dt><xsl:copy-of select="td[1]/text()"/></dt> <dd><xsl:copy-of select="td[2]/node()"/></dd> </xsl:for-each> </dl> </replace>
Rules may operate on content that is fetched from somewhere other than the
current page being rendered by Plone, by using the href
attribute to specify
a path of a resource relative to the root of the Plone site:
<after css:theme-children="#leftnav" css:content=".navitem" href="/@@extra-nav" />
It is possible to pass arbitrary parameters to your theme, which can be referenced as variables in XPath expressions.
For example, you could have a parameter mode
that could be set to the
string live
or test
. In your rules, you could do something like this
to insert a warning when you are on the test server:
<before css:theme-children="body" if="$mode = 'test'"> <span class="warning">Warning: This is the test server</span> </before>
You could even use the parameter value directly, e.g.:
<before css:theme-children="body"> <span class="info">This is the <xsl:value-of select="$mode" /> server</span> </before>
See the Diazo documentation for more details about rules
that support if
parameters and inline HTML and XSL.
The following parameters are always available when using
plone.app.theming
:
scheme
- The scheme portion of the inbound URL, usually
http
orhttps
. host
- The hostname in the inbound URL.
path
- The path segment of the inbound URL. This will not include any virtual hosting tokens, i.e. it is the path the end user sees.
base
- The Zope base url (the
BASE1
request variable).
You can add additional parameters through the control panel, using TALES
expressions. Parameters are listed on the Advanced tab, one per line, in
the form <name> = <expression>
.
For example, if you want to avoid theming any pages that are loaded by Plone's
overlays, you can make use of the ajax_load
request parameter that they
set. Your rules file might include:
<notheme if="$ajax_load" />
To add this parameter as well as the mode
parameter outlined earlier, you
could add the following in the control panel:
ajax_load = python: 'ajax_load' in request.form mode = string: test
The right hand side is a TALES expression. It must evaluate to a string,
integer, float, boolean or None
: lists, dicts and objects are not
supported. python:
, string:
and path expressions work as they do
in page templates.
The following variables are available:
context
- The context of the current request, usually a content object.
request
- The current request.
portal
- The portal root object.
context_state
- The
@@plone_context_state
view, from which you can look up additional values such as the context's URL or default view. portal_state
- The
@@plone_portal_state
view, form which you can look up additional values such as the navigation root URL or whether or not the current user is logged in.
See plone.app.layout
for details about the @@plone_context_state
and
@@plone_portal_state
views.
Theme parameters are really integral to a theme, and will therefore be set
based on a theme's manifest when a theme is imported from a Zip file or
enabled from a resource directory. This is done using the
[theme:parameters]
section in the manifest file. For example:
[theme] title = My theme description = A test theme [theme:parameters] ajax_load = python: 'ajax_load' in request.form mode = string: test
Note that when Zope is in development mode (e.g. running in the foreground
in a console with bin/instance fg
), the theme will be re-compiled on each
request. In non-development mode, it is compiled once when first accessed, and
then only re-compiled the control panel values are changed.
Also, in development mode, it is possible to temporarily disable the theme
by appending a query string parameter diazo.off=1
. For example:
http://localhost:8080/Plone/some-page?diazo.off=1
The parameter is ignored in non-development mode.
The following recipes illustrate rules commonly used in building Plone themes:
To copy the page title:
<replace css:theme="title" css:content="title" />
To copy the <base />
tag (necessary for Plone's links to work):
<replace css:theme="base" css:content="base" />
If there is no <base />
tag in the theme, you can do:
<before css:theme-children="head" css:content="base" />
To drop all styles and JavaScript resources from the theme and copy them
from Plone's portal_css
tool instead:
<!-- Drop styles in the head - these are added back by including them from Plone --> <drop theme="/html/head/link" /> <drop theme="/html/head/style" /> <!-- Pull in Plone CSS --> <after theme-children="/html/head" content="/html/head/link | /html/head/style" />
To copy Plone's JavaScript resources:
<!-- Pull in Plone CSS --> <after theme-children="/html/head" content="/html/head/script" />
To copy the class of the <body />
tag (necessary for certain Plone
JavaScript functions and styles to work properly):
<!-- Body --> <merge attributes="class" css:theme="body" css:content="body" />