Building Blocks

« Return to page index

Plone Theme Reference

1. Overview

An overview of the building blocks and how they fit together to create a theme.

There are really three main building blocks in a theme. The diagram below shows you how these slot together:

diagram of the building blocks used to create a theme

Skin

  • deals with the overall construction of a page and the delivery of content
  • comprises page templates, macros, and Python scripts, and is also the place to put style sheets and JavaScript
  • to help you understand these we'll point you in the direction of tutorials on the templating language TAL and introduce you to skin layers and order of precedence
  • to find skin elements, look in
    • portal_skins in the Zope Management Interface
    • the skins directory in a file system product

Components

  • the Components part deals (mostly) with page furniture - the page elements which have some level of consistency from page to page along with page elements involving a level of processing - such as the navigation tree, RSS feeds
  • deploys a mixture of Python classes and page templates to create viewlets, portlets and browser views
  • to help you understand these, we'll give you an overview of how they are wired together with ZCML, and we'll give you the briefest of introductions to the bits you really need to know about Python classes
  • to find the pieces that go together to make a component, look in
    • portal_view_customizations in the Zope Management Interface
    • the browser directory in a file system product

Configuration

  • the Configuration part deals with setting the order of some page elements (or individual items) on the page and with automatically setting some of the configuration you would otherwise make manually through the Site Setup interface
  • to help you understand the configuration, we'll point you in the direction of the main tools for manual configuration, give you a quick overview of the Generic Setup Tool and the XML used for automatic configuration
  • configuration tools are found in several places in the site, but the files required to run a configuration automatically are found in the profiles directory of a file system product

2. Skin

Templates, style sheets, Javascript files, how to customize them, where to find them.

2.1. Templates and the Templating Language

Templates and the Templating Language

2.1.1. Templates and the Templating Language

The main elements of a skin are page templates, images, Python scripts, CSS files, and JavaScript files.

(Zope) Page Templates

Page templates (.pt files or ZPT) are an essential part of a Plone theme and are probably the easiest aspect of Plone to get to grips with. They are written in an elegant XML-based templating language called TAL, sometimes make use of macros (METAL), and sometimes incorporate Python expressions (small one-line calculations) or Python scripts.

There are several excellent introductions to ZPT, and it doesn't take long to learn TAL. Try these:

TAL is the one language that we really recommend you learn properly. The rest you can pick your way through or familiarise yourself with as you go along.

A Plone web page is delivered via an aggregation of templates, rather than just one, and there a couple of aspects of Zope Page Template that you'll need to be aware of.

1. Slot

A slot is a predefined section of a template. This can be left empty, or given some default content, but it is available to be filled on the fly. A slot is defined in a template in code like this:

<metal:bodytext metal:define-slot="main" tal:content="nothing">
    .....
</metal:bodytext>

And filled via another template like this:

<metal:main fill-slot="main">
 <h1 class="documentFirstHeading">
    ......
 </h1>
</metal:main>

The ZPT tutorial on plone.org talks you through this in more detail, and the Templates and Components to Page section of this manual gives you an example.

2. Content view templates (_view)

Note: the term "view" also has a more technical application, so in the context of Components (discussed later in this manual) it will mean something different.

From the user's, contributor's, or visitor's perspective, a view is the way in which a content item is presented on the page. There's a useful introduction to this in the Plone User Manual.

Templates that are used to render a content item for a view have _view appended to their name (e.g., document_view.pt) and may have a title such as "Standard View." These templates are, in fact, sets of information ready to drop into slots.

Scripts

These are small stand-alone functions for times when you need a few lines of code to perform your calculation. On the file system, they have a .py extension; you'll find them in the Zope Management Interface as Script (Python).

Here's a snippet from the event_view template (the content view for the event content type) which uses a Python script to format the a time field according to the default format for the site. If you look in CMFPlone/skins/plone_scripts, you'll find toLocalizedTime.py - just a few lines of code.

<span metal:define-slot="inside" 
            class="explain"
            tal:attributes="title python:here.end()"
            tal:content="python:here.toLocalizedTime(here.end(),
                         long_format=1)">End Date Time</span>

 

2.1.2. Getting started

Page Templates are a web page generation tool. In this part, we'll go through their basics and show how to use them in your web site to create dynamic web pages easily.

The goal of Page Templates is natural workflow. A designer will use a WYSIWYG HTML editor to create a template, then a programmer will edit it to make it part of an application. If required, the designer can load the template back into his editor and make further changes to its structure and appearance. By taking reasonable steps to preserve the changes made by the programmer, he will not disrupt the application.

Page Templates aim at this goal by adopting three principles:

  1. Play nicely with editing tools.

  2. What you see is very similar to what you get.

  3. Keep code out of templates, except for structural logic.

A Page Template is like a model of the pages that it will generate. In particular, it is a valid HTML/XHTML page. Since HTML is highly structured, and WYSIWYG editors carefully preserve this structure, there are strict limits on the ways in which the programmer can change a page and still respect the first principle.

Although Page Templates are suited for programmers and designers who need to work together to create dynamic web pages, they form the basis for most of Plone's pages, so you should learn them a bit at least, if you need to customize the Plone look or layout. Moreover, they can be simpler to use and understand than the alternative, DTML.

Why Yet Another Template Language?

There are plenty of template systems out there, some of them quite popular, such as ASP, JSP, and PHP. Since the beginning, Zope has come with a template language called DTML. Why invent another?

First, none of these template systems are aimed at HTML designers. Once a page has been converted into a template, it is invalid HTML, making it difficult to work with outside of the application. Each of them violates the first or second principle of Zope Page Templates to one degree or another. Programmers should not "hijack" the work of the designers and turn HTML into software. XMLC, part of the Enhydra project, shares our goal, but requires the programmer to write substantial amounts of Java support code for each template.

Second, all of these systems suffer from failure to separate presentation, logic, and content (data). Their violations of the third principle decrease the scalability of content management and website development efforts that use these systems.

Applying The Principles

Page Templates use the Template Attribute Language (TAL). TAL consists of special tag attributes. For example, a dynamic page title might look like this:

      <title tal:content="context/title">Page Title</title>

The tal:content attribute is a TAL statement. Since it has an XML namespace (the tal: part) most editing tools will not complain that they don't understand it, and will not remove it. It will not change the structure or appearance of the template when loaded into a WYSIWYG editor or a web browser. The name content indicates that it will set the content of the title tag, and the value "context/title" is an expression providing the text to insert into the tag.

To the HTML designer using a WYSIWYG tool, this is perfectly valid HTML, and shows up in the editor looking the way a title should look. The designer, not caring about the application details of TAL, only sees a mockup of the dynamic template, complete with dummy values like "Page Title" for the title of the document.

When this template is saved in Zope and viewed by a user, Zope turns this static content into dynamic content and replaces "Page Title" with whatever "context/title" resolves to. In this case, "context/title" resolves to the title of the object to which to the template is applied. This substitution is done dynamically, when the template is viewed.

This example also demonstrates the second principle. When you view the template in an editor, the title text will act as a placeholder for the dynamic title text. The template provides an example of how generated documents will look.

There are template commands for replacing entire tags, their contents, or just some of their attributes. You can repeat a tag several times or omit it entirely. You can join parts of several templates together, and specify simple error handling. All of these capabilities are used to generate document structures. You can't create subroutines or classes, write loops or multi-way tests, or easily express complex algorithms. For these tasks, you should use Python.

The template language is deliberately not as powerful and general-purpose as it could be. It is meant to be used inside of a framework (such as Zope) in which other objects handle business logic and tasks unrelated to page layout.

For instance, template language would be useful for rendering an invoice page, generating one row for each line item, and inserting the description, quantity, price, and so on into the text for each row. It would not be used to create the invoice record in a database or to interact with a credit card processing facility.

Creating a Page Template

If you design pages, you will probably use FTP or WebDAV instead of the Zope Management Interface (ZMI) to create and edit Page Templates, or you will be developing templates on the filesystem for later installation. If you're not the Zope site owner, ask your Zope administrator for instructions. For the very small examples in this article, it is much easier to use the ZMI. For more information on using FTP or WebDAV with Zope, see The Zope Book or Jeffrey Shell's WebDAV article.

You may also use Emacs, cadaver, or some other client, but if you are a Zope administrator or a programmer, you will probably use the ZMI anyway at least occasionally. See the Zope Book for instructions on setting up Zope to to work with various clients.

Use your web browser to log into the Zope management interface as you normally would with Zope. Choose a Folder (the root is fine) and pick "Page Template" from the drop-down add list. Type "simple_page" in the add form's Id field, then push the "Add and Edit" button.

You should now see the main editing page for the new Page Template. The title is blank, the content-type is text/html, and the default template text is in the editing area.

Now you will create a very simple dynamic page. Type the words "a Simple Page" in the Title field. Then, edit the template's body text to look like this:

      This is <b tal:replace="template/title">the Title</b>.

Now push the "Save Changes" button. The edit page should show a message confirming that your changes have been saved. If  an error message appears above the code area, or some text starting with <-- Page Template Diagnostics is added to the template, then check to make sure you typed the example correctly and save it again. You don't need to erase the error comment: once the error is corrected it will go away.

Click on the Test tab. You should see a mostly blank page with "This is a Simple Page." at the top.

Back up, then click on the "Browse HTML source" link under the content-type field. This will show you the unrendered source of the template. You should see "This is the Title." Back up again, so that you are ready to edit the example further.

Simple Expressions

The text "template/title" in your simple Page Template is a path expression. This the most commonly used of the expression types defined by the TAL Expression Syntax (TALES). It fetches the title property of the template. Here are some other common path expressions:

  • request/URL: The URL of the current web request.

  • user/getUserName: The authenticated user's login name.

  • container/objectIds: A list of Ids of the objects in the same Folder as the template.

Every path starts with a variable name. If the variable contains the value you want, you stop there. Otherwise, you add a slash (/) and the name of a sub-object or property. You may need to work your way through several sub-objects to get to the value you're looking for.

There is a small built in set of variables, such as request and user, that will be listed and described later. You will also learn how to define your own variables.

Inserting Text

In your "simple_page" template, you used the tal:replace statement on a bold tag. When you tested it, it replaced the entire tag with the title of the template. When you browsed the source, instead, you saw the template text in bold. We used a bold tag in order to highlight the difference.

In order to place dynamic text inside of other text, you typically use tal:replace on a span tag. Add the following lines to your example:

      <br>
      The URL is <span tal:replace="request/URL">URL</span>.

The span tag is structural, not visual, so this looks like "The URL is URL." when you view the source in an editor or browser. When you view the rendered version, it may look something like:

      <br>
      The URL is http://localhost:8080/simple_page.

Remember to take care when editing not to destroy the span or place formatting tags such as b or font inside of it, since they would also be replaced.

If you want to insert text into a tag but leave the tag itself alone, you use tal:content. To set the title of your example page to the template's title property, add the following lines above the other text:

      <head>
        <title tal:content="template/title">The Title</title>
      </head>

If you open the "Test" tab in a new window, the window's title will be "a Simple Page".

Repeating Structures

Now you will add some context to your page, in the form of a list of the objects that are in the same Folder. You will make a table that has a numbered row for each object, and columns for the id, meta-type, and title. Add these lines to the bottom of your example template:

      <table border="1" width="100%">
        <tr>
          <th>#</th><th>Id</th><th>Meta-Type</th><th>Title</th>
        </tr>
        <tr tal:repeat="item container/objectValues">
          <td tal:content="repeat/item/number">#</td>
          <td tal:content="item/id">Id</td>
          <td tal:content="item/meta_type">Meta-Type</td>
          <td tal:content="item/title">Title</td>
        </tr>
      </table>

The tal:repeat statement on the table row means "repeat this row for each item in my container's list of object values". The repeat statement puts the objects from the list into the item variable one at a time, and makes a copy of the row using that variable. The value of "item/id" in each row is the Id of the object for that row.

You can use any name you like for the "item" variable, as long as it starts with a letter and contains only letters, numbers, and underscores (_). It only exists in the <tr> tag; If you tried to use it above or below that tag you would get an error.

You also use the tal:repeat variable name to get information about the current repetition. By placing it after the builtin variable repeat in a path, you can access the repetition count starting from zero (index), from one (number), from "A" (Letter), and in several other ways. So, the expression repeat/item/number is 1 in the first row, 2 in the second row, and so on.

Since one tal:repeat loop can be placed inside of another, more than one can be active at the same time. This is why you must write repeat/item/number instead of just repeat/number. You must specify which loop you are interested in by including the loop name.

Conditional Elements

View the template, and you'll notice that the table is very dull looking. Let's improve it by shading alternate rows. Copy the second row of the table, then edit the code so that it looks like this:

      <table border="1" width="100%">
        <tr>
          <th>#</th><th>Id</th><th>Meta-Type</th><th>Title</th>
        </tr>
        <tbody tal:repeat="item container/objectValues">
          <tr bgcolor="#EEEEEE" tal:condition="repeat/item/even">
            <td tal:content="repeat/item/number">#</td>
            <td tal:content="item/id">Id</td>
            <td tal:content="item/meta_type">Meta-Type</td>
            <td tal:content="item/title">Title</td>
          </tr>
          <tr tal:condition="repeat/item/odd">
            <td tal:content="repeat/item/number">#</td>
            <td tal:content="item/id">Id</td>
            <td tal:content="item/meta_type">Meta-Type</td>
            <td tal:content="item/title">Title</td>
          </tr>
        </tbody>
      </table>

The tal:repeat has not changed, you have just moved it onto the new tbody tag. This is a standard HTML tag meant to group together the body rows of a table, which is how you are using it. There are two rows in the body, with identical columns, and one has a grey background.

View the template's source, and you see both rows. If you had not added the tal:condition statements to the rows, then the template would generate both rows for every item, which is not what you want. The tal:condition statement on the first row ensures that it is only included on even-indexed repetitions, while the second row's condition only lets it appear in odd-indexed repetitions.

A tal:condition statement does nothing if its expression has a true value, but removes the entire statement tag, including its contents, if the value is false. The odd and even properties of repeat/item are either zero or one. The number zero, a blank string, an empty list, and the builtin variable nothing are all false values. Nearly every other value is true, including non-zero numbers, and strings with anything in them (even spaces!).

Defining Variables

Note: In Plone 4 or newer, use container/values instead of container/objectValues below.

Your template will always show at least one row, since the template itself is one of the objects listed. In other circumstances, you might want to account for the possibility that the table will be empty. Suppose you want to simply omit the entire table in this case. You can do this by adding a tal:condition to the table:

      <table border="1" width="100%"
             tal:condition="container/objectValues">

Now, when there are no objects, no part of the table will be included in the output. When there are objects, though, the expression "container/objectValues" will be evaluated twice, which is mildly inefficient. Also, if you wanted to change the expression, you would have to change it in both places.

To avoid these problems, you can define a variable to hold the list, and then use it in both the tal:condition and the tal:repeat. Change the first few lines of the table to look like this:

      <table border="1" width="100%"
             tal:define="items container/objectValues"
             tal:condition="items">
        <tr>
          <th>#</th><th>Id</th><th>Meta-Type</th><th>Title</th>
        </tr>
        <tbody tal:repeat="item items">

The tal:define statement creates the variable items, and you can use it anywhere in the table tag. Notice also how you can have two TAL attributes on the same table tag. You can, in fact, have as many as you want. In this case, they are evaluated in order. The first assigns the variable items and the second uses items in a condition to see whether or not it is false (in this case, an empty sequence) or true.

