Journal

4 January 2007

Building a Symphony Theme: Part 4

The series on Building a Symphony Theme has touched briefly on several concepts to make sure we understand the basics before we get into the more complex areas of developing with Symphony. You may realize by now that building a theme in Symphony really means building an entire site from the ground up. But once we have built a theme, the same templates and database structures can be used to easily build other types of sites that may have an entirely different style with very different functionality.

Every site tends to have some basic similarities. There needs to be some way to manage the static content of your site. It may not change much, but it is good to have some way to update these areas, such as the About and Contact pages. Here is where we go beyond the default parameters and data sources to actually creating content. We will start by creating a Section and creating Custom Fields for entering content.

Creating a Section and Custom Fields

For the areas of your site that contain static content, you may not need anything more than a text field for the Title and a text area for the Body. We will also need some way to associate the content with a particular page. In the interests of keeping things simple, we’ll do just that. Go to the Structure menu and select Sections.

Symphony Admin : Structure : Sections

Create a New Section

Click on the Create New button and give the section the name “Content”. It’s not likely that we’ll need comments for this type of content so we can deselect the check box beside “Enable comments for this section”. You can also choose whether you would like to display the Date and Time widget to select the Publish Date for each entry.

There are couple check boxes under the heading “Show the following columns”. This allows you to customize how the entries are listed in the Symphony Admin.

Symphony Admin : Structure : Sections : Content

View the Content Entries

For each Section that you create, a new Custom Field, named “Title”, will be created. Go to the Publish menu and you will see that a new Section has been added to the menu. Comments and File Manager will always appear at the bottom of the Publish menu. Select the Content Section from the Publish menu and you will see the column headings for the list of entries, but we have yet to create any entries.

Symphony Admin : Publish : Content

Create a New Content Entry

Click on the Create New button and you will see a text input field labeled “Title” and the “Date and Time” widget (a calendar and a list of time values as a pull-down menu) for the Publish Date. If you resize the browser window, you will notice that the “Title” input field has a fluid width, while the “Date and Time” widget maintains a fixed width.

Symphony Admin : Publish : Content : Create New

The Primary Custom Field

As I mentioned above, a Custom Field was created automatically when the new Section was created. Go to the Structure menu and select Custom Fields and you will find a list of Custom Fields. At the moment there is only one Custom Field associated with the Content Section.

Symphony Admin : Structure : Custom Fields

Click on the title of the Custom Field to edit it. The “Title” Custom Field comes with a warning message because it is a Primary Custom Field:

bq. This custom field is linked to the Content section, so you cannot delete or re-associate it, and it must use either a text input or text area field type.

It is possible to add a description that will appear to the right of the label for the Custom Field, but this is optional. It is also possible to apply validation rules for each Custom Field. By default, the Primary Custom Field is a required field.

Symphony Admin : Structure : Custom Fields : Title

Create New Custom Fields

For the Content section, we will create three new Custom Fields: Body, Page and Photo, just so we can see how it is possible to customize the placement of these fields in the Admin pages. Go to Structure : Custom Fields : Create New and give it the name, “Body”. Select “Text Area” under Field Type and give the text area a depth of 15 rows. Associate the Custom Field with the Content Section (since there is no other Section, it should be selected already), and leave the Location as “Main Content”. Click the Save button.

Symphony Admin : Structure : Custom Fields : Create New : Body

Next, create a new Custom Field called “Page” with a “Select Box” Field Type, with the Options: “Home, About, Contact, Maintenance” and a Location in the “Sidebar”.

Symphony Admin : Structure : Custom Fields : Create New : Page

Finally, create one more Custom Field called “Photo” with a “File Attachment” Field Type with “/workspace/upload/” selected as the Destination Folder and a Location in the “Drawer”.

Symphony Admin : Structure : Custom Fields : Create New : Photo

Go to Structure : Sections and click on “Content” to edit the Section. You can select the check box next to “Page” to view this column in the entries list. Now, when you go to Publish : Content, you will see the additional column (still no entries yet). Click on Create New and you will see the new customized interface you just created for the Content Section.

Symphony Admin : Publish : Content : Create New

Click on the “More Options” button to view the upload link for the Photo file attachment field.

Symphony Admin : Publish : Content : Create New : Options

Create Content

For the moment, I am going to ignore the Photo field as we start creating some basic content for the site. Now we can create an entry for each page. For my home page, I have given the page a Title: “Welcome” and selected “Home” from the pull-down menu under Page. For the Body, I have the following:

bq. Welcome to my website. I am currently in the process of developing a theme for the Symphony Web Publishing System. Please stay tuned while I assemble this website.

Symphony Admin : Publish : Content : Create New : Welcome

Create another entry for the About page, selecting “About” from the Page pull-down menu. Then create two more entries, one for the Contact page and the Maintenance page, making sure that you select the appropriate page from the Page pull-down menu for each entry. When you are finished, you should be able to go to Publish : Content to view the list of entries, showing the page that each entry is associated with.

Symphony Admin : Publish : Content

Transforming Content Data into HTML Output

To display our content, we will be retracing our steps in familiar territory, since the same process that we used for creating the navigation template for the main menu will be the same process that we will be using to transform the Content data we have just created into HTML output that we can display on our pages. The difference will be that the templates we build will be specific to individual pages rather than Master templates that apply to several pages.

The process goes something like this:

  • Create the Data Source
  • Associate the Data Source with a Master or Page Template
  • Create the XSL Template
  • Test the front end
  • Troubleshoot any problems using the ?debug information

The Data Source Editor

To view the Data Source Editor, go to Blueprints : Controllers : Data Sources : Create New. Now that we have a new Section, the New Data Source page will look a little different than when we had created the Navigation Data Source. There are a number of options available to us that were not there before. The Data Source Editor responds to the selection of different sources by dynamically offering different options. The Source pull-down menu has defaulted to the selection of the first available Data Source, which is now the Content Section.

Symphony Admin : Blueprints : Controllers : Data Sources : Content

So, what are all these options and why would you use them? Well, the XML data associated with a particular Section can become quite large and, if the XSLT template is required to process a very large amount of data, this will increase the amount of time necessary to transform the XML data into HTML output, meaning page load times will become longer than necessary. No one wants a slow site, so it is best to select only the data that is necessary to produce the desired output. Symphony makes it possible to customize the XML Data Source to fit the needs of the page by filtering the data by:

  • URL Parameters
  • Custom Field Values
  • XML Elements (as part of the Format Options)
  • Maximum Number of Records per Page
  • Page Numbers

There are also some formatting options for the XML data:

  • Entry List
  • Group by Date
  • Archive Overview

An Entry List has the following XML structure:

                  <entries section="entries">
    <entry id="102" handle="entry-title">
        <date>2007-01-03</date>
        <time>12:07</time>
        <author />
        <fields>
            <title handle="entry-title">Entry Title</title>
            <body word-count="7"><p>The body of the entry goes here.</p></body>
        </fields>
        <comments count="0" spam="0" />
    </entry>
</entries>

                

A Data Source that has a Group by Date format has the following XML structure:

                  <entries-grouped-by-date section="entries">
    <year value="2007">
        <month value="01">
            <day value="03">
                <entry id="102" handle="entry-title">
                    <date>2007-01-03</date>
                    <time>12:07</time>
                    <author />
                    <fields>
                        <title handle="entry-title">Entry Title</title>
                        <body word-count="7"><p>The body of the entry goes here.</p></body>
                    </fields>
                    <comments count="0" spam="0" />
                </entry>
            </day>
        </month>
    </year>
</entries-grouped-by-date>

                

An Archive Overview has the following XML structure:

                  <archive-overview section="entries" year-start="2006" year-end="2007">
    <year value="2007">
        <month value="01" entry-count="2" />
    </year>
    <year value="2006">
        <month value="12" entry-count="0" />
        <month value="11" entry-count="1" />
        <month value="10" entry-count="5" />
        <month value="09" entry-count="5" />
        <month value="08" entry-count="0" />
        <month value="07" entry-count="0" />
        <month value="06" entry-count="0" />
        <month value="05" entry-count="0" />
        <month value="04" entry-count="0" />
        <month value="03" entry-count="0" />
        <month value="02" entry-count="0" />
        <month value="01" entry-count="0" />
    </year>
</archive-overview>

                

The Archive Overview and Group by Date formats are intended for features such as a blog archive or listings of news articles which are sorted by date. The Entry List format works well for most applications.

Finally, the entries can be sorted by the Publish Date, either in ascending order (earliest first) or descending order (latest first). [Note to the Symphony Team: it would be amazing if it were possible to add other options for sorting the XML Data Sources, such as alphabetically, or by custom field to control how entries are listed in each Section.] XSLT offers the <xsl:sort/> element to be able to sort the data in other ways for the HTML output.

Create the Data Source

For our page content, the Entry List format will work fine. The only fields we really need are the title, body and page elements. (I won’t be displaying photos with the content, so I will not include this element in the Data Source, since I will address image handling in another tutorial.)

So, let’s go ahead and create a new Data Source with the following features:

  • Name: Content
  • Source: Content
  • Format Style: Entry List
  • Included Elements: title, body, page
  • Sort Results by: Ascending Date
  • Limit Options: remove limits (leave these fields blank)