Now, suppose that instead of simply leaving the table out when there are no items, you want to show a message. To do this, you place the following above the table:

      <h4 tal:condition="not:container/objectValues">There
      Are No Items</h4>

You can't use your items variable here, because it isn't defined yet. If you move the definition to the h4 tag, you can't use it in the table tag any more, because it becomes a local variable of the h4 tag. You could place the definition on some tag that enclosed both the h4 and the table, but there is a simpler solution. By placing the keyword global in front of the variable name, you can make the definition last from the h4 tag to the bottom of the template:

      <h4 tal:define="global items container/objectValues"
          tal:condition="not:items">There Are No Items</h4>
      <table border="1" width="100%"
          tal:condition="items">

The not: in the first tal:condition is an expression type prefix that can be placed in front of any expression. If the expression is true, not: is false, and vice versa.

Changing Attributes

Most, if not all, of the objects listed by your template have an icon property, that contains the path to the icon for that kind of object. In order to show this icon in the meta-type column, you will need to insert this path into the src attribute of an img tag, by editing the meta-type column in both rows to look like this:

      <td>
          <img src="/misc_/OFSP/Folder_icon.gif"
               tal:attributes="src item/icon">
          <span tal:replace="item/meta_type">Meta-Type</span>
      </td>

The tal:attributes statement replaces the src attribute of the image with the value of item/icon. The value of src in the template acts as a placeholder, so that the image is not broken, and is the correct size.

Since the tal:content attribute on the table cell would have replaced the entire contents of the cell, including the image, with the meta-type text, it had to be removed. Instead, you insert the meta-type inline in the same fashion as the URL at the top of the page.

Based on the Zope Book, © Zope Corporation

2.1.3. Macros and Slots

Macros

So far, you've seen how Page Templates can be used to add dynamic behavior to individual web pages. Another feature of page templates is the ability to reuse look and feel elements across many pages.

For example, with Page Templates, you can have a site that has a standard look and feel. No matter what the "content" of a page, it will have a standard header, side-bar, footer, and/or other page elements. This is a very common requirement for web sites, and this is exactly how Plone works.

You can reuse presentation elements across pages with macros. Macros define a section of a page that can be reused in other pages. A macro can be an entire page, or just a chunk of a page such as a header or footer. After you define one or more macros in one Page Template, you can use them in other Page Templates.

Using Macros

You can define macros with tag attributes similar to TAL statements. Macro tag attributes are called Macro Expansion Tag Attribute Language (METAL) statements. Here's an example macro definition:

<p metal:define-macro="copyright">
  Copyright 2008, <em>Foo, Bar, and Associates</em> Inc.
</p>

This metal:define-macro statement defines a macro named "copyright". The macro consists of the p element (including all contained elements, ending with the closing p tag).

Macros defined in a Page Template are stored in the template's macros attribute. You can use macros from other Page Templates by referring to them through the macros attribute of the Page Template in which they are defined. For example, suppose the copyright macro is in a Page Template called "master_page". Here's how to use the copyright macro from another Page Template:

<hr />
<b metal:use-macro="container/master_page/macros/copyright">
  Macro goes here
</b>

In this Page Template, the b element will be completely replaced by the macro when Zope renders the page:

<hr />
<p>
  Copyright 2008, <em>Foo, Bar, and Associates</em> Inc.
</p>

If you change the macro (for example, if the copyright holder changes) then all Page Templates that use the macro will automatically reflect the change.

Notice how the macro is identified by a path expression using the metal:use-macro statement. The metal:use-macro statement replaces the statement element with the named macro.

Macro Details

The metal:define-macro and metal:use-macro statements are pretty simple. However there are a few subtleties to using them which are worth mentioning.

A macro's name must be unique within the Page Template in which it is defined. You can define more than one macro in a template, but they all need to have different names.

It should also be noted that, despite the define-macro attribute, the macro is anyway a regular section of the template; so, when you call the whole template, the macro section is rendered in the output page just like any other section in the template. By using the define-macro attribute you are simply adding some sort of "anchor" to that section, so that you can call it from outside; but you are not changing anything regarding the behaviour of that same section in the template itself.

Normally you'll refer to a macro in a metal:use-macro statement with a path expression. However, you can use any expression type you wish so long as it returns a macro. For example:

<p metal:use-macro="python:context.getMacro()">
  Replaced with a dynamically determined macro,
  which is located by the getMacro script.
</p>

In this case the path expression returns a macro defined dynamically by the getMacro script. Using Python expressions to locate macros lets you dynamically vary which macro your template uses.

You can use the default variable with the metal:use-macro statement:

<p metal:use-macro="default">
  This content remains - no macro is used
</p>

The result is the same as using default with tal:content and tal:replace. The "default" content in the tag doesn't change when it is rendered. This can be handy if you need to conditionally use a macro or fall back on the default content if it doesn't exist.

If you try to use the nothing variable with metal:use-macro you will get an error, since nothing is not a macro. If you want to use nothing to conditionally include a macro, you should instead enclose the metal:use-macro statement with a tal:condition statement.

Zope handles macros first when rendering your templates. Then Zope evaluates TAL expressions. For example, consider this macro:

<p metal:define-macro="title"
   tal:content="template/title">
  template's title
</p>

When you use this macro it will insert the title of the template in which the macro is used, not the title of the template in which the macro is defined. In other words, when you use a macro, it's like copying the text of a macro into your template and then rendering your template.

Using Slots

Macros are much more useful if you can override parts of them when you use them. You can do this by defining slots in the macro that you can fill in when you use the template. For example, consider a side bar macro:

<div metal:define-macro="sidebar">
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
  </ul>
</div>

This macro is fine, but suppose you'd like to include some additional information in the sidebar on some pages. One way to accomplish this is with slots:

<div metal:define-macro="sidebar">
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
  </ul>
  <span metal:define-slot="additional_info"></span>
</div>

When you use this macro you can choose to fill the slot like so:

<p metal:use-macro="container/master_page/macros/sidebar">
  <b metal:fill-slot="additional_info">
    Make sure to check out our <a href="/specials">specials</a>.
  </b>
</p>

When you render this template the side bar will include the extra information that you provided in the slot:

<div>
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
  </ul>
  <b>
    Make sure to check out our <a href="/specials">specials</a>.
  </b>
</div>

Notice how the span element that defines the slot is replaced with the b element that fills the slot.

Customizing Default Presentation

A common use of slot is to provide default presentation which you can customize. In the slot example in the last section, the slot definition was just an empty span element. However, you can provide default presentation in a slot definition. For example, consider this revised sidebar macro:

<div metal:define-macro="sidebar">
  <div metal:define-slot="links">
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
  </ul>
  </div>
  <span metal:define-slot="additional_info"></span>
</div>

Now the sidebar is fully customizable. You can fill the links slot to redefine the sidebar links. However, if you choose not to fill the links slot then you'll get the default links, which appear inside the slot definition.

You can even take this technique further by defining slots inside of slots. This allows you to override default presentation with a fine degree of precision. Here's a sidebar macro that defines slots within slots:

<div metal:define-macro="sidebar">
  <div metal:define-slot="links">
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
    <span metal:define-slot="additional_links"></span>
  </ul>
  </div>
  <span metal:define-slot="additional_info"></span>
</div>

If you wish to customize the sidebar links you can either fill the links slot to completely override the links, or you can fill the additional_links slot to insert some extra links after the default links. You can nest slots as deeply as you wish.

Combining METAL and TAL

You can use both METAL and TAL statements on the same elements. For example:

<ul metal:define-macro="links"
    tal:repeat="link context/getLinks">
  <li>
    <a href="link_url"
       tal:attributes="href link/url"
       tal:content="link/name">link name</a>
  </li>
</ul>

In this case, getLinks is a (imaginary) Script that assembles a list of link objects, possibly using a Catalog query.

Since METAL statements are evaluated before TAL statements, there are no conflicts. This example is also interesting since it customizes a macro without using slots. The macro calls the getLinks Script to determine the links. You can thus customize your site's links by redefining the getLinks Script at different locations within your site.

It's not always easy to figure out the best way to customize look and feel in different parts of your site. In general you should use slots to override presentation elements, and you should use Scripts to provide content dynamically. In the case of the links example, it's arguable whether links are content or presentation. Scripts probably provide a more flexible solution, especially if your site includes link content objects.

Whole Page Macros

Rather than using macros for chunks of presentation shared between pages, you can use macros to define entire pages. Slots make this possible. Here's an example macro that defines an entire page:

<html metal:define-macro="page">
  <head>
    <title tal:content="context/title">The title</title>
  </head>

  <body>
    <h1 metal:define-slot="headline"
        tal:content="context/title">title</h1>

    <p metal:define-slot="body">
      This is the body.
    </p>

    <span metal:define-slot="footer">
      <p>Copyright 2008 Fluffy Enterprises</p>
    </span>

  </body>
</html>

This macro defines a page with three slots: headline, body, and footer. Notice how the headline slot includes a TAL statement to dynamically determine the headline content.

You can then use this macro in templates for different types of content, or different parts of your site. For example here's how a template for news items might use this macro:

<html metal:use-macro="container/master_page/macros/page">

  <h1 metal:fill-slot="headline">
    Press Release:
    <span tal:replace="context/getHeadline">Headline</span>
  </h1>

  <p metal:fill-slot="body"
     tal:content="context/getBody">
    News item body goes here
  </p>

</html>

This template redefines the headline slot to include the words "Press Release" and call the getHeadline method on the current object. It also redefines the body slot to call the getBody method on the current object.

The powerful thing about this approach is that you can now change the page macro and the press release template will be automatically updated. For example you could put the body of the page in a table and add a sidebar on the left and the press release template would automatically use these new presentation elements.

Based on the Zope Book, © Zope Corporation

2.1.4. Advanced Usage

In this part we'll look at some more advanced features of the Template Attribute Language, including a more in-depth look at the TAL Expression Syntax (TALES).

Mixing and Matching Statements

As you have seen in the example template, you can put more than one TAL statement on the same tag. There are three limits you should be aware of, however:

  1. Only one of each kind of statement can be used on a single tag. Since HTML does not allow multiple attributes with the same name, you can't have two tal:define on the same tag.

  2. Both of tal:content and tal:replace cannot be used on the same tag, since their functions conflict.

  3. The order in which you write TAL attributes on a tag does not affect the order in which they execute. No matter how you arrange them, the TAL statements on a tag always execute in the following order: define, condition, repeat, content / replace, attributes.

To get around these limits, you can add another tag and split up the statements between the tags. If there is no obvious tag type that would fit, use span or div.

For example, if you want to define a variable for each repetition of a paragraph, you can't place the tal:define on the same tag as the tal:repeat, since the definition would happen before all of the repetitions. Instead, you would write either of the following:

      <div tal:repeat="ph phrases">
        <p tal:define="n repeat/ph/number">
        Phrase number <span tal:replace="n">1</span> is
        <b tal:content="ph">Phrase</b>.</p>
      </div>

      <p tal:repeat="ph phrases">
        <span tal:define="n repeat/ph/number">
        Phrase number <span tal:replace="n">1</span> is
        <b tal:content="ph">Phrase</b>".</span>
      </p>

Note: the definition of "n" is actually not much useful in this example because we could have directly used "repeat/ph/number" in the replace attribute which only occurs once, but it serves our purpose.

Statements with Multiple Parts

If you need to set multiple attributes on a tag, you can't do it by placing multiple tal:attributes statements on the tag, and splitting them across tags is useless.

Both the tal:attributes and tal:define statements can have multiple parts in a single statement. You separate the parts with semicolons (;), so any semicolon appearing in an expression in one of these statements must be escaped by doubling it (;;). Here is an example of setting both the src and alt attributes of an image:

      <img src="default.jpg"
           tal:attributes="src item/icon; alt item/id">

Here is a mixture of variable definitions:

      <span tal:define="global logo context/logo.gif; ids context/objectIds">

Note: in Plone 4 or newer you can use context/items instead of context/objectIds.

String Expressions

String expressions allow you to easily mix path expressions with text. All of the text after the leading string: is taken and searched for path expressions. Each path expression must be preceded by a dollar sign ($). If it has more than one part, or needs to be separated from the text that follows it, it must be surrounded by braces ({}). Since the text is inside of an attribute value, you can only include a double quote by using the entity syntax &quot;. Since dollar signs are used to signal path expressions, a literal dollar sign must be written as two dollar signs ($$). For example:

      <span tal:replace="string:Just text."/>
      <span tal:replace="string:© $year, by Me."/>
      <span tal:replace="string:Three ${vegetable}s, please."/>
      <span tal:replace="string:Your name is ${user/getUserName}!"/>
      <span tal:replace="string:She answered &quot;$answer&quot;."/>
      <span tal:replace="string:This product costs $price $$."/>

Nocall Path Expressions

An ordinary path expression tries to render the object that it fetches. This means that if the object is a function, Script, Method, or some other kind of executable thing, then the expression will evaluate to the result of calling the object. This is usually what you want, but not always. For example, if you want to put a DTML Document into a variable so that you can refer to its properties, you can't use a normal path expression because it will render the Document into a string.

If you put the nocall: expression type prefix in front of a path, it prevents the rendering and simply gives you the object. For example:

      <span tal:define="doc nocall:context/aDoc"
            tal:content="string:${doc/id}: ${doc/title}">
      Id: Title</span>

This expression type is also valuable when you want to define a variable to hold a function or class from a module, for use in a Python expression.

Other Builtin Variables

You have already seen some examples of the builtin variables template, user, repeat, and request. Here is a more complete list of the other builtin variables and their uses:

nothing
A false value, similar to a blank string, that you can use in tal:replace or tal:content to erase a tag or its contents. If you set an attribute to nothing, the attribute is removed from the tag (or not inserted), unlike a blank string. Equivalent to None in Python.
default
A special value that doesn't change anything when used in tal:replace, tal:content, or tal:attributes. It leaves the template text in place.
options
The keyword arguments, if any, that were passed to the template.
attrs
A dictionary of attributes of the current tag in the template. The keys are the attributes names, and the values are the original values of the attributes in the template.
root
The root Zope object. Use this to get Zope objects from fixed locations, no matter where your template is placed or called.
context
The object on which the template is being called. This is often the same as the container, but can be different if you are using acquisition. Use this to get Zope objects that you expect to find in different places depending on how the template is called.
here
An (older) alias for context.
container
The container (usually a Folder) in which the template is kept. Use this to get Zope objects from locations relative to the template's permanent home.
request
Contains the complete information about the current HTTP request that Zope is processing. See this page in the zope.org wiki for further info about the request object.
modules
The collection of Python modules available to templates. See the section on writing Python expressions.
view
For templates called from a Zope 3-style view only, this variable refers to the associated view class. This may then contain functions and variables prepared explicitly for the template to output

Alternate Paths

The path template/title is guaranteed to exist every time the template is used, although it may be a blank string. Some paths, such as request/form/x, may not exist during some renderings of the template. This normally causes an error when the path is evaluated.

When a path doesn't exist, you often have a fallback path or value that you would like to use instead. For instance, if request/form/x doesn't exist, you might want to use context/x instead. You can do this by listing the paths in order of preference, separated by vertical bar characters (|):

      <h4 tal:content="request/form/x | context/x">Header</h4>

Two variables that are very useful as the last path in a list of alternates are nothing and default. Use nothing to blank the target if none of the paths is found, or default to leave the example text in place.