(It is not likely that we will have more than 50 records in our Content section, so it doesn’t really matter whether we change the Limit Options or not.)

Associate the Data Source with a Master or Page Template

It is possible to associate a Data Source with a Master template and/or a Page template. If we remember back to the Navigation template, we associated the Navigation Data Source with the default Master template. Since all our pages use the default Master template, we can easily include the Content Data Source in the Data Source XML for all pages by configuring the default Master template to include the Content Data Source. Otherwise, you can modify the Page Settings by configuring each page to include the Content Data Source. Note that when a Data Source is attached to a Master template, any page the uses this Master template will have an asterisk (*) next to the name of this Data Source in the Page Settings in the select box for Data Source XML.

So, let’s go ahead and attach the Content Data Source to the default Master template by going to Blueprints : Components : Masters : default.xsl. Click on the Configure button and select “Content” in the Master Settings : Data Source XML select box and click the Save button.

We should now be able to go the home page and view the ?debug information for the page. The XML data should now contain the <content> node with four <entry> elements.

Create the XSL Template

Now, we can start creating the XSL templates for our pages. However, before we get there, I need to modify the default Master template.

In Part 3, you may have noticed that I obliterated my page heading <h2> element by turning it into a list item element for the navigation. Rather than add this back into the default Master template, we can add this code to the Page template. At the same time, I also removed the <xsl:apply-templates/> element. This is important to include in the default Master template if we want our Page templates to be included in the XSL stylesheet for each page. First, however, let’s try something else, because it will be easier than modifying each of our page templates. By adding code to the default Master template, we can affect all our pages. So, go to Blueprints : Components : Masters : default.xsl and add the following code below the <xsl:call-template name="main-menu"/> element:

                  <h2><xsl:value-of select="$page-title"/></h2>
<xsl:copy-of select="data/content/entry[fields/page/@handle=$current-page]/fields/body/*"/>

                

The default Master template should look like this:

                  <?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output
    method="xml" 
    doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" 
    doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
    omit-xml-declaration="yes"
    encoding="UTF-8" 
    indent="yes" />
<xsl:template match="/">
    <html>
        <head>
            <title><xsl:value-of select="$page-title"/></title>
            <style type="text/css">
                li.current a {color:#f00; text-decoration:none;}
            </style>
        </head>
        <body>
            <h1>
                <a href="{$root}/" title="Home Page">
                    <xsl:value-of select="$website-name"/>
                </a>
            </h1>
            <xsl:call-template name="main-menu"/>
            <h2><xsl:value-of select="$page-title"/></h2>
            <xsl:copy-of select="data/content/entry[fields/page/@handle=$current-page]/fields/body/*"/>
        </body>
    </html>
</xsl:template>
</xsl:stylesheet>

                

What just happened here? Well, we used a <xsl:copy-of/> element to select data from the XML Data Source attached to our default Master template and thereby attached to each of our pages. The “select” attribute of the xsl:copy-of element is selecting a node set based on the context node being matched by the default Master template, which is the root of the XML data, indicated by:

                  <xsl:template match="/">

                

Using XPath to find each entry node in the Content Section, we use the following relative expression:

                  select="data/content/entry"

                

But we also want to test for a specific condition: that the handle of the “Page” Custom Field for the entry we want to display matches the current page handle. So, we test each entry by using a predicate, a conditional statement enclosed in square brackets:

                  [fields/page/@handle=$current-page]

                

Once we have found the matching entry, we find the Custom Field for the Body of the entry with this expression, relative to the selected entry:

                  fields/body

                

And, since the <body> element contains child elements, such as <p> elements, we use the asterisk (*) to copy all the elements contained by the <body> element.

Why use <xsl:copy-of/> instead of <xsl:value-of/>? Symphony defaults to some sort of text formatter to ensure that text gets wrapped in some sort of HTML element, at the very least some paragraph tags: <p>. Because of this, there are child elements within the <body> element and the value of the <body> element is empty. So, <xsl:value-of/> would return no value. On the other hand, <xsl:copy-of/> will reproduce the child elements that are selected. Using the asterisk (*) will select all child elements.

We have content, but no style?

So, there we have it! A fully functioning Symphony theme. Well, it ain’t pretty, but it works. But if our site has no style, who will want to read it? And surely there are some features that are missing. And surely not all of our pages are going to look the same.

Symphony is all about content and style. It’s time to add some style to our site. Enter Cascading Style Sheets (CSS) in the next installment: Building a Symphony Theme: Part 5 - Integrating CSS and Creating the Entries Section.