You can also test the existence of a path directly with the exists: expression type prefix. A path expression with exists: in front of it is true if the path exists, false otherwise. These examples both display an error message only if it is passed in the request:

      <h4 tal:define="err request/form/errmsg | nothing"
          tal:condition="err" tal:content="err">Error!</h4>

      <h4 tal:condition="exists:request/form/errmsg"
          tal:content="request/form/errmsg">Error!</h4>

Dummy Elements

You can include page elements that are visible in the template but not in generated text by using the builtin variable nothing, like this:

      <tr tal:replace="nothing">
        <td>10213</td><td>Example Item</td><td>$15.34</td>
      </tr>

This can be useful for filling out parts of the page that will take up more of the generated page than of the template. For instance, a table that usually has ten rows will only have one row in the template. By adding nine dummy rows, the template's layout will look more like the final result.

Inserting Structure

Normally, the tal:replace and tal:content statements quote the text that they insert, converting < to &lt;, for instance. If you actually want to insert the unquoted text, you need to precede the expression with the structure keyword. Given a variable copyright with a string value of "© 2008 By <b>Me</b>", the following two lines:

      <span tal:replace="copyright">Copyright 2008</span>
      <span tal:replace="structure copyright">Copyright 2008</span>

...will generate "© 2001 By <b>Me</b>" and "© 2001 By Me", respectively.

This feature is especially useful when you are inserting a fragment of HTML that is stored in a property or generated by another Zope object. For instance, you may have news items that contain simple HTML markup such as bold and italic text when they are rendered, and you want to preserve this when inserting them into a "Top News" page. In this case, you might write:

      <p tal:repeat="article topnewsitems"
         tal:content="structure article">A News Article</p>

Basic Python Expressions

A Python expression starts with python:, followed by an expression written in the Python language. Python is a simple and expressive programming language. If you have never encountered it before, you should read one of the excellent tutorials or introductions available at the official website http://www.python.org.

A Page Template Python expression can contain anything that the Python language considers an expression. You can't use statements such as if and while, and Zope's security restrictions are applied.

Comparisons

One place where Python expressions are practically necessary is in tal:condition statements. You usually want to compare two strings or numbers, and there isn't any other way to do that. You can use the comparison operators < (less than), > (greater than), == (equal to), and != (not equal to). You can also use the boolean operators and, not, and or. For example:

        <p tal:repeat="widget widgets">
          <span tal:condition="python:widget.type == 'gear'">
          Gear #<span tal:replace="repeat/widget/number">1</span>:
          <span tal:replace="widget/name">Name</span>
          </span>
        </p>

Sometimes you want to choose different values inside a single statement based on one or more conditions. You can do this with the test function, like this:

        You <span tal:define="name user/getUserName"
              tal:replace="python:test(name=='Anonymous User', 'need to log in', default)">
              are logged in as
              <span tal:replace="name">Name</span>
            </span>

        <tr tal:define="oddrow repeat/item/odd"
            tal:attributes="class python:test(oddrow, 'oddclass', 'evenclass')">

Using other Expression Types

You can use other expression types inside of a Python expression. Each type has a corresponding function with the same name, including path(), string(), exists(), and nocall(). This allows you to write expressions such as:

        "python:path('context/%s/thing' % foldername)"
        "python:path(string('context/$foldername/thing'))"
        "python:path('request/form/x') or default"

The final example has a slightly different meaning than the path expression "request/form/x | default", since it will use the default text if "request/form/x" doesn't exists or if it is false.

Getting at Zope Objects

Much of the power of Zope involves tying together specialized objects. Your Page Templates can use Scripts, SQL Methods, Catalogs, and custom content objects. In order to use them, you have to know how to get access to them.

Object properties are usually attributes, so you can get a template's title with the expression "template.title". Most Zope objects support acquisition, which allows you to get attributes from "parent" objects. This means that the Python expression "context.Control_Panel" will acquire the Control Panel object from the root folder. Object methods are attributes, as in "context.objectIds" and "request.set". Objects contained in a folder can be accessed as attributes of the folder, but since they often have Ids that are not valid Python identifiers, you can't use the normal notation. For example, instead of writing "context.penguin.gif", you must write "getattr(context, 'penguin.gif')".

Some objects, such as request, modules, and Zope Folders support item access. Some examples of this are:

      request['URL'], modules['math'], and context['thing']

When you use item access on a Folder, it doesn't try to acquire the name, so it will only succeed if there is actually an object with that Id contained in the folder.

Path expressions allow you to ignore details of how you get from one object to the next. Zope tries attribute access, then item access. You can write "context/images/penguin.gif" instead of "python:getattr(context.images, 'penguin.gif')", and "request/form/x" instead of "python:request.form['x']".

The tradeoff is that path expressions don't allow you to specify those details. For instance, if you have a form variable named "get", you must write "python:request.form['get']", since "request/form/get" will evaluate to the "get" method of the form dictionary.

Using Scripts

Script objects are often used to encapsulate business logic and complex data manipulation. Any time that you find yourself writing lots of TAL statements with complicated expressions in them, you should consider whether you could do the work better in a script.

Each script has a list of parameters that it expects to be given when it is called. If this list is empty, then you can use the script by writing a path expression. Otherwise, you will need to use a Python expression, like this:

      "python:context.myscript(1, 2)"
      "python:context.myscript('arg', foo=request.form['x'])"

If you want to return more than a single bit of data from a script to a page template, it is a good idea to return it in a dictionary. That way, you can define a variable to hold all the data, and use path expressions to refer to each bit. For example, supposing we have a getPerson script which returns a dictionary like {'name':'Fred', 'age':25}:

      <span tal:define="person context/getPerson"
            tal:replace="string:${person/name} is ${person/age}">
      Name is 30</span> years old.

Calling DTML

DTML is another templating language made available by Zope, mostly replaced by ZPT nowadays, but still in use. You can read more about it in the relevant chapter of the Zope Book.

Unlike Scripts, DTML Methods don't have an explicit parameter list. Instead, they expect to be passed a client, a mapping, and keyword arguments. They use these to construct a namespace.

When Zope's ZPublisher publishes a DTML object, it passes the context of the object as the client, and the REQUEST as the mapping. When one DTML object calls another, it passes its own namespace as the mapping, and no client.

If you use a path expression to render a DTML object, it will pass a namespace with request, context, and the template's variables already on it. This means that the DTML object will be able to use the same names as if it were being published in the same context as the template, plus the variable names defined in the template.

Python Modules

The Python language comes with a large number of modules, which provide a wide variety of capabilities to Python programs. Each module is a collection of Python functions, data, and classes related to a single purpose, such as mathematical calculations or regular expressions.

Several modules, including "math" and "string", are available in Python Expressions by default. For example, you can get the value of pi from the math module by writing "python:math.pi". To access it from a path expression, however, you need to use the modules variable. In this case, you would use "modules/math/pi". Please refer to the Zope Book or a DTML reference guide for more information about these modules.

The "string" module is hidden in Python expressions by the "string" expression type function, so you need to access it through the modules variable. You can do this directly in an expression in which you use it, or define a global variable for it, like this:

      tal:define="global mstring modules/string"
      tal:replace="python:mstring.join(slist, ':')"

Modules can be grouped into packages, which are simply a way of organizing and naming related modules. For instance, Zope's Python-based Scripts are provided by a collection of modules in the "PythonScripts" subpackage of the Zope "Products" package. In particular, the "standard" module in this package provides a number of useful formatting functions that are standard in the DTML "Var" tag. The full name of this module is "Products.PythonScripts.standard", so you could get access to it using either of the following statements:

      tal:define="pps modules/Products.PythonScripts.standard"
      tal:define="pps python:modules['Products.PythonScripts.standard']"

Most Python modules cannot be accessed from Page Templates, DTML, or Scripts unless you add Zope security assertions to them. That's outside the scope of this document, and is covered by the Zope Security Guide.

Special HTML attributes

The HTML boolean attributes checked, selected, nowrap, compact, ismap, declare, noshade, disabled, readonly, multiple, selected and noresize are treated differently by tal:attributes. The value is treated as true or false (as defined by tal:condition). The attribute is set to attr=”attr” in the true case and omitted otherwise. If the value is default, then it is treated as true if the attribute already exists, and false if it does not. For example, each of the following lines:

<input type="checkbox" checked tal:attributes="checked default">
<input type="checkbox" tal:attributes="checked string:yes">
<input type="checkbox" tal:attributes="checked python:42">

will render as:

<input type="checkbox" checked="checked">

while each of these:

<input type="checkbox" tal:attributes="checked default">
<input type="checkbox" tal:attributes="checked string:">
<input type="checkbox" tal:attributes="checked nothing">

will render as:

<input type="checkbox">

This article contains information and examples from the Zope Book, © Zope Developers Community.

2.1.5. Global Template Variables

Plone defines a few useful global variables to use them in your templates

While writing templates for Plone, you will notice a set of variables you use more often, like the URL of the portal or the currently authenticated member.

For your convenience, Plone defines a few global template variables that are pulled into main_template via global_defines. Some of the most useful ones are:

portal
The portal object.
portal_url
The url of the portal.
member
The current user (None if user is anonymous)
checkPermission
A function to check if the current user has a certain permission in the current context, e.g. checkPermission('View portal content', context).
isAnon
True if the current user is not logged in.
is_editable
True if the current user has edit permissions in the context.
default_language
The default language of the portal.
here_url
The URL of the current object.

To see the full list list of these variables, see the docstring for globalize() in the interface Products.CMFPlone.browser.interfaces.IPlone.

2.1.6. Customizing AT Templates

This tutorial describes the steps to produce a fully-customized view of an Archetypes object. This is applicable to tweaking little details of the default AT behavior as well as gutting and re-building the view from scratch. (contributed by Floyd May)

2.1.6.1. Introduction

Goals, Pre-Requisites, and Tools

So you think Archetypes' way of automagically generating HTML to view your object isn't pretty enough? Well, you've come to the right place! I'm going to teach you how to dress up those boring, drab views and make them shine!

Goals: What will I learn?

  • How Archetypes generates views for content objects
  • How much control Archetypes gives you
  • How to change the HTML output for a particular field by creating a custom widget template
  • How to use the Archetypes template framework to make minor changes to the default AT-generated view
  • How to customize the HTML output for the entire view of an Archetypes object by using the title,body,folderlisting, and footer macros

Pre-Requisites: What do I need to know?

  • How to read and write Python code
  • How to read and write Zope Page Templates (ZPT)
  • How to create Archetypes-based products (ArchGenXML is acceptable)

Tools: What do I need to have installed?

  • Plone 2.0 or 2.1
  • Archetypes (included by default in Plone 2.1)
  • The ATViewTutorial product - this product has examples of the concepts in this tutorial

2.1.6.2. What Makes It Tick?

This page describes how Archetypes uses different templates to generate HTML, and how AT-based template customization can be applied.

Archetypes has a very clever system for generating HTML pages for AT-based objects. The same set of templates generates all of the content areas for all AT-based objects. This buys you the following benefit, useful for site consistency:

  • All the pages look the same.

However, it also has the following drawback:

  • All the pages look the same.

Different types of content need to be displayed differently. Let's dig into how Archetypes does things, so we can figure out how to make your content types shine!

The base_view Template

The base_view template (found in the archetypes skin) handles selecting the appropriate macros from the appropriate templates, and using those macros to display content objects. If you look at this piece of code from 'base_view':

        <tal:block define="portal_type python:here.getPortalTypeName().lower().replace(' ', '_');
                           view_template python:'%s_view' % portal_type;
                           view_macros python:path('here/%s/macros|nothing' % view_template);
                           macro view_macros/css | nothing"
                   condition="macro">

You can see that it defines the variable view_template as the object's Portal Type Name converted to lowercase, with spaces replaced with underscores (_), followed by _view. So, MyType's view template, for instance, would be called mytype_view.

Now, before we move on, I must warn you: don't edit base_view. Seriously. Don't.[1]

No, really. Don't customize base_view. If you think you need to customize base_view, first, well... don't. Keep reading the tutorial. If you're certain, after reading the tutorial, that you need to customize base_view, again, don't! Write a clear, concise example indicating why, after reading this tutorial, you believe you should customize base_view, and send it to the archetypes-users mailing list. If you really do need to customize base_view, you've found a shortcoming in Archetypes, and the people on the list will inform you if that's the case. So, repeat after me: "Don't customize base_view." Good!

Now, there are six important macros to be aware of. These six macros give you the power to insert template code that is customized for your class. These macros are:

js
A macro to insert javascript into the <head> tag of the generated HTML page
css
A macro to insert CSS includes and style code into the <head> tag of the page
header
The macro that defines the topmost portion of the content area. By default, this macro has an <h1> tag that contains the title, and links for printing, emailing, etc. on the right.
body
The macro that defines the "body" area of the content. This is where the fields and their values are displayed.
folderlisting
This macro shows a list of the child content for the object. Don't confuse this with folder_contents, this is what the view tab shows for folderish objects. Folderish objects use both the body macro and the folderlisting macro.
footer
This is where AT puts the byline.

This image shows the areas generated by the header, body, and folderlisting macros

As you can see, the header macro generates what's outlined in the area marked ''header'' in red, the body macro generates the content just beneath it, and the folderlisting macro generates the listing of the objects within the folderish object.

The base_view template automatically pulls the appropriate macro from the custom view template (mytype_view, from our earlier ad-hoc example), or from the next template that we are going to explore: base.

The base Template

The base template contains four of the six macros that base_view looks for:

  • header
  • body
  • folderlisting
  • footer

The only reason why I mention base is so that you know where AT's default behavior comes from. This is important if you only want to change a little bit of a type's view. It's often helpful to copy the macro from base into your custom view template, and then start tweaking and customizing.

Widgets

Widgets are what Archetypes uses to display fields. Widgets have two parts:

The widget class
This class defines data and behavior for the widget. In most cases, you'll never need to create a custom 'Widget'-derived class. See Archetypes/Widget.py for examples.
The widget template
This is a ZPT that provides three macros: view, edit, and search. These macros display the field. Some of the macros depend on certain variables being defined in the calling template, so pay close attention. Most often, you'll only need to provide a custom widget template, and not a custom widget class.

There are all kinds of widgets out there to do all kinds of things. The Archetypes Quick Reference Manual covers the details for the various widgets in Archetypes.

[1] Unless you're wiggy.

2.1.6.3. Customizing Widgets

This page shows you how to customize widgets, and gives some examples of what kinds of neat tricks can be done with widget customization.

As we've already said, widgets are what Archetypes uses to display individual fields. Archetypes' built-in templates, base_view,base, and widgets/field use each field and the field's associated widget (specified in the schema) to determine which widget template to use. However, you can override a widget's template, as we will show below. Furthermore, you can create a whole new widget class, which will have data and operations specific to the display of your custom data type. Read the next section to determine how much widget hacking you need to do.

How to Determine If You Need to Create A Custom Widget Class

If you cannot find a widget in Archetypes or in a readily-available third-party product that does what you need, use the following set of questions to determine if you can just customize the template versus creating a new widget class. If you answer "no" to the following questions, a custom template is all you need:

  • Does the display of your field require helper functions to do conversions or formatting that would be difficult or cumbersome in TALES?
  • Do you have multiple AT-based classes where some fields in those classes share all of the the following characteristics?
    • The same data type
    • Similar, needs for display
    • One or more attributes that are class-specific that apply to the display (i.e. the most appropriate place to set these attributes is in the schema definition)
  • Does your custom data type need some super-specific marshalling when edited or searched that you can't get from any standard AT widget class?
  • Do you need to override or change the way that Archetypes handles the processing of the edit form for a particular field?

If you answered yes to most of the questions, then you might need to create a Widget class. If the questions aren't clear, take a look at the RichDocument tutorial . If you've got a specific enough use-case (like RichDocument) that you need custom widget classes, you're probably able to make it happen just by the sheer fact that you know you need them. [1]

Customizing Widget Templates

Creating custom widget templates is not hard, so don't be afraid. I assume if you're reading this far, you've already determined that you don't need a custom widget class, and just need to tinker a little bit with how things are done by default.

First, you should understand what you have control over by customizing a widget template itself. You're controlling the display of the widget's data, but not its label. For a StringField called myfield, the default display is something like:

myfield: some value

The only thing we can control is the display of what comes after "myfield:", which is just the data contained within the field itself (we'll talk about how to customize the display of labels later). However, if we customize the template, we can insert all kinds of nifty HTML into there! So, let's look at StringWidget's template, 'widget/string':

It's pretty straight-forward. As you can see, there are three important macros in a widget template:

  • view
  • edit
  • search

Don't concern yourself with the edit and search macros; remember, we're customizing the view. Let's start by creating a new template called my_string_widget [2]. Start like this:

Notice how we use the same pass-through macro call in the edit macro that the string template originally uses in the search macro. It's important to remember the following concept: Widget templates must define all three macros: view, edit, and search. Also, notice how there is no display code for the label; that's handled elsewhere. If you're wondering where the accessor variable comes from, that's part of the widget display code. The widget class defines the following local variables that are accessible inside widget templates:

accessor
The accessor method for the field. Call it to retrieve the value of the field.
fieldName
The name of the field.
widget
The widget object for the field.
field
The instance of the field class itself.
mode
Will be view or edit, based on the action being taken. For our purposes, it should always be view.

Now, let's modify the way that our StringField displays. For brevity, I'll just show the view macro:

Then, we should tell our type's schema to point at the new template:

    StringField('myfield',
        widget=StringWidget(
            label='Myfield',
            label_msgid='ATViewTutorial_label_myfield',
            description_msgid='ATViewTutorial_help_myfield',
            i18n_domain='ATViewTutorial',
            macro='my_string_widget',
        )
    ),

Be sure to restart Zope and reinstall using portal_quickinstaller. Now, our StringField, when rendered, looks like this:

Custom Widget

Yes, folks, it's just that easy.

[1] Again, this probably only applies to wiggy.

[2] Notice I'm breaking AT's convention here. You don't have to do that, but I find it more convenient and understandable to add a _widget to the names of my widget templates.

 

2.1.6.4. Total Control: The View Template

This page describes how you can control every portion of the HTML output in the content area by creating a custom view template.

Okay, so you've hacked up some custom widget templates, and they're basking in the glory of your newfound power, yet you're still not satisfied. You want to control it all! Well, I've got the information for you!

Archetypes and Type-Specific View Templates

Archetypes automatically recognizes templates with specific names, and uses the code within those templates to display your AT-based object. All of this magic happens within the base_view template. To create a custom view template, convert your type name to lowercase (the name that's listed in portal_types, or what's returned from myObject.portal_type). Now, replace spaces with underscores ( _ ). Finally, add _view to the end of the name, and you've almost got a custom view template.

See below for examples of type names and their corresponding view templates.

Type Name

View Template Name

My Type

my_type_view

SomeTypeV2

sometypev2_view

Now, to remedy that "almost" part of the above paragraph, define one or more of the following macros in your template:

  • header
  • body
  • folderlisting
  • footer

Voila! You've got a custom view template. To see how this works, create a simple template (named appropriately, of course) that contains the following code:

          Foo

          Foo

          Foo

          Foo

And, just like magic, you should see, rendered in your content area:

The Infamous "Foo" View

But Wait! Where Are All My Fields?

So now you want your data back. You said you wanted total control, and now you don't want total control. But the point of this tutorial isn't control, it's shine. So, let's explore how to mix and match existing AT templates with your custom code to make a shiny template that renders exactly how you want it.

First, keep the above "Foo" template around. It's very useful when you aren't quite sure which of the four macros is generating a portion of the content area. Simply comment out one or more of the macros, and you'll see which macro generates what portion.

Now, do you remember when I talked about using the base template as a starting point for creating custom templates? Well, that's what we'll do. Let's start by customizing the footer. The footer macro in the following template is copied directly from 'base':

Get the byline - contains details about author and modification date.

Now, let's add something below the byline, say, some important information that applies to every instance of your custom type::

Get the byline - contains details about author and modification date.

Important information that applies to every instance of my custom type.

Notice that all we had to do was copy the macro from base , and add the <p> tag with some text in it. Notice that, for example, we could have used tal:content="here/getCustomFooterData" in the </p> <p> tag if we had defined a getCustomFooterData() method in our class.

Now, let's apply this concept to the body macro, and play around with displaying fields. First, we'll start by coping the body from base into our template.

Now, we'll change up some things by adding a little bit of code into the macro. First, notice that the tal:repeat is repeating over all the fields that are not metadata. Therefore, if you want to do something for every field, put it inside this macro. You could (conceivably) rearrange the macro so that the tal:repeat loop is inside another containing block, and put TAL code before and after the display of the fields, or make use of the first and last repeat variables to achieve the same thing. So, let's do two things to customize our body macro:

  • Surround all the fields with a </p> <div> that has a custom CSS class, my-custom-at-body
  • Surround each field with a <div> that has a different custom CSS class, my-custom-at-field
 

These changes, as I'm sure you've figured out, aren't going to make much of a difference (if any) in the look of the rendered page without actually writing some custom CSS code. We now introduce the css macro:

<link href="#" type="text/css" rel="stylesheet" />
<div class="my-custom-at-body">
<div class="my-custom-at-field">&nbsp;</div>
</div>

Now, we can define a CSS stylesheet called my_custom_css.css that contains our custom CSS code:

    .my-custom-at-body {
        border: thin dashed;
        background-color: #cccccc;
        padding-top: 1em;
    }

    .my-custom-at-field {
        background-color: #ffffff;
    }

Archetypes inserts the css macro into the '' tag of the rendered page, making our custom CSS code, linked files, and includes available within the page. Our end-result would look something like this:

Custom Body Macro

If we had created custom widget templates, those would be applied to the rendered page as well.

Customizing Labels

There's still one element of control that we're missing: we still can't override the display of a field label. By customizing the display of the label, we can insert images, links, etc. into the page instead of the default label.

The macro included in our custom view template below will do that magic for us:

<link href="#" type="text/css" rel="stylesheet" />
<div class="my-custom-at-body">
<div class="my-custom-at-field">&nbsp;</div>
</div>
<label>Now presenting... Field1!</label>

Notice that I've only overridden the default label for fields labeled "myfield". The label macro in widgets/field is where the default behavior can be found. The final result looks like this:

Customized Label

Also, don't forget that you have the power to omit head,body,folderlisting, and footer by simply writing in do-nothing macros into your view template. Furthermore, you can reach into your object and retrieve field values without using the widget framework.

2.1.6.5. Conclusion

Some final notes about customizing Archetypes view templates

So, now you should know all of the following information:

  • How to identify which parts of an Archetypes view template are generated by the header,body,folderlisting, and footer macros
  • How to create a custom view template that overrides one or more of the header,body,folderlisting, and footer macros
  • How to create a custom widget template that works in the Archetypes framework
  • How to create a custom body template that uses Archetypes' widget rendering templates
  • How to inject custom CSS code and links to custom CSS files into your view template

Some Final Notes

I want to cover a few details about how to apply all of these tools. Some wise guy somewhere said something like, "To a man whose only tool is a hammer, every problem tends to look like a nail." Your success with Archetypes is very contingent upon selecting the appropriate tool for your specific problem. So, use the following outline of the basic AT page layout as a guideline to determine what should be customized:

  • header macro
    • Title (or id if no title is present)
    • Document actions (e.g. print, send to)
  • body macro
    • List of fields
      • Field label (from the label macro in the view template, if one exists)
      • Field value (from the widget template's view macro)
  • folderlisting macro
    • List of links to each sub-object
  • footer macro
    • byline

So, based on which parts of this standard layout you need to customize, use the appropriate macro. Keep the infamous "Foo" template around to help you with debugging. See the next page for a reference on customizing Archetypes view templates.

2.1.6.6. Reference

A reference for customizing Archetypes view templates

View Templates

View templates are named according to the portal_type of the class. To create the name for a view template, follow these steps to create the name of the template:

  1. Change the portal_type to lowercase.
  2. Replace all spaces with underscores (_).
  3. Append _view to the end of the name.

Archetypes will automatically locate templates with names created according to the above steps, and will make use of the macros defined within the template. View templates can define one or more of the following macros:

css
A macro for inserting type-specific CSS code, including <link> tags pointing to custom CSS files. There is no default macro for this within Archetypes; Archetypes uses the existing CSS styles in Plone to render AT-based objects.
header
This macro, by default, generates the <h1> tag containing the object's title and the document actions (print, rss, e.g.)
body
The location where the fields and values are displayed by default. When rendering fields using the existing widget mechanism, be sure to tal:define the variable field as the current field; the widget templates depend on this variable being set.
folderlisting
This is the folder listing display when viewing the view tab of a folderish object. This is not the same as the contents view.
footer
By default, this is where Archetypes puts the byline.
label
This template generates field labels.

For any of these macros that is not defined in the custom view template, Archetypes will use the default behavior in its place, taken from base or widgets/field.

Widget Templates

Use custom widget templates by naming them in the schema - insert a macro parameter into the widget's constructor in the schema, and set the value to the name of the template. For example, macro="my_widget_template". Widget templates must have the following three macros:

  • view
  • edit
  • search

Widget templates have the following local variables available within TALES expressions:

accessor
The accessor method for the current field. The code <p tal:content="accessor" /> will cause the field's value to be written within the <p> tag.
fieldName
The name of the field.
widget
The widget object for the field.
mode
Will always be view for view templates, but is useful for, say, error checking.

2.1.7. How to customise view or edit on archetypes content items

Explains one way to customise the view or edit templates without having to change the action of an object.

Reasons/Use Cases

I usually like to customise as little as possible so that more of my page templates are just like the plone default templates. I find this helps when I move to a new version and also makes doing styling using CSS easier.

Another use case is if I want to generate a form using the schema but I need it to do different things based on which button is pushed, you can accomplish this with putting named buttons on the form in combination by using portal_formcontroller to override what happens on a submit. E.g. importing data from CSV, in a seperate schemata I have a form.button.Import button and on this schemata I only show this button and the cancel button (instead of save, nex previous etc.) and then I customise the portal_formcontroller action (and validation) so content_edit (the script that does the saving) goes to a script that does the importing before going back to the view action.

Archetypes base_view and base_edit

Both of these templates have several macros which are gotten by from other page templates. They are setup in such a way that they will look for a template named with the content type for these macros and then default to the generic archetypes macros. I.e. say you have a content type 'Newsletter' base_view looks for a template named 'newsletter_view' if it finds it and it contains the right macros it will use those instead of the default 'view_macros' (found in 'portal_skins/archetypes' skin folder.

Below is a skeleton example of a custom view template showing the different things you can customise. See base.pt

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"

      lang="en"

      metal:use-macro="here/main_template/macros/master"

      i18n:domain="plone">

<body>



<metal:main fill-slot="main">

        <!-- header, H1 with title in it -->

        <metal:header define-macro="header">

        

        </metal:header>

        

        <!-- body macro where all the fields are -->

        <metal:body define-macro="body">

                

        </metal:body>

        

        <!-- folderlisting that shows sub-objects if there are any -->

        <metal:folderlisting define-macro="folderlisting">

                

                

        </metal:folderlisting>

        

        <!-- footer, by line created date etc. -->

        <metal:footer define-macro="footer">

                

        </metal:footer>

        

</metal:main>

</body>

</html>

Below is an skeleton of a custom edit template:

<html xmlns="http://www.w3.org/1999/xhtml"

      xml:lang="en"

      lang="en"

      xmlns:tal="http://xml.zope.org/namespaces/tal"

      xmlns:metal="http://xml.zope.org/namespaces/metal"

      xmlns:i18n="http://xml.zope.org/namespaces/i18n"

      i18n:domain="plone">



  <metal:head define-macro="topslot">

  </metal:head>

  

  <metal:head define-macro="javascript_head">

  </metal:head>



  <body>

        <!-- header, h1 of Edit <Type>, schemata links and webdav lock message -->

        <metal:header define-macro="header">



        </metal:header>

        

        <!-- typedesription, typeDescription from the content type -->

        <metal:typedescription define-macro="typedescription">



        </metal:typedescription>



        <!-- body, editform , fields, buttons, the default macro 

             contains a number of slots which usually provide enough

             ways to customise so often I use that macro and just 

             fill the slots

        -->

        <metal:body define-macro="body">

            <metal:default_body use-macro="here/edit_macros/macros/body">

              <!-- inside the fieldset but above all the fields -->

              <metal:block fill-slot="extra_top">

              </metal:block>

              

              <!-- listing of the fields, usually I won't customise this

              <metal:block fill-slot="widgets">

              </metal:block>

              -->



              <!-- below the fields above the formControls (hidden fields for refernce stuff is above buttons) -->

              <metal:block fill-slot="extra_bottom">

              </metal:block>



              <!-- within the formControls these are the default previous, next, save, cancel buttons -->

              <metal:block fill-slot="buttons">

              </metal:block>



              <!-- within the formControls a slot for extra buttons -->

              <metal:block fill-slot="extra_buttons">

              </metal:block>



            </metal:default_body>

        </metal:body>



        

        <!-- footer, by line created date etc. -->

        <metal:footer define-macro="footer">

        

        </metal:footer>



  </body>



</html>



See the templates into Products.Archetypes:skins/archetypes for examples about how does Archetypes work by default: get the field lists, hook up translation, handle form processing and more. Using them as a base and customizing only the neccessary bits can make the job much easier than starting from scratch.

How to do it

Lets say your content type is 'Newsletter'

Steps for View

  1. Create a page template (either file system of in ZMI) called 'newsletter_view'
  2. Use the skeleton and comment out the macros you wish to keep the same. I.e. the ones you want to use from view_macros template (in portal_skins/archetypes)
  3. Put your code into the relevant macros/slots.
  4. Test and you are done.

Steps for Edit

  1. Create a page template called 'newsletter_edit'
  2. Use the skeleton and then comment out the macros you wish to use the default for. (from edit_macros).
  3. Put your code into the relevant macros/slots.
  4. Test and you are done.

2.1.8. Create an Alternative Edit Template

Suppose you've a content type and you want to keep the default "edit" template, but you want also to create another edit template suited for editing only some particular metadata.

Think of the standard edit as a "full" edit, while your new edit is a custom version. They co-exist together. This method also works if you've already a mycustomtype_edit edit template, and you want to have another one.

Limiting the Fields Available to Edit

Step 1: Copy the base_edit and the edit_macros (you can do it by simply customizing them) and change their id (rename) to mynewedit and mynewmacros

Step 2: Modify mwynewedit to point to mynewmacros:

  • edit_template python:'newemacros';
  • edit_macros python:path('here/newemacros/macros | nothing');

so this edit template will pick up the macros from your custom version.

Step 3: Modify mynewmacros as you need. For example, if you want to display only some fields, you can (in<metal:block define-slot="widgets"> which manages widgets display) change the template as below:

<tal:fields repeat="field 
python:schematas[fieldset].editableFields(here, visible_only=True)">

   <div tal:omit-tag="" 
         tal:condition="python:field.getName() 
         in ['title','myfield1','myfield2','myfield3','myfield4']">

       <metal:fieldMacro use-macro="python:here.widget(field.getName(),
        mode='edit')" />

     </div>

</tal:fields>

So only 'title','myfield1','myfield2','myfield3','myfield4' will be displayed and you will avoid people editing unwanted fields (even if they can).

Step 4: If there's a failure, the user should go back to your form, so in mynewedit -> actions, change failure from string:edit to string:mynewedit 

Dealing with Validation

Suppose you've some validated fields, or you want to run custom code when the user saves the form. The validation will kick in when you'll press "save". If you've some required fields, but they're not in the fields listed above, you'll get the red warning and your data will not be saved. So you have to bypass it, and we need to do it in two places:

Step 1:

  • edit mwynewedit -> validation -> delete the validation step.
  • edit mwynewedit -> actions -> change string:content_edit to string:mycontent_edit (a custom content edit)

Step 2:

  • Find content_edit in portal_skins/archetypes and customize it. Then rename it to mycontent_edit. Here you can add custom code, sending mail and so on.
  • Edit it -> actions -> delete the validate_integrity step
  • Edit it -> actions -> change any string:edit to string:mynewedit , so after saving you'll go to your edit form if there are any failures (should not if you remove the validation)

Now your form can edit your content type without any restrictions. If you need some restrictions, just don't delete the validation steps above and customize the validation scripts validate_base and validate_integrity (renaming them and pointing to them in the validation steps above).

This is enough to perform your "very" own custom edit, with custom saving and custom validation, leaving the default one untouched.

2.2. Where to find what you need

Where the skin lives in Plone and in your own theme product.

Through the Web

You can customize all page templates, skins and CSS very easily through the web.

  • Site Setup > Zope Management Interface > portal_skins

Locate the item you want to change, click the customize button and a copy will be dropped into the custom layer for you.

You can also add new page templates, Python scripts and files (for CSS and JavaScript) to the custom layer by using the drop-down list in the ZMI. In most cases, though, you will find it easier to locate a template you want to base your new template on, customize it and then rename it through the ZMI.

Don't forget that, if you're hunting for something, the Find tab in the ZMI can be very useful.

Plone Default Skin on the File System

All the page templates, style sheets, scripts and JavaScript for the Plone Default skin can be found in the CMFPlone product:

[your products directory]/CMFPlone/skins
You'll see there are a number of directories corresponding to specific skin layers. Most of this should be self-explanatory, but its worth remembering that only the generic templates live in plone_templates. If you want to track down a specific content view (e.g., document_view) then you'll need to look in plone_content.

In your own Theme Product

The skins folder in your theme product/skins/[your theme namespace].[your theme name]_custom_templates | custom_images | styles
These directories will form your skin layers. Your templates, images, and style sheets can go here. If you asked it to, the plone3_theme paster template will have provided blank style sheets to override the Plone Default ones.
/skins.zcml
When your Zope instance starts up, this turns your directories into skin layers
Subsidiary files used for installing and setting up the Skin/profiles/default/skins.xml | cssregistry.xml | jsregistry.xml
When your theme is installed in your Plone site, these set up the hierarchy of skin layers, and register your style sheets and JavaScript with the registries
 

2.3. Style Sheets

Style Sheets

2.3.1. The Custom Style Sheet and Base Properties

You can do a great deal by simply overriding Plone's existing styles. There's a stylesheet available for just this purpose.

You'll find an empty stylesheet called ploneCustom.css in

  • [your products directory]/CMFPlone/skins/plone_styles or
  • Site Setup > Zope Management Interface > portal_skins > plone_styles

This stylesheet is always loaded last on a page, and can be used, therefore, to override any other styles. There's an excellent and comprehensive tutorial on this here:

 

DTML

You'll see that ploneCustom.css has a .dtml extension, and the CSS inside is wrapped in

/* <dtml-with base_properties> */
 .......
/* </dtml-with> */

DTML is another Zope templating language, which in this case is deployed so that particular variables can be picked up from a properties sheet (base_properties.props) - for example:

#portal-column-one {
    vertical-align: top;
    width: <dtml-var columnOneWidth missing="16em">;
    border-collapse: collapse;
    padding: 0;
}


We wouldn't recommend using this technique as it is likely to be phased out, but it is as well to know that it is there. You can sometimes get caught out if you're customizing ploneCustom.css and accidentally delete the top or bottom "dtml-with" statement, or forget to add the .dtml extension.

2.3.2. CSS

Getting familiar with Plone's style sheets

2.3.2.1. CSS Quick Start

instructions for finding and modifying Plone's base_properties and CSS.

Purpose, Prerequisites & Audience

This tutorial describes the use of CSS (Cascading Style Sheets) in Plone 3.x and is intended for site customizers who are familiar with CSS and have administrative privileges on a Plone site. The approach here is strictly for making through-the-web modifications to stylesheets.

No previous knowledge of Python, Plone or Zope is required and examples will walk those new to Plone through every step required to make CSS modifications to Plone.  If you have set up a Plone site but are otherwise new to Plone, this tutorial is for you.  If you are a web designer who needs to work as part of a Plone team, this tutorial might help clarify how CSS is used in Plone.

Using the Right Tools

By far, the most popular website CSS introspector tool is Mozilla's Firebug tool. No matter the level of your experience, Firebug is the ultimate CSS debugging tool and all themers should use it.

The basic idea is that you can step through the HTML that frames your Plone pages, see the CSS that is applied to the HTML. You can even change the CSS settings on the fly to experiment with the layout of your site. For help on using Firebug, click here.

Introduction

Plone's extensive use of CSS gives customizers a great amount of control over the appearance of a Plone site.  The quickest way to get a sense for this is to look at a Plone site with styles disabled in your browser.  In Firefox you can disable styles with "View > Page Style > No Style".  (Try this now.)  Clearly, CSS is very heavily used which makes for an excellent separation of form and content.

Custom styling of a Plone site can be performed in one of the following ways:

  1. modifying 'base_properties' 
  2. overriding existing style by adding styling information to ploneCustom.css
  3. adding, deleting or reordering stylesheets

This tutorial will describe techniques 1 and 2.

Note that serious customizations of the Plone interface are best done by creating custom products.  These allow you to encapsulate all of your style and template changes in one place, save them and reapply them elsewhere.  The instructions here apply to 'quick and dirty' customizations for an individual site.

Navigating the ZMI

The following folders in the ZMI (Zope Management Interface) allow you control what stylesheets are used and their contents:

ZMI > portal_CSS
Controls the registration and ordering of stylesheets within Plone. 
 ZMI > portal_skins > custom
 Location for locally customized versions of the stylesheets found in ZMI > portal_skins > plone_styles.
ZMI > portal_skins > plone_styles
Location of default base_properties and stylesheets.

Simple Customizations

Enabling development mode

Before beginning any CSS customization you should turn on debug-/development mode.  This will guarantee that caching and compression of CSS is disabled.

Here is how you enable debug-/development mode:

  1. log on to your Plone site as the 'admin' user or with your manager account
  2. add '/manage' to the URL to access the ZMI
  3. navigate to ZMI > portal_css
  4. click the Debug/development mode checkbox
  5. click the "Save" button

When you are finished making your CSS modifications you should disable debug-/development mode as it does impact the performance of your Plone site.

Modifying base_properties

Plone provides a set of base_properties that control some of Plone's color, font, logo and border defaults.  These properties allow you to modify the basic look-and-feel of a site without working directly with Plone's CSS files and provides the simplest way to do basic customization.  Individual property names are reasonably self-explanatory (linkColor, borderStyle, etc.) and accept standard CSS style values.

Here is how you modify the base_properties:

  1. enable development mode
  2. navigate to ZMI > portal_skins > plone_styles > base_properties
  3. click the Customize button
  4. modify individual properties using CSS style values
  5. click the "Save Changes" button (at the bottom of the page)

Section 3 of this tutorial provides more detailed descriptions of each of the base properties.

Making CSS modifications

The next step beyond base_property modifications is overriding Plone's CSS with custom CSS of your own.  Plone provides the ploneCustom.css stylesheet for site customizations.  The difficult part of this for newcomers to plone is figuring out the CSS selectors that are used within Plone.

For many people, the Firebug or Web Developer Firefox extensions provide the easiest way to inspect the style associated with HTML elements in a web page.  Either of these provides easy access to the CSS selectors and style information needed to create a custom stylesheet.

Note that the .css files in ZMI > portal_skins > plone_styles are actualy dtml templates, meaning that they can utilize base_properties to make global changes via variables.  This means that they may contain references to base_properties alongside standard CSS as in the following example from public.css:

h1, h2 {
    border-bottom: &dtml-borderWidth; &dtml-borderStyle; &dtml-globalBorderColor;;
    font-weight: normal;
}d

Here is how you add CSS customizations to your Plone site:

  1. enable development mode
  2. navigate to ZMI > portal_skins > plone_styles > ploneCustom.css
  3. click the Customize button
  4. add CSS
  5. click the "Save Changes" button (at the bottom of the page)

Sections 4 and 5 of this tutorial describe Plone's stylesheets and the CSS selectors associated with various elements of the Plone interface.

Restoring default styling

When you first start playing with base_properties and stylesheets you will want the freedom to make lots of changes knowing that you can easily get back to the default settings.  Plone makes this easy by always keeping custom versions of base_properties and stylesheets in a separate folder. When Plone assembles the CSS stylesheets it looks for custom versions first and uses these when found.  If custom versions are not found it uses the default versions.  To restore default settings you only need to delete the custom versions.

Here is how you restore default styling to your Plone site:

  1. enable development mode
  2. navigate to ZMI > portal_skins > custom
  3. check base_properties and/or ploneCustom.css (or anything else you've modified)
  4. click the "Delete" button

2.3.2.2. CSS Customization Examples

Examples of Plone 3.0.x customization through base_properties and CSS.

The following examples are provided to get you started making style changes to your site.  They are not intended to be complete examples.  In each case we will take an existing website to use as our target and make change that imitate the target style.  Finishing up the styling is left as an exercise for the student.

Example 1:  'Austin Neon' style

One of the easiest ways to see what the base_properties control is to create a 'dark' style for your site.  As an example of where this style is appropriate we will use Austin Neon as our target site.  As always, the Firebug extension for Firefox is invaluable for inspecting the style in our target site.  If you haven't already, please install and familiarize yourself with Firebug before attempting to discover how the target site is styled.

Initial base_properties settings

The first step will be to modify our Plone site's base_properties as outlined in section 1.  The following screenshot shows base_properties that come close to mimicing the colors that are found in our target site.  Go ahead and make these changes to your base_properties now. 

neon_base_properties

Further styling with CSS

TODO

Example 2:  'New York Times' style

Anyone wish to make a contribution here?

2.3.2.3. base_properties

Description of all base_properties in Plone 3.0.x

The following list of base_properties are used for styling throughout Plone.  They can be edited through the ZMI at

ZMI > portal_skins > plone_styles > base_properties

Plone 3.0.x base_properties

logoName
the file name of the portal logo
fontFamily
the font family used for all text that is not headers
fontBaseSize
the base font size that everything is calculated from
fontColor
the main font color
fontSmallSize
used for various elements like buttons and discreet text
discreetColor
the font color of discreet text
backgroundColor
the background color
linkColor
the color used on normal links
linkActiveColor
color used on active links
linkVisitedColor
color used on visited links
borderWidth
the width of most borders in Plone
borderStyle
the style of the border lines, normally solid
borderStyleAnnotations
style of border lines on comments etc
globalBorderColor
the border color used on the main tabs, the portlets etc
globalBackgroundColor
background color for the selected tabs, portlet headings etc
globalFontColor
the color of the font in the tabs and in portlet headings
headingFontFamily
font family for h1/h2/h3/h4/h5/h6 headlines
contentViewBorderColor
the content view tabs border color
contentViewBackgroundColor
the content view tabs background color
contentViewFontColor
the font color used in the content view tabs
inputFontColor
the font color used for input elements
textTransform
whether to lowercase text in portlets, tabs etc.
evenRowBackgroundColor
the background color of even rows in listings
oddRowBackgroundColor
the background color of even rows in listings
notifyBorderColor
border color of notification elements like the status message, the calendar focus
notifyBackgroundColor
background color of notification elements like the status message, the calendar focus
lpBackgroundColor
background color of information pop-ups (currently not used)

2.3.2.4. Default CSS Stylesheets

Description of the default CSS stylesheets in Plone 3.0.x.

Plone's default stylesheets are described below along with list of the CSS selectors defined in each.  The stylesheets are presented in the same order they are defined in ZMI > portal_css so that style defined in stylesheets lower on the page will override style defined in earlier stylesheets.

member.css

Styles for workflow states of logged in users.

.state-private { ... }
.state-visible { ... }
.state-published { ... }
.state-pending { ... }
.state-expired { ... }
.syndicated { ... }

RTL.css

Right-to-Left styles for Arabic and Hebrew.

base.css

Styles for base elements (HTML tags).

body { ... }
table { ... }
a { ... }
img { ... }
p { ... }
p img { ... }
hr { ... }
h1, h2, h3, h4, h5, h6 { ... }
h1 a{ ... }
h1 { ... }
h2 { ... }
h3 { ... }
h4 { ... }
h5 { ... }
h6 { ... }
ul { ... }
ol { ... }
li { ... }
dt { ... }
dd { ... }
abbr, acronym, .explain { ... }
abbr .explain { ... }
q { ... }
blockquote { ... }
code, tt { ... }
pre { ... }
ins { ... }
del { ... }

public.css

Lots of styles for public-facing elements.

/* Accessibility elements, applied by JS */
body.largeText { ... }
body.smallText { ... }

/* Padding for the columns */
#portal-column-one .visualPadding { ... }
#portal-column-two .visualPadding { ... }

/* Content area */
h1, h2 { ... }
body.kssActive h2.inlineEditable:hover, body.kssActive h1.inlineEditable:hover { ... }
h3, h4, h5, h6 { ... }

.documentFirstHeading { ... }
.documentContent { ... }
.documentContent ul { ... }
.documentContent ol { ... }

/* Links with differently colored link underlines - only for content */
.documentContent p a { ... }
.documentContent p a:visited { ... }
.documentContent p a:active { ... }
#content a:target { ... }
.documentContent li a { ... }
.documentContent li a:visited { ... }
.documentContent li a:active { ... }
.documentContent dd a { ... }
.documentContent dd a:visited { ... }
.documentContent dd a:active { ... }
/* End links */

#visual-portal-wrapper { ... }

/* Logo properties */
#portal-logo img { ... }

/* The skin switcher at the top, only shows up if you have multiple skins available */
#portal-skinswitcher { ... }
#portal-skinswitcher a { ... }
#portal-top { ... }

/* Site-wide action menu - font size, contact, index, sitemap etc */
#portal-siteactions { ... }
#portal-siteactions li { ... }
#portal-siteactions li a { ... }
#portal-siteactions li.selected a { ... }
#portal-siteactions li a:hover { ... }

/* Searchbox style and positioning */
#portal-searchbox { ... }
#portal-advanced-search { ... }
#portal-advanced-search a { ... }

/* Search results elements */
dl.searchResults dt { ... }
form.searchPage { ... }
input.searchPage { ... }
form.searchPage input.searchButton { ... }

/* LiveSearch styles */
.LSRes { ... }
#LSHighlight, .LSHighlight { ... }
.LSRow { ... }
.LSRow a { ... }
.LSDescr { ... }
.LSResult { ... }
.LSShadow { ... }
.livesearchContainer { ... }
* html .livesearchContainer { ... }
#livesearchLegend { ... }
* html #livesearchLegend { ... }

/* Workaround for Internet Explorer's broken z-index implementation */
.LSIEFix { ... }
.LSBox { ... }
#LSNothingFound { ... }
.LSBox label { ... }

/* The global section tabs. */
#portal-globalnav { ... }
#portal-globalnav li { ... }
#portal-globalnav li a { ... }
#portal-globalnav li.selected a { ... }
#portal-globalnav li a:hover { ... }
/* Bar with personalized menu (user preferences, favorites etc) */
#portal-personaltools { ... }
#portal-personaltools .portalUser { ... }
/* Used on all descriptions relevant to those not logged in */
#portal-personaltools .portalNotLoggedIn { ... }
#portal-personaltools li { ... }
#portal-personaltools li a { ... }
#portal-personaltools .visualIconPadding { ... }
.visualCaseSensitive { ... }
#portal-languageselector { ... }
#portal-languageselector li { ... }
/* The path bar, including breadcrumbs and add to favorites */
#portal-breadcrumbs { ... }
#portal-breadcrumbs a { ... }
.breadcrumbSeparator { ... }
.addFavorite { ... }
.documentEditable { ... }
#content-news h1 { ... }

/* Only h5/h6 headlines in the content area should have the discreet color */
#content h5, #content h6 { ... }
.newsItem { ... }
.newsImage { ... }
.newsImageContainer { ... }
.newsContent { ... }
.newsContent ul, .newsContent li { ... }
.newsAbout { ... }
.newsAbout li { ... }
.newsFooter { ... }
.newsFooter li { ... }
.documentActions { ... }
.documentActions ul { ... }
.documentActions li { ... }
.documentActions a { ... }

/* Status messages */
dl.portalMessage { ... }
dl.portalMessage a { ... }
dl.portalMessage dt { ... }
dl.portalMessage dd { ... }
dl.warning dt { ... }
dl.error dt { ... }
dl.warning dd { ... }
dl.error dd { ... }

/* The summary text describing the document */
.documentDescription { ... }
.documentByLine { ... }
dl.searchResults span.documentByLine { ... }
#category ul { ... }
#category ul li { ... }
.discussion { ... }
.even { ... }
.odd { ... }
.visualHighlight { ... }
.discreet { ... }
.pullquote { ... }
.callout { ... }
.notify, .documentEditable * .notify { ... }
.card { ... }
.card a { ... }
.portrait { ... }
.portraitPhoto { ... }

/* The table used for listings - horizontal and vertical variants */
table.listing, .stx table { ... }
table.listing th, .stx table th { ... }
table.listing .top { ... }
table.listing .listingCheckbox { ... }
table.listing td, .stx table td { ... }
table.listing a { ... }
table.listing a:hover { ... }
table.listing img { ... }
table.listing td a label, .stx table td a label { ... }

/* Vertical addition class */
table.vertical { ... }
table.vertical th { ... }
table.vertical td { ... }

/* grid addition class */
table.grid td { ... }

/* plain table class with light gray borders */
table.plain, table.plain td, table.plain th { ... }

/* Batch selector */
.listingBar { ... }
.listingBar span.previous, .listingPrevious { ... }
.listingBar span.next, .listingNext { ... }
.listingBar img { ... }
.listingBar a { ... }
.tileItem { ... }
.tileHeadline { ... }
.tileHeadline a { ... }
.tileBody { ... }
.tileImage { ... }
.eventDetails { ... }

/* Useful deviations from regular style on elements */

/* List classes without markers */
ul.visualNoMarker, ol.visualNoMarker { ... }
ul.discreet { ... }
textarea.proportional { ... }
.productCredits { ... }
#portal-footer { ... }
#portal-footer p { ... }
#portal-footer a { ... }
#portal-footer a:visited { ... }
#portal-footer a:hover { ... }
#portal-colophon { ... }
#portal-colophon ul { ... }
#portal-colophon ul li { ... }
#portal-colophon ul li a { ... }

.feedButton { ... }
.poweredBy { ... }

/* Sitemap styles */
#portal-sitemap { ... }
#portal-sitemap a { ... }
#portal-sitemap a:hover { ... }
#portal-sitemap .navTreeLevel1 { ... }
#portal-sitemap .navTreeLevel2 { ... }

/* Album view classes */
.photoAlbumEntry { ... }
.photoAlbumEntry img { ... }
.photoAlbumEntryWrapper { ... }
.photoAlbumEntry a { ... }
.photoAlbumFolder { ... }
.photoAlbumEntryTitle { ... }

/* Link types */
a.link-parent { ... }
#content .link-category { ... }
#content .link-user { ... }
#content .link-comment { ... }
#content .link-anchor { ... }
#content .link-presentation { ... }
#content .link-wiki-add { ... }

/* Handling external/internal links, we first set the icon on all links, then
remove it from the ones that are local - for both http and https */
#content a[href ^="http:"], #content a.link-external { ... }
#content a[href ^="https:"], #content a.link-https { ... }
#content a[href ^="&dtml-portal_url;"] { ... }

/* Protocol-specific links */
#content a[href ^="mailto:"], #content a.link-mailto { ... }
#content a[href ^="news:"], #content a.link-news { ... }
#content a[href ^="ftp:"], #content a.link-ftp { ... }
#content a[href ^="irc:"], #content a.link-irc { ... }
#content a[href ^="callto:"], #content a.link-callto { ... }
#content a[href ^="webcal:"], #content a.link-webcal { ... }
#content a[href ^="feed:"], #content a.link-feed { ... }

#content .link-plain { ... }

/* For ghosted elements */
.visualGhosted { ... }

/* Fullscreen */
body.fullscreen #portal-logo, body.fullscreen #portal-siteactions { ... }
body.fullscreen #portal-globalnav { ... }
body.fullscreen #portal-searchbox { ... }

/* Kupu image alignment classes */
.image-left { ... }
.image-inline { ... }
.image-right { ... }
dd.image-caption { ... }
dl.captioned { ... }

/* Dashboard */
#dashboard-info-message { ... }
#dashboard { ... }
#dashboard-portlets1,
#dashboard-portlets2,
#dashboard-portlets3 { ... }
#dashboard-portlets4 { ... }
#dashboard-portlets1 a,
#dashboard-portlets2 a,
#dashboard-portlets3 a,
#dashboard-portlets4 a { ... }
#dashboard-portlets1 dl.portlet,
#dashboard-portlets2 dl.portlet,
#dashboard-portlets3 dl.portlet,
#dashboard-portlets4 dl.portlet { ... }
div.managedPortlet.portlet { ... }
#dashboard select { ... }
.portletAssignments { ... }
#dashboard-portlets1 div.managedPortlet a,
#dashboard-portlets2 div.managedPortlet a,
#dashboard-portlets3 div.managedPortlet a,
#dashboard-portlets4 div.managedPortlet a { ... }
#dashboard-portlets1 div.managedPortlet span a,
#dashboard-portlets2 div.managedPortlet span a,
#dashboard-portlets3 div.managedPortlet span a,
#dashboard-portlets4 div.managedPortlet span a{ ... }
#dashboard-actions { ... }
#dashboard-actions ul { ... }
#dashboard-actions ul li { ... }
#dashboard-actions ul li.portalUser { ... }

/* manage portlets */
.section div { ... }

columns.css

Styles for table-based columns also known as "left slot", "right slot", etc.

#portal-columns { ... }
#portal-column-one { ... }
#portal-column-content { ... }
#portal-column-two { ... }
body.fullscreen #portal-column-one, body.fullscreen #portal-column-two { ... }
body.fullscreen #portal-column-content { ... }

authoring.css

Styles associated with authoring elements visible to content providers.

/* Editable border */
.contentViews { ... }
.contentViews li { ... }
.contentViews li a { ... }
.contentViews .selected a { ... }
.contentViews li a:hover { ... }
.configlet .contentViews { ... }

/* begin ECMAScript Content Action Menus */
.contentActions { ... }
.contentActions ul, .contentActions li { ... }
.contentActions li { ... }
.contentActions a { ... }
.contentActions span.subMenuTitle { ... }
.contentActions a span.subMenuTitle { ... }
.actionMenu { ... }
.actionMenu .actionMenuHeader { ... }
.actionMenu.activated .actionMenuHeader { ... }
.actionMenu .actionMenuHeader a { ... }
.arrowDownAlternative { ... }
.actionMenu .actionMenuContent { ... }
.actionMenu.activated .actionMenuContent { ... }
.actionMenu.activated .actionMenuContent { ... }
.actionMenu.deactivated .actionMenuContent { ... }
.actionMenu .actionMenuContent ul { ... }
.actionMenu .actionMenuContent li { ... }
.actionMenu .actionMenuContent li a { ... }
.actionMenu .actionMenuContent .selected { ... }
.actionMenu .actionMenuContent li a:hover { ... }
.actionMenu .actionMenuContent .actionSeparator a { ... }
#templateMenu li a { ... }
/* end ECMAScript Content Action Menus */

ul.configlets { ... }
ul.configlets li { ... }
ul.configlets li a { ... }
ul.configlets li a:visited { ... }
ul.configlets li a:active { ... }
ul.configlets li label { ... }
ul.configletDetails { ... }
ul.configletDetails li { ... }
ul.configletDetails li a { ... }
ul.configletDetails li label { ... }

/* Additional STX workaround classes */
.stx table p { ... }
.stx table { ... }
.stx table td { ... }

.reviewHistory { ... }
.comment { ... }
.comment h1, .comment h2, .comment h3, .comment h4, .comment h5, .comment h6 { ... }
.comment h3 a { ... }
.commentBody { ... }
.spacer { ... }

/* Collapsible elements */
dl.collapsible { ... }
dl.collapsible dt.collapsibleHeader { ... }
dl.collapsible dd.collapsibleContent { ... }

/* for IE the following isn't needed, that's why the css2 selector is used */
dl.collapsible dd.collapsibleContent > dl { ... }
dl.expandedInlineCollapsible dt.collapsibleHeader, dl.expandedBlockCollapsible dt.collapsibleHeader { ... }
dl.collapsedBlockCollapsible { ... }
dl.collapsedBlockCollapsible dt.collapsibleHeader { ... }
dl.collapsedInlineCollapsible dd.collapsibleContent, dl.collapsedBlockCollapsible dd.collapsibleContent { ... }
dl.collapsedInlineCollapsible { ... }
dl.collapsedInlineCollapsible dt.collapsibleHeader { ... }

.configlet .documentEditable { ... }
.documentEditable .documentContent { ... }
.label { ... }
.optionsToggle { ... }

#portal-column-content fieldset > * input:focus, #portal-column-content fieldset > * textarea:focus { ... }

.highlightedSearchTerm { ... }
dl.searchResults .highlightedSearchTerm { ... }
.noInheritedRoles { ... }
.currentItem { ... }
tr.dragging td { ... }
.draggingHook { ... }
.notDraggable { ... }

.managePortletsLink { ... }
ul.formTabs { ... }
li.formTab { ... }
li.formTab a { ... }
li.formTab a { ... }
li.firstFormTab a { ... }
li.lastFormTab a { ... }
li.formTab a.selected { ... }
li.formTab a:hover { ... }
li.formTab a.notify { ... }
li.formTab a.required span { ... }
li.formTab a.notify:hover { ... }
.formPanel { ... }
.formPanel.hidden { ... }
div.formControls input.hidden { ... }

portlets.css

Styles associated with components of individual portlets.

/* Main portlet elements */
.portlet { ... }
.portlet a { ... }
.portlet a.tile { ... }

.portletItem a:visited, .portletFooter a:visited { ... }
.portletHeader { ... }
.portletHeader a { ... }
.portletItem { ... }
.portletItem ol { ... }
.portletItemDetails { ... }
.portletFooter { ... }

/* Elements that enable layout with rounded corners */
.portletTopLeft { ... }
.portletTopRight { ... }
.portletBottomLeft { ... }
.portletBottomRight { ... }

/* Calendar elements - used in the calendar rendering */
.dayPopup { ... }
.date { ... }
.portletCalendar { ... }
.portletCalendar dt { ... }
.portletCalendar dd { ... }
.portletCalendar a { ... }
.portletCalendar a:hover { ... }
.ploneCalendar { ... }
.ploneCalendar td { ... }
.ploneCalendar .weekdays th { ... }
.ploneCalendar .event { ... }
.ploneCalendar .todayevent { ... }
.ploneCalendar .todaynoevent { ... }

.managePortletsLink { ... }
div.portlets-manager div.section { ... }
div.managedPortlet { ... }
.managedPortlet .portletHeader { ... }
.managedPortlet a { ... }
.managedPortletActions { ... }
.managedPortletActions a { ... }
.managedPortletActions a.up,
.managedPortletActions a.down { ... }
.managedPortletActions a.delete { ... }

/* Table of Contents styling - essentially a portlet with smaller fonts and aligned right + limited in width */
.toc { ... }

controlpanel.css

Styles associated with the Plone control panel.

.inlineDisplay { ... }

table.controlpanel-listing { ... }
table.controlpanel-listing td, table.controlpanel-listing th { ... }
table.controlpanel-listing dl { ... }
table.controlpanel-listing dd { ... }
table.controlpanel-listing dl dt a .trigger{ ... }
table .controlpanel-listing td { ... }
table.controlpanel-listing td.checker{ ... }
table.controlpanel-listing th.smallcolumn { ... }

.chooser-right { ... }

.rule-element { ... }
.rule-element dl { ... }
.rule-element dl dd { ... }
.rule-updown, .rule-operations { ... }

print.css

Print styles for CSS2-capable browsers.  Much of this stylesheet has to do with hiding components that an inappropriate for printed documents.

deprecated.css

Styles for deprecated elements that will disappear from plone in a future release.

navtree.css

Styles associated with the navigation tree.

.portletNavigationTree { ... }
.navTree { ... }
.navTree li { ... }
.navTreeItem { ... }
.navTreeItem a, dd.portletItem .navTreeItem a { ... }
.navTreeItem a:hover, dd.portletItem .navTreeItem a:hover { ... }
.navTreeCurrentItem { ... }
li.navTreeCurrentItem { ... }
li.navTreeCurrentItem a, li.navTreeCurrentItem a:hover { ... }

.navTreeLevel0 { ... }
.navTreeLevel1 { ... }
.navTreeLevel2 { ... }
.navTreeLevel3 { ... }
.navTreeLevel4 { ... }
.navTreeLevel5 { ... }

invisibles.css

Styles for invisible and accessibility elements.

/* List classes without markers */
ul.visualNoMarker, ol.visualNoMarker { ... }
.visualOverflow { ... }
.visualOverflow pre, .visualOverflow table, .visualOverflow img { ... }

/* Accessibility and visual enhancement elements */
.hiddenStructure { ... }
.contentViews .hiddenStructure, .contentActions .hiddenStructure { ... }
.hiddenLabel { ... }

/* Helper element to work with CSS floats */
.visualClear { ... }

/* Hiding helper elements for old browsers */
.netscape4 { ... }

forms.css

Styles associated with forms.

textarea { ... }
input { ... }

input[type=checkbox] { ... }

#searchGadget { ... }

button { ... }
select { ... }
form { ... }
fieldset { ... }
legend { ... }
label { ... }
optgroup { ... }
option { ... }
optgroup > option { ... }

dl.enableFormTabbing dd { ... }

#login-form { ... }
#login-form .field { ... }
#login-form input { ... }
#login-form input.context { ... }

#forgotten-password { ... }

.standalone, .documentEditable * .standalone { ... }
.context, .formControls .actionButtons .button, .documentEditable * .context { ... }
.destructive, .documentEditable * .destructive { ... }
input.searchButton { ... }
.searchSection { ... }
.searchSection label:hover { ... }

/* The edit form elements */
.field { ... }
.field .field { ... }
.fieldRequired { ... }
.fieldUploadFile { ... }
.fieldTextFormat { ... }
.formHelp { ... }
.formHelp:hover { ... }
div.error { ... }
.error .fieldRequired { ... }

/* Styles to make the editing widgets look more like their view counterparts */
#archetypes-fieldname-title input, input#form\.title { ... }
#archetypes-fieldname-description textarea, textarea#form\.description { ... }
input.inputLabelActive { ... }

textarea#form\.description { ... }

tr.selected { ... }

.kupu-save-message { ... }

ploneKss.css

Not accessible from ZMI > portal_skins > plone_styles.

ploneCustom.css

This is where your locally modified styles should go.  By default, no styles are defined in this stylesheet.

kupustyles.css

Not accessible from ZMI > portal_skins > plone_styles.

kupuplone.css

Not accessible from ZMI > portal_skins > plone_styles.

kupudrawerstyles.css

Not accessible from ZMI > portal_skins > plone_styles.

2.4. Skin Layers

Skin layers

2.4.1. Skin Layers

Templates, scripts, images, CSS and JavaScript files are organized with skin layers.

Note: in the context of components, "layer" has a slightly different meaning.

A skin is comprised of a series of skin layers. On the file system, each layer is a directory. In the Zope Management Interface (ZMI), each layer appears in portal_skins as a separate folder (containing page templates, style sheets or Python scripts).

These have two uses.

  • Firstly they keep things organized. If you take a look at the Plone Default Skin (part of which is shown in portal_skins in the screenshot above) you'll see that they've separated out templates, scripts, styles and images into separate skin layers.
  • More importantly they have an order of precedence. This means that an item named main_template in the top layer will be found and used before an item named main_template in the bottom layer. We will go into this in more detail on the next page.

To create a skin layer through the web, simply add a new folder. On the file system, add a directory to your skins directory. You will also need to add a small amount of configuration to ensure that your directory is found and registered as a skin layer on installation.

Firstly, in [your theme package]/skins.zcml

<cmf:registerDirectory
       name="[Your Skin Directory Name]"/>

Next, in [your theme package]/profiles/default/skins.xml

<object name="[Your Skin Directory Name]"
    meta_type="Filesystem Directory View"
    directory="[your namespace].[your theme name]:skins/[Your Skin Directory Name]"/>

and

<skin-path name="[your skin name]" based-on="Plone Default">
  <layer name="[Your Skin Directory Name]"
     insert-after="custom"/>
 </skin-path>

 

2.4.2. Customizing through Order of Precedence

How skin layers work and how they can be used in customization.

If you've worked with Plone 2, you'll be familiar with this type of customization. As we mentioned earlier, the order of layers in a skin determines which page templates, CSS files and Python scripts are processed first.

To inspect the order of precedence:

  • Site > Zope Management Interface > portal_skins
  • click the Properties tab

You should see the layers of the Plone Default skin listed there. Layers such as 'plone_templates' come from the main Plone theme but there will also be layers providing templates from specific add-on products (the visual editor kupu for instance).

screenshot of Skin layers in the ZMIWhen asked to process a specific template, Plone will work from the top of this list downwards, looking in each layer in turn to retrieve the template.

At the top is a custom layer; any template placed in here will be found and used first. So, to create your own version of a Plone template or CSS file, give it the same name as the Plone version but put it in the custom layer.

This is the simplest approach, but just ensuring that your version lives in a layer higher in the order of precedence in a skin than the main Plone theme layers will be enough to ensure that Plone finds it first and ignores the original version.

This technique can be used in two ways

using the custom folder
through the Zope Management Interface, you can add your own versions of templates, style sheets etc to the custom folder. This always comes at the top, so you can be sure your versions will be found first.
adding your own skin layers
in your own theme product on the file system, create one or two skin layers, and ensure that on installation these layers are put just below the custom folder in the order or precedence. There's more information on how to do this in the next section.

Probably the most comprehensive description of skins, layers and order or precedence can be found in the first two sections of Chapter 7 of The Definitive Guide to Plone (note that most of this book refers to Plone 2, but these sections are still relevant for Plone 3).

2.4.3. Making and Naming your own Skin

How do you actually create a Skin?

Through the ZMI

  • Go to Site Setup > Zope Management Interface > portal_skins
  • Click the Properties tab
  • Choose Add New and give your skin a name
  • You can now type in a list of the layers you want to use, in the order you want to use them
  • Finally, at the bottom of the page, set your new skin as the default

On the File System

If you use the plone3_theme paster template, code will be provided which, when your theme product is installed, will register your skin directories as skin layers and put these together into a new skin.

The paster template gives you the option of basing your skin on Plone Default. That is, when you install the theme in your site, the Plone skin layers will be added to yours - but below yours in the order of precedence. This is a good idea, you can then re-use bits of Plone Default without duplicating it, and overwrite the bits you don't want.

The key steps are:

  1. Register your skin directories as Filesystem Directory Views, so that they can become skin layers. This happens in two places:[your theme package]/skins.zcml and [your theme package]/profiles/default/skins.xml
    <cmf:registerDirectory
           name="[Your Skin Directory Name]"/>
    
    <object name="[Your Skin Directory Name]"
        meta_type="Filesystem Directory View"
        directory="[your namespace].[your theme name]:skins/[Your Skin Directory Name]"/>
  2. Add and organize your skin layers into a skin in [your theme package]/profiles/default/skins.xml
    <skin-path name="[your skin name" based-on="Plone Default">
      <layer name="[Your Skin Directory Name]"
         insert-after="custom"/>
     </skin-path>
  3. Set your skin as the default skin in [your theme package]/profiles/default/skins.xml by wrapping this node around the nodes in the previous two examples.
    <object name="portal_skins" allow_any="False" cookie_persistence="False"
       default_skin="[your skin name]">
        .........
    </object>
    

 About the Skin Name

The name of your skin is required in a few places in your theme product. It is worth knowing where and why, so, for reference, the occurrences are listed here.

Where

Attributes/Directives used

Use

profiles/default/skins.xml

<skin_path name="[your skin name]"

Used to name your set of skin layers.

profiles/default/skins.xml

<object name="portal_skins"

default_skin="[your skin name]">

Used to set your set of skin layers as the default skin.

browser/configure.zcml

<interface …

name="[your skin name]"

/>

Used to name the theme specific interface (see Components section)

profiles/default/viewlets.xml

<order manager="plone.portalfooter" skinname="[your skin name]"

>

Used to specify the theme when reordering viewlets in viewlet managers

(see Components section)

3. Components

The page furniture, viewlets, portlets, and their managers. How to make your own and where to find the bits you need.

3.1. Component Wiring and ZCML

About components and how they are wired together

diagram of a componentComponents are powerful and flexible tools in Plone 3, but a little more abstract than page templates or Python scripts. As the diagram on the right attempts to show, they are normally combinations of Python classes and page templates wired together in Zope Configuration Language (ZCML) and given a name.

There are two important things to remember about components

Components are compounds of classes, templates, interfaces, permissions etc.
To track components down you need to look in .zcml files first, locate their names, and that will lead you to the classes and templates that contribute to them.
Components come into existence when your Zope Instance is started up
Provided Zope has read the .zcml file, a component will be available to use. You can't overwrite existing components, better to create your own, reusing some of the parts.

Parts of a Component

A component comes into being via a ZCML "directive" (there's an example of one of these below). The directive will have a series of "attributes" which will point to the various parts that go into its creation. These parts have four main functions.

  1. To identify the component (in the case of a viewlet this will usually be done with a "name" attribute).
  2. To compute the information the component is supposed to display (this is usually done with a Python class, and pointed to with a "class" attribute). For example, in the case of the navigation tree, this would be working out which part of the tree should be displayed for each page.
  3. To display the information the component's class has computed (this is usually done with a page template).
  4. To restrict the display of the component. In the case of a viewlet, this could be restricting it to display only to certain logged-in users (by using the "permission" attribute) or restricting it to display only with specific content types (by using the "for" attribute).

There's more about this in the component parts section.

Zope Configuration Language (ZCML)

The Five Tutorial on WorldCookery.com will walk you through ZCML, and there are plenty of examples in tutorials on the plone documentation site.

Here's a sample ZCML directive conjuring up the presentation viewlet (which simply provides a link to a presentation version of a page):

<configure    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">  
    <browser:viewlet      
         name="plone.presentation"      
         for="Products.ATContentTypes.interface.IATDocument"      
         manager="plone.app.layout.viewlets.interfaces.IAboveContentBody"      
         class=".presentation.PresentationViewlet"      
         permission="zope2.View"      
    />
</configure>

There are three things to note:

  • Like any kind of XML, ZCML uses namespaces - watch out for these if you're writing your own ZCML file. For theme components, you'll mostly use the browser namespace.
  • ZCML attributes often refer to interfaces rather than actual content types, classes or components (see the for and manager attributes in the example above). You'll find more about interfaces in a later section.
  • Look at the class attribute and you'll see it begins with a leading dot. This means you can find it in the same directory as the ZCML file itself. If it isn't within the same directory you'll need to give the full name.

You can get detailed information about ZCML directives in the ZCML Reference section of the Zope 3 API - http://apidoc.zope.org/++apidoc++/. If you want to be very disciplined and tidy, consult the ZCMLStyleGuide http://wiki.zope.org/zope3/ZCMLStyleGuide.

 

3.2. Viewlets, Portlets and Other Components

Types of component.

Viewlet

This is a new feature in Plone 3 and is used to provide aspects of the page furniture - those elements of the page which generally don't change throughout the site. These are organized by another type of component - a Viewlet Manager.

For more information you can look at

Portlet

Portlets in Plone are boxes of information, usually in the right or left column of a page, containing aggregated content or additional information, which may or may not be directly relevant to the content item being displayed. Behind the scenes these used to be constructed from ordinary page templates, but now, in Plone 3, they are wired together as components and are managed by another component - a Portlet Manager.

For more information take a look at:

View (Browser View)

We gave one definition of the term "view" above in the skin section. However, behind the scenes, in the context of components, View has a more technical meaning. It refers to a component which is usually made up of a Python class or a template or both and, put simply, processes the data from a content item before it reaches the page. There's a technical explanation in the Plone Developer Manual.

You'll sometimes see it referred to as BrowserView or <browser:page> and in templates you'll see a browser view's name prefaced by @@. We look at browser views again in the section on putting a page together.

Note that the term browser and the browser namespace are used to demarcate presentational components – that is, those bits of code which go to make up elements which will find their way to a web browser at some point.

Resource (Browser Resource) & ResourceDirectory

Although we've indicated that the skin and layers are the usual home of page templates, images and style sheets, it is also possible to turn them into components by registering them in ZCML. In this case you'll see them referred to like this ++resource++[resource name]. The same can be done for a directory containing templates and style sheets.

“Oh great”, I can hear you saying, “so which should I use, components or skins?” Go to the section Skin or Components? for a discussion of the pros and cons. At the time of writing we suggest the simpler option is to keep your templates, images and style sheets in your skin. We're just mentioning browser resources so that you know what they are if you encounter them.

3.3. Customizing or Creating New

You can customize through the web, but on the file system, the way to customize or create components for your theme is to wire up new ones.

Through the Web

Just as for Skins and Layers, it is possible to customize the templates used by components through the Zope Management Interface.

  • Site Setup > Zope Management Interface > portal_view_customizations

You will need to know the name of your component (plone.presentation for instance). The Elements section of this manual will help if the name isn't obvious. You can only rewrite the template, which might be limiting.

On the File System

You can achieve much more if you are building your own theme product on the file system, and in this case the approach is slightly different.

Rather than overwrite a component (as you could for skins), it is far easier to create your own version. This involves some rewiring or new wiring in your own .zcml file, but is actually simpler than it sounds.

Here's an example of the presentation viewlet - as it is used by Plone:

<browser:viewlet
      name="plone.presentation"
      for="Products.ATContentTypes.interface.IATDocument"
      manager="plone.app.layout.viewlets.interfaces.IAboveContentBody"
      class=".presentation.PresentationViewlet"
      permission="zope2.View"
      />

Imagine, for your purposes, you need to use a new class to get this viewlet as you want. In your own configure.zcml file, give it a new name and wire in your own class.

<browser:viewlet
      name="[your namespace].[your presentation viewlet]"
      for="Products.ATContentTypes.interface.IATDocument"
      manager="plone.app.layout.viewlets.interfaces.IAboveContentBody"
      class=".[your viewlet module].[your viewlet class]"
      permission="zope2.View"
      />
  • Remember that the dot in front of your class namespace indicates that it can be found in the same directory as this configure.zcml file.
  • If you're not sure where your configure.zcml file lives, consult the Where to Find What you Need page of this section.

3.4. Component Parts

Further information on some of the parts that go to make up components.

3.4.1. Interfaces and why they matter

Interfaces are a bit techie and something a non-developer would probably rather not think about. However, they are an important part of component wiring, so it is as well to know a bit about what they are and do.

Interfaces as Markers

ZCML attributes often refer to interfaces rather than actual classes - for instance the example below wires up the presentation viewlet for content types that have the IATDocument interface.

<browser:viewlet
      name="plone.presentation"
      for="Products.ATContentTypes.interface.IATDocument"
      manager="plone.app.layout.viewlets.interfaces.IAboveContentBody"
      class=".presentation.PresentationViewlet"
      permission="zope2.View"
      />

In effect this is saying that the presentation viewlet is available for any content type which is ATDocument-like or behaves like an ATDocument. So, in this case, the interface is a marker.

The convenience of this is that a content type can have one (or more) interfaces, and several content types can share the same one. If you develop a new content type and mark it with the IATDocument interface, you can use this presentation viewlet with it - no extra wiring required.

Components and Interfaces

Components themselves can be marked with an interface - the technical term is "provides". Note that in the presentation viewlet example, the viewlet manager is referred to by its interface, not its name:

 manager="plone.app.layout.viewlets.interfaces.IAboveContentBody"

To track down the actual component, look in the configure.zcml file in the same directory as the interfaces. For instance, in plone/app/layout/viewlets/configure.zcml you'll see the interface has been wired up with a Python class to create a viewlet manager component:

      <browser:viewletManager
        name="plone.abovecontentbody"
        provides=".interfaces.IAboveContentBody"
        permission="zope2.View"
        class="plone.app.viewletmanager.manager.OrderedViewletManager"
        />

How to spot an interface

It is usually fairly easy to spot a reference to an interface. By convention, their names will be prefixed with an "I", and they will live in an interface or interfaces namespace. If you investigate interfaces.py or interface.py in any egg or product, you won't find very much code, but you'll often find useful information – effectively it is documentation about what a component providing (i.e. marked by) that interface should do. For example:

class IAboveContentBody(IViewletManager):
    """A viewlet manager that sits above the content body in view templates    """

If you've used the plone3_theme paster template, you'll find you have a ready-made interfaces.py file to which you can add your own interfaces if you need to create them.

3.4.2. Python Classes

You'll have noticed that Python classes are often part of the wiring of Components, and you will find that you can't really avoid understanding a little bit about them, particularly if you want to make your own viewlets.

Having to deal with something as advanced as Python classes can be daunting for the non-developer. The good news is that using Python classes will be more a case of copying and changing little bits of code than writing anything from scratch.

What's a Class?

It's best to think of a class as a discrete piece of code containing a collection of methods ('actions' of some sort) and attributes ('variables' which can hold a value).

In the case of components, the main purpose of a class is to compute the pieces of information a component needs to display. The class for the logo viewlet is a good example. You can find it in:

  • [your egg location]/plone/app/layout/viewlets/common.py - look for LogoViewlet

After a bit of preparatory work, the LogoViewlet class first finds out the name of the image that is to be used for the logo (and is defined in the base_properties property sheet):

logoName = portal.restrictedTraverse('base_properties').logoName

Then it works out the logo's vital statistics, size, alt text etc and turns this into an HTML anchor tag:

self.logo_tag = portal.restrictedTraverse(logoName).tag()

Finally, just in case you might need it, it looks up the title of the site:

self.portal_title = self.portal_state.portal_title()

In the page template associated with this viewlet you can get hold of this information (self.logo_tag, self.portal_title) using the variable "view":

<img src="logo.jpg" alt=""
         tal:replace="structure view/logo_tag" />

Do I have to use Classes?

Viewlets tend to be wired up with a Python class which points to a template. So, even though you might only want to create a new template, you'll find that you have to write a class to point to your new template. The Elements section of this manual should help you by giving you a snippet of code for each element to copy and paste into your own product.

Here's an example. The standard logo template doesn't actually make use of view/portal_title. So if you wanted to incorporate this into your logo in some way, then you would need to write your own template and then also your own class:

from plone.app.layout.viewlets.common import LogoViewlet
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

class [your class name](LogoViewlet):
    render = ViewPageTemplateFile('[your template name]')
  • First, pull in ("import") all the bits and pieces with which to build your class using from ….. import …… .

  • Next, define your class. The important thing here is to base it on a pre-existing class so that you don't have to start from scratch. Put the name of the pre-existing class in brackets after your class name (make sure that you've imported it first). Don't forget the colon!
  • Finally, rewrite any of the methods or attributes you need. Here, we've just rewritten the render method to display our own template.

Note: indenting is very important in Python code, the convention is to use four spaces (rather than a tab). If you are having problems, double check the indentation first.

If you're feeling brave or want to know more, a straightforward introduction is here:


3.4.3. Permission

The permission attribute can be used to restrict visibility of a component.

When a user logs in to a site, they will be given a role ('manager' or 'editor' for instance). This role is, effectively, a set of permissions, giving them particular rights over particular aspects of the site.

To find out more about permissions consult the Understanding Permissions and Security Tutorial:

In the case of components, the permission attribute allows the site to decide whether a user has a right to see, or interact with a component. Most viewlets have the permission Zope2.View or Zope2.Public, which are permissions assigned to everyone, even anonymous visitors. However, look at the Lock Info viewlet:

<browser:viewlet
        name="plone.lockinfo"
        manager=".interfaces.IAboveContent"
        class="plone.locking.browser.info.LockInfoViewlet"
        permission="cmf.ModifyPortalContent"
        for="plone.locking.interfaces.ITTWLockable"
        />

By using cmf.ModifyPortalContent, this viewlet is restricted only to those who have the right to edit content (those who don't wouldn't be interested in whether an item was locked or not).

The list of available permissions is buried rather deeply in the Five product which comes with your installation of Zope - look in permissions.zcml for the most up-to-date list.

 

zope2.Public

Public, everyone can access

zope2.Private

Private, only accessible from trusted code

zope2.AccessContentsInformation

Access contents information

zope2.ChangeImagesFiles

Change Images and Files

zope2.ChangeConfig

Change configuration

zope2.ChangePermissions

Change permissions

zope2.CopyOrMove

Copy or Move

zope2.DefinePermissions

Define permissions

zope2.DeleteObjects

Delete objects

zope2.FTPAccess

FTP access

zope2.ImportExport

Import/Export objects

zope2.ManageProperties

Manage properties

zope2.ManageUsers

Manage users

zope2.Undo

Undo changes

zope2.View

View

zope2.ViewHistory

View History

zope2.ViewManagementScreens

View management screens

zope2.WebDAVLock

WebDAV Lock items

zope2.WebDAVUnlock

WebDAV Unlock items

zope2.WebDAVAccess

WebDAV access

cmf.ListFolderContents

List folder contents

cmf.ListUndoableChanges

List undoable changes

cmf.AccessInactivePortalContent

Access inactive portal content

cmf.ManagePortal

Manage portal

cmf.ModifyPortalContent

Modify portal content

cmf.ManageProperties

Manage properties

cmf.ListPortalMembers

List portal members

cmf.AddPortalFolders

Add portal folders

cmf.AddPortalContent

Add portal content

cmf.AddPortalMember

Add portal member

cmf.SetOwnPassword

Set own password

cmf.SetOwnProperties

Set own properties

cmf.MailForgottonPassword

Mail forgotten password

cmf.RequestReview

Request review

cmf.ReviewPortalContent

Review portal content

cmf.AccessFuturePortalContent

Access future portal content

 

3.5. Making Components Theme Specific

You may want to make components only available for your particular theme. To do this you will need an interface.

As components come into being as soon as Zope starts up and reads the .zcml files, they are available for every Plone site you have in a Zope instance. You might not want this to happen.

A Theme Interface

You can specify that your components are available only for your theme with a marker interface and a layer attribute in ZCML. Here's a rewired version of the presentation viewlet:

<browser:viewlet
      name="[your namespace].[your presentation viewlet]"
      for="Products.ATContentTypes.interface.IATDocument"
      manager="plone.app.layout.viewlets.interfaces.IAboveContentBody"
      class=".[your viewlet module].[your viewlet class]"
      layer=".interfaces.IThemeSpecific"
      permission="zope2.View"
      />

Note: Don't confuse the layer attribute with a skin layer. Here, layer refers to the whole theme rather than just one slice of it.

There are two methods for creating a theme interface:

Using plone.theme

In Plone 3.0, plone.theme is used:

  • A marker interface is defined in [your theme package]/browser/interfaces.py:
from plone.theme.interfaces import IDefaultPloneLayer

class IThemeSpecific(IDefaultPloneLayer):
    """Marker interface that defines a Zope 3 browser layer.    """
  • and this is registered in ZCML in [your theme package]/browser/configure.zcml
<interface
        interface=".interfaces.IThemeSpecific"
        type="zope.publisher.interfaces.browser.IBrowserSkinType"
        name="[your skin name]"
        />

Note: [your skin name] crops up here; refer back to the skins section if you are wondering what this is.

Using plone.browserlayer

In Plone 3.1, plone.browserlayer is available to you.

  • Create your interface (e.g. in [your theme package]/browser/interfaces.py)
from zope.interface import Interface
    class IThemeSpecific(Interface):
        """A layer specific to my product        """
  • Register this in the configuration (in [your theme package]/profiles/default/browserlayer.xml):
<layers>
 <layer name="[your skin name]"
   interface="[your namespace].[your theme name].browser.interfaces.IThemeSpecific"
 />
</layers>

If you generate your file system product or egg using the plone3_theme paster template, then the basics will be done for you (using the plone.theme method), you will simply need to track down the interface to find its name. Look in

  • [your theme package]/browser/interfaces.py or configure.zcml

and you should find it with the name IThemeSpecific. When you refer to it, use its path

layer=".interfaces.IThemeSpecific"

3.6. Skin or Components?

You’ll have noticed that you can turn any template or css file, or any directory containing these into a component. So why bother with the Skin building block?

The product created by the plone3_theme paster template does the following:

  • overrides and rewrites of the standard Plone Default templates and CSS files go in the Skin section – the skins directory.
  • new style sheets and images go in the Components section – the browser directory.

This manual suggests putting all your templates, style sheets and images in the Skin section - leaving just the viewlet and portlet templates in the components. There are a few reasons for this

  • it is simpler to do this when you're just starting out
  • it follows the way in which Plone Default is constructed
  • it makes it quick and easy to adjust your theme on-the-fly after it's installed. At that point, you can make further customizations of the Skin through the Zope Management Interface.

At the time of writing there's a big discussion going on about this very question.

 

If you want to strip the browser resources out of the product created by the plone3_theme paster template

  • remove the images and stylesheets directories in the [your theme package]/browser
  • remove the <browser:resourceDirectory /> entries in [your theme package]/browser/configure.zcml
  • remove the register stylesheet entry for main.css in [your theme package]/profiles/default/cssregistry.xml
  • if you have already installed your product you may need to check the CSS registry in the Zope Management Interface (portal_css) and delete the main.css entry there too

 

3.7. Where to find what you need

Where to put components in your own product and how to track them down in the Zope Management Interface and on the file system.

Through the Web

The templates for most components can be customized through the web:

  • Site Setup > Zope Management Interface > portal_view_customizations

The Elements section can help you identify the component you need.

Plone Default Components on the File system

If you're planning to wire up your own components, you may need to track down the relevant files of existing components to copy. This can be tricky. They are packaged up into a number of different eggs, so you need first to locate where your eggs are stored, and then work out which of these contains the component elements you need.

  • To work out where your eggs are stored, look at the Where is What section of this manual?
  • The Elements section of this manual will help you track down the egg containing the component you need.

In your own Theme Product

The browser folder in your theme product/browser/viewlet.py | viewlet.pt
An example viewlet component
/browser/interfaces.py
This is used to create your theme interface
/profiles/default/viewlets.xml
Use this file to order your viewlets within viewlet managers
/browser/configure..zcml
Use this file to wire up your components
/browser/templates | styles
These directories can be used for templates, styles, and images. You will need to register these as directories as resources in configure.zcml

4. Configuration

How to write a configuration file and where to put it.

4.1. Profiles

Configuration and profiles

Configuration refers to the default behaviour of a site (for instance, whether you allow people to sign up to your site, or how dates are displayed). You're likely to want some of this behaviour to be embedded in your theme.

There is also some overlap between Components, Skins, and Configuration. For instance, the order of skin layers and the order in which viewlets appear within a viewlet manager are considered aspects of configuration.

Profile

A profile is a set of configuration files. Each file is written in fairly simple XML and refers to a particular group of components or page elements. There are two different types of profile, base profiles and extension profiles. For theme purposes you will only ever need to use an extension profile (i.e., a profile that extends the configuration of an existing site).

A profile comes into being when it is wired up by ZCML. Here's the version created by the plone3_theme paster template:

<genericsetup:registerProfile
 name="default"
 title="[your skin name]"
 directory="profiles/default"
 description='Extension profile for the "[your skin name]" Plone theme.'
 provides="Products.GenericSetup.interfaces.EXTENSION"
/>

You'll see that it points at a directory for the location of the XML files and indicates that it is an extension profile by using an interface.

4.2. Generic Setup XML

The language used to define profiles.

The XML used for profile files is straightforward. There's no DTD available, but there are plenty of examples to reuse or adapt for your purposes. If all of this seems too much, the good news is that you can get Generic Setup to write the files for you by exporting the configuration from an existing site. There's more information on how to do this on the Generic Setup Tool page.

The root node of an XML profile is usually an object:

<object name="portal_javascripts" meta_type="JavaScripts Registry">
     .......
</object>

which corresponds to a particular site tool (in this case the JavaScripts registry). Sub-nodes represent sub-objects and XML attributes correspond to the attributes of those classes.

<javascript cacheable="True" compression="none" cookable="True"
            enabled="True" expression="" id="jquery.js" inline="False"/>

So, in this case, the sub-node represents an entry in the JavaScripts registry and its tick boxes.

screenshot of the javascripts registry in the ZMI

In the very unlikely event that you need to work out for yourself what attributes to use, you'll need to investigate the API (or the interfaces and classes) of the tool in question. Use http://api.plone.org or dig into the source code.

4.3. The Generic Setup Tool

The Generic Setup tool is used to apply your profiles to your site.

You can find the Generic Setup tool here

  • Site Setup > Zope Management Interface > portal_setup

You can run the tool manually, but for theme purposes, if you have created a product using the plone3_theme paster template, Generic Setup will be triggered automatically when you install your theme in your site.

You'll find more extensive information about the Generic Setup Tool in this tutorial:

However, there are two useful facts to know about it.

No Undo

Although you can uninstall your theme using portal_quickinstaller, at present, you can't undo the profiles Generic Setup applied during installation. For the most part, this isn't a problem, but it can get confusing - if, for instance, you are experimenting with the order of your viewlets and have tried several versions of viewlets.xml in successive installations. In this case, exporting a profile (explained below) can help you make sense of what you've done.

Exporting Profiles

You can export the current configuration of your site as a set of XML files. This can be helpful if you're not quite sure what you've done, if you're searching for a profile to base your own configuration on, or if you just want the Generic Setup Tool to write out a configuration for you.

  • Site Setup > Zope Management Interface > portal_setup
  • Click the Export tab
  • Select the steps you wish to export
  • Click the Export Selected Steps button
  • You'll be given a zip file with the relevant XML files

It isn't always obvious which export step you need to get the exact configuration you want, you may need to experiment.

4.4. Where to find what you need

How configuration works through the web and how to track down files on the file system.

Through the Web

There are a number of different routes to configure your site through the web. The Elements section of this manual should give you pointers on where to look to configure particular page elements. In general

  • Site Setup leads you to configlets for the site settings
  • Site Setup > Zope Management Interface will lead you to the style sheet and JavaScript registry (portal_css and portal_javascripts)
  • Adding /@@viewlet_manager to a URL will enable you to order viewlets

Plone Default Configuration on the File System

You will find most of the configuration files you need in:

  • [your products location]/CMFPlone/profiles/default

However, be aware that some configuration files may be located in third-party products. For instance, if you want to add some styles to the visual editor, Kupu, as part of your theme, then you will need kupu.xml which you'll find in [your products location]/kupu/plone/profiles/default.

There's an alternative to hunting around the file system, and that's to use the Generic Setup Tool to export the profile.

In your own Theme Product

The configuration directory in your theme product/profiles/default/
This directory holds the XML for Generic Setup. The plone3_theme paster template will have provided you with some ready-made files - for setting up your skin layers, registering your style sheets and JavaScript, and ordering your viewlets.
/profiles/default/import_steps.xml
Is an essential file for installation, you shouldn't need to change this.
/profiles/default/cssregistry.xml | jssregistry.xml
will register any style sheets and JavaScript in your skin. You will have to edit these yourself if you have any css or Javascript files to add.
/profiles/default/skins.xml
Will drop your skin layers into the right order of precedence. You won't need to change this unless you've renamed, removed, or added directories in the skins directory of your theme egg.
/profiles/default/viewlets.xml
will determine in what order viewlets appear in viewlet managers. You will need to edit this yourself if you want to add your own viewlets.
/profiles.zcml
When your Zope instance starts up, this file makes the profile available for Generic Setup to use.