Title photo
Ode is simple! (Simple means that you know how it works.)

Hello, and welcome to news.ode-is-simple.com.

This is a weblog dedicated to Ode (ode-is-simple.com) and other topics relevant to the project.

If you're looking for general info about Ode you may want to start at the project homepage at ode.simple.com/home

To stay up to date with the newest news and info related to Ode, subscribe to this site's RSS 2.0 using Google Reader or your preferred feed reader.

Posts

Mon, 29 Mar 2010

Getting to know Ode: Themes

Did you know you can maintain a live history of your themes as you make updates? Let's take a closer look at themes.

Themes

Themes completely specify the look of the site and are (that is, they really should be) standards-compliant (X)HTML and CSS. (Yes, I'm talking to you. :)

Each theme has a minimum of four components (along with any other misc resources you may want to include, e.g. CSS files, maybe an image directory, etc).

These theme components are:

  1. content_type
  2. date
  3. page
  4. page_no_posts

!--jump--!

1 - content_type._ theme file

Example: content_type.html

This file must contain one of a small number of valid content_type strings (appropriate for the page being returned).

This is used in the HTTP header and communicates crucial information to the web server.

What are these values?

They are standard strings which are used to identify the type of the associated document.

These 'type definitions' are published and maintained as recommended standards by the World Wide Web Consortium (http://www.w3.org/) and are used in combination with Content-Type entity-header field, which is part of the HTTP protocol.

Refer to the W3C's website for more information.

(Fair warning: The w3.org site is confusing, as are the RFCs and other documents describing these protocols.)

You do not need to understand how these media types work to use them. But, you do need to make sure you are using the proper media type for the documents you are serving.

Unless you know what you're doing, do not modify the content_type._ file included with any of the bundled themes.

If you are writing your own themes, start by deciding which language you want to support (e.g. HTML 4, XHTML 1) and then search for the proper media type to use.

'text/html' is appropriate for standard HTML 4.x and XHTML 1.0 (including strict) but not XHTML 1.1, which expects 'application/xhtml+xml'.

You should consider the W3C recommendations authoritative.

2 - date._ theme file

Example: date.html

The file contains a format string which is inserted between posts whenever a specified date range is exceeded, where the range is determined by the format string itself. (Keep reading.)

A simple date._ file may look like:

START OF FILE
<h3>$wkday_name, $month_day $month_name $year</h3>
END OF FILE

('START OF FILE' and 'END OF FILE' are used here to set off the content of a fictitious date._ theme file and are not part of the file itself.)

So, in this case the date._ file consists of the single line:

<h3>$wkday_name, $month_day $month_name $year</h3>
  • $wkday_name

    The value of $wkday_name is a 3 character string representing a day the week (Mon, Tue, ..., Sun).

  • $month_day,

    The value of $month_day is a 2 digit string representing a day of the month (01, 02, ..., 31)

  • $month_name

    The value of $month_name is a 3 character string representing a month name (Jan, Feb, ..., Dec)

  • $year

    The value of $year is a 4 digit string representing a year (e.g. 2009)

Whenever the modification date of a post differs, in any way, from the date of the previous post, a new date string will be inserted between the two.

Notice that the smallest measure of time included in this format string is:

either $wkday_name or $month_day.

Two posts generated on the same day will _not_ have a date string between them, because both of those posts would share the same day of the week ($wkday_name), day of the month ($month_day), month ($month_name), and year ($year).

However, if the date string looked something like

START OF FILE
<h3>$wkday_name, $month_day $month_name and $sec seconds</h3>
END OF FILE

a date would be inserted between any two posts with modification times that differed by as little as a second. Practically speaking, a date-string would be inserted between every two consecutive posts.

This is a pretty unlikely format string, which would result in something like the following string being inserted between every two consecutive posts:

Thu, 12 Jan and 43 seconds

3 - page._

Example: page.html

page._ is a single, fairly typical, (X)HTML page with a few important exceptions (none of which should bother your HTML editor or development applications).

The page._ file must be divided into three sections:

  • head
  • posts
  • foot

**IMPORTANT**: This is accomplished by inserting special HTML comments at two points in the page file to mark the boundaries between sections:

These comments are defined in the config file using:

$page_delimiter_head_posts
$page_delimiter_posts_foot

The posts section should include all of the literal text and uninterpolated variables necessary to generate the actual content of the site.

This section is repeated once for each post displayed.

4) page_no_posts._

Example: page_no_posts.html

Everything said for the page._ file also applies to page_no_posts._.

For every request, the routine chooses one of page._ or page_no_posts._, and pulls the head, posts, and foot theme components from that file.

page_no_posts._ is used whenever the number of posts matching the current request (i.e. the value of $num_posts_s, which is passed into the routine from the caller) is zero (0).

page._ is used in every other case.

page_no_posts._ should be a drop in substitute for page._, so like page._, it too must be divided into three sections:

  • head
  • posts
  • foot

Read the description for page._ above for more info.

Though the basic structure of the page must be the same, theme designers are free to design the look and layout of a no_posts page however they see fit. The file might be exactly the same, or it might be entirely distinct.

Variable interpolation in themes

The script supports variable interpolation in themes, so variables such as $config::site_title, in theme files are replaced by the corresponding values.

This simple variable interpolation is fundamentally important to Ode. This is what allows the script to build a page from a collection of discrete post files. (Keep reading.)

Think of themes as templates.

Themes usage

Themes are stored alongside posts under the site's document root.

In addition to the document root itself, any subdirectory under the site's document root may contain a 'themes' directory.

Each of these directories may contain one or more subfolders, each dedicated to an individual theme.

So, the picture is this:

There may be any number of 'themes' directories scattered throughout the site's content directories. (As many as one 'themes' folder per directory.)

Each of these 'themes' folders may contain any number of folders defining individual themes.

There is no limit on the number of individual themes per 'themes' directory, except that no two directory entries at the same path can share the same name. (You will probably recognize that this is not a limitation of the script, but a fundamental characteristic of the structure of the underlying filesystem).

The general idea is that themes which are closer to the request will always override themes with the same name higher in the content hierarchy, (i.e. further from the request).

This simple design allows us to define different themes for different segments of the site and then apply them in a way that is consistent with the natural organization and categorization scheme for the content of an Ode site.

For example let's say that the following is the partial structure of an Ode site.

/themes/html
/entertainment/themes/html
/entertainment/movies/
/entertainment/tv/themes/html
/entertainment/video_games/

/operating_systems/themes/html
/operating_systems/apple/themes/html
/operating_systems/apple/macosx/
/operating_systems/linux/themes/
/operating_systems/linux/redhat/fedora/themes/
/operating_systems/linux/ubuntu/
/operating_systems/microsoft/themes/html
/operating_systems/microsoft/windows7/themes/html
/operating_systems/microsoft/xp/themes/html

/travel/
/travel/usa/
/travel/western_europe/
/travel/western_europe/france/

The root, as well as many, but not all, of the children, grandchildren, and later descendants of the root contain a 'themes/html/' directory.

When a client submits a request for some page on the site, the script starts by looking for the requested theme closest to the request. If the named theme cannot be found, the routine works backward, up toward the root until it finds the theme it's looking for, or runs out of places to look.

Let's look at a couple of example requests:

Example 1:

 .../operating_systems/microsoft/xp/some_post.html

Given this request, the routine starts by looking for an html theme at:

/operating_systems/microsoft/xp/

There is a 'themes/html/' directory at: '/operating_systems/microsoft/xp/', and this is the theme used in response to the request.

Example 2

.../entertainment/tv/index.html

The routine starts by looking for an html theme at:

/entertainment/tv/

You can see that there is a 'themes/html/' directory at that path, and this is the theme that will be used.

Note that it is different than the theme used in the first example.

These two themes are completely independent of each other, which gives us a lot of flexibility when it comes to designing an Ode site. We are not restricted to a single look. Rather, we can create as many themes as we might want, tied to various subdirectories, (i.e. content categories) to tailor the site however we see fit.

Example 3

.../entertainment/movies/some_post.html

Again the routine starts looking for an html theme closest to the request at:

/entertainment/movies/

This time there it will not find the theme it's looking for (in fact it will not find any themes directory at all). So the routine will try again one level closer to the root at:

/entertainment/

As you can see, there is a themes/html/ directory at /entertainment/ and this is the theme that will be used.

Notice that this is a different theme then the one used for example 1 and 2.

It is the same theme that would be used for a request like:

.../entertainment/some_post.html

and

.../entertainment/video_games/some_post.html

This example demonstrates that because the two directories:

.../entertainment/movies/
.../entertainment/video_games/

do not have an 'html/' theme, they share the theme at their parent directory: 'entertainment/'

Example 4

.../travel/western_europe/some_post.html

Given this request, the routine will start at the directory closest to the root. Failing to find an html/ theme there, it will search in the next directory closest to the root, and so on.

The routine will fail to find a theme at:

  • /travel/western_europe/france/
  • /travel/western_europe/
  • /travel/

Before eventually finding a 'themes/html/' directory at the site root.

This is the same theme that would be used for a request for the site's homepage, and also for every other branch of the hierarchy, at any level that does not contain its own more specific html theme.

This setup allows us to create custom themes for any portion of the content hierarchy we wish, simply by creating the necessary 'themes/html/' folder and defining a theme which is appropriate to that category.

For example we may want a different look for the following two segments of the site:

  • /travel/western_europe/france/
  • /operating_systems/microsoft/windows7/

Alternatively we can maintain a consistent look across the entire site by defining a single html theme at the site root.

Because themes completely dictate the look and layout of an Ode site, this arrangement allows for the freedom to create what appear to be entirely distinct sites.

But wait, that's not all. There's more.

What I've already described allows us to apply themes across the filesystem space, but I wanted to be able to apply themes across time as well.

The theme of a site consists of everything that is not the content of the site. As such the theme is incredibly important. To my way of thinking it is fully one half of the whole that is a site.

Moreover, it is strictly correct to say that it is everything that is not content, because there may in fact be quite a bit of content contained in the themes.

Picture a fairly typical 3 column site design:

 _ _ _ _ _ _ _
| _ _ _ _ _ _ |
| |         | |
| | content | |
| |         | |
| |         | |
| | _  _ _ _| |
| _ _ _ _ _ _ |


 _ _ _ _ _ _ _
| _ _ _ _ _ _ |
|         | | |
| content | | |
|         | | |
|         | | |
| _ _ _ _ _ _ |
| _ _ _ _ _ _ |

Everything except the portion of the diagram labeled content is dictated by the themes. This includes the header, footer and sidebars. Those portions of the page will probably include: images, announcements and other notices, important links to relevant content elsewhere on the web, ads, and other content.

Though the theme may change very little from day to day, there may be subtle changes introduced daily or weekly, and occasionally more substantial modifications.

I wanted the script to support navigating themes in time as well as space.

This means that it must be possible to archive themes in such a way that they are available to the script. Of course, there must also be a way to request archived themes.

To allow for this, the script recognizes a 'site_look_date' parameter, which works in combination with dated theme archives. These work together to allow clients to specify a target date, in addition to a theme name.

Dated theme archives.

As described above, individual themes are defined by directories found in container directories titled 'themes', and these containers exist alongside content under the site's document root.

For example

/technology/apple/macosx/themes/html/

The 'html/' directory in 'themes/' defines a specific theme that can be applied to requests for posts in the '/technology/apple/macosx/' directory, and subdirectories of .../macosx/.

Dated theme archives work much the same way, except that the names of the individual theme directories include a '-' followed by an eight (8) digit date suffix, in the format YYYYMMDD, specifying the date when the instance of the theme was created.

For example

/technology/apple/macosx/themes/html-20090131/

In this example we see that there is an 'html' theme at

/technology/apple/macosx/

which was created on Jan 31, 2009.

Note: The '-' is a delimiter, separating the date portion of the directory name from the rest of the name. This is intended to make it less confusing (for both the user and the script) when the non-date portion of the theme name includes digits.

For example

'rss2-20090131'

As already mentioned, dated themes work in combination with the 'site_look_date' parameter

The value of the parameter is a 4, 6, or 8 character string of digits, conforming to the format YYYY[MM[DD]].

All of the following describe valid site_look_date formats:

YYYY     (2009    )
YYYYMM   (200901  )
YYYYMMDD (20090128)

Underscores are allowed between components of the site_look_date string to improve readability.

The following are all valid site_look_date strings:

2009_0128
200901_28
2009_01_28
2009___01_28

The value of the site_look_date parameter specifies a target date.

It is a request for the version of the theme (whatever theme is specified in the request) that was in use (i.e. current) on the specified day.

For example

http://sample.net/cgi-bin/ode.cgi/technology/apple/macosx/some_file.html?
site_look_date=2009_0201

This address is a request for the post at:

'/technology/apple/macosx/some_file'.

It also indicates that the response should use an 'html' theme, and more specifically the 'html' theme that would have been the current theme on February 1, 2009.

$config::use_site_look_date

There is one other factor we need to consider, namely the configuration option, 'use_site_look_date'.

The option is interpreted as a Boolean.

When true the script recognizes (i.e. acts on) the site_look_date parameter and uses dated themes.

When false, the site_look_date parameter is ignored, and the script recognizes only non-dated themes.

So let's say that the /technology/apple/macosx/ directory includes a themes folder containing the following group of html themes:

.../themes/html-20090131/ , 2009_0131
.../themes/html-20090202/ , 2009_0202
.../themes/html/          , No date

Which of these themes will be used to build the page returned to the client in response to the following request?:

http://sample.net/cgi-bin/ode.cgi/technology/apple/macosx/some_file.html?
site_look_date=2009_0201

To answer that question, we need to consider the value of $config::use_site_look_date.

If the value is false the dated themes are ignored and '.../themes/html' is used. All of the other theme directories are ignored. On the other hand, if the value is true, then the non-dated theme is ignored, and the script chooses from among only the dated themes.

That leaves

.../themes/html-20090131/  , 2009_0131
.../themes/html-20090202/  , 2009_0202

It's important to understand that the date associated with a theme is the date when the theme was created (the date when the theme was first used).

In this case, 'html-20090131/' is the theme that would have been in use on the date specified by the site_look_date parameter in the example above, and so this is the theme that should be used in answer to the request.

Apparently 'html-20090131/' was replaced as the current theme two days later.

Assuming there are no other html themes with later dates, /html-20090202/ would be used for all requests including site_look_date values on or after 2009_0202.

There are 3 cases we need to consider

Case 1: The site_look_date mechanism is disabled

All dated themes are ignored and the script navigates the non-dated themes only to determine the appropriate theme to use.

Case 2: The site_look_date mechanism is enabled but the site_look_date parameter is not included in the request.

The non-dated themes are ignored and the script must negotiate the dated themes to determine the appropriate theme to use.

The absence of the site_look_date parameter is interpreted as an implicit request for the current version of the theme (i.e the theme with the latest date).

Case 3: The site_look_date mechanism is enabled and the parameter is included as part of the request.

As with the second case, _non-dated themes are ignored_.

Unlike case 2, the parameter specifies a target date. The goal of the script in this case is not to determine the current theme, but the theme that would have been used to answer the request on the specified date.

A closer look at Case 3

This final case is a little more complicated than the other two.

As with the previous case, we're looking for dated theme directories, e.g.

themes/
    html-2009_0101
    html-2007_02_15
    html-20090612

Remember that in the previous case we were looking for the current version of the named theme closest to the request, i.e. the theme folder with the latest date nearest to the requested path.

What does that mean?

We started looking for a 'themes/' directory containing a dated version of the requested theme in the path closest to the request.

If we could not find such a theme, the search continued one level closer to the site's document root. (If an appropriate theme was not found after working all the way back to the root, the routine returned a baked-in error theme.)

On finding a themes directory containing at least one dated theme appropriate to the request, the script sorted all of the matching themes in that directory, then selected and used the theme with the latest date (because the theme with the latest date is the current version of the theme).

So continuing with the example above:

themes/
    html-2009_0101
    html-2007_02_15
    html-20090612

are sorted in ascending order as follows:

html-2007_02_15
html-2009_0101
html-20090612

'html20090612' is selected because it is the theme with the latest date. (It is the current version of the theme.)

This brings us to the difference in the behavior of the script if a valid site_look_date value is included. In this case, because the client has specified a target date, we're no longer looking for the current version.

Continuing with the example from above:

themes/
    html-2009_0101
    html-2007_02_15
    html-20090612

If the request included the parameter:

site_look_date=2009_01_20

The theme the visitor will expect to see is:

html-2009_0101

Why?

html-20090612 is the current version, but did not exist on 2009_0120.

html-2007_02_15 did exist at the time of the request, but it had already been superseded by html-2009_0101.

So we know that we're looking for the dated theme with the latest date substring that is also before the value of the site_look_date parameter.

But this only describes part of the behavior of the script for this case.

In the previous case (which applies whenever the site_look_date mechanism is active but the parameter is not included as part of the request), we stopped looking for themes as soon as we found one that was appropriate to the request. This allowed us to prioritize themes closest to the request.

For example consider the following partial directory structure:

.../technology/apple/
.../technology/apple/themes/
.../technology/apple/themes/html-20090612/

.../technology/apple/macosx/
.../technology/apple/macosx/themes/
.../technology/apple/macosx/themes/html-2009_0101
.../technology/apple/macosx/themes/html-2007_02_15

Given the request:

http://sample.net/cgi-bin/ode.cgi/technology/apple/macosx/index.html

The theme used in response will be:

.../technology/apple/macosx/themes/html-2009_0101

Because it is the theme with the latest date substring in the themes directory closest to the request.

This is true even though technology/apple/themes/html-20090612 is more recent.

Again, this is because closeness of the theme to the request is the primary consideration.

This agrees with the behavior of the script when the site_look_date mechanism is disabled altogether. In both cases the idea is that themes can be customized on a per category basis by always preferring themes which are closer to the requested path.

Things are a little different when a valid site_look_date parameter is included with the request. We're no longer looking for the current version of the theme. Instead, we want to make sure that the script returns the theme that would have been used in answer to the same request on the specified date. This requires a bit more work.

When we specify a date using the parameter, we're telling the script that the provided date is the current date for the purposes of picking a theme. Any theme with a date later than the value of site_look_date has to be ignored because it doesn't exist yet from the perspective of the script. Those themes won't be written until some time in the future.

Here's an example to illustrate this behavior:

.../technology/apple/
.../technology/apple/themes/
.../technology/apple/themes/html-20090101/
.../technology/apple/themes/html-20090201/
.../technology/apple/themes/html-20090501/

.../technology/apple/macosx/
.../technology/apple/macosx/themes/
.../technology/apple/macosx/themes/html
.../technology/apple/macosx/themes/html-2009_02_16

Consider the request:

http://sample.net/cgi-bin/ode.cgi/technology/apple/macosx/
?site_look_date=2009_02_15

If this were case 2, we would be looking for the current version of the named theme closest to the request, which would be:

'.../technology/apple/macosx/themes/html-2009_02_16'

The script would find this version of the theme and stop looking. But, we're not looking for the current version. We're looking for the version of the theme that would have been used to answer the request on 2009_02_15. We can't use html-2009_02_16 because it won't exist until the next day. So the script has to continue its search one level up the directory hierarchy (i.e. one level closer to the root).

That brings us to:

'.../technology/apple/themes/'

which contains three dated themes that match the name specified with the request:

html-20090101
html-20090201
html-20090501

html-20090501 doesn't work because the date is after the requested site_look_date, so it essentially hasn't happened yet and the script ignores it. That leaves two potential matches, html-20090201 and html-20090101. Of these two, html-20090201 is the best fit because it has the latest date that's still earlier than the requested site_look_date. This is the theme that's used in answer to the request.

What if there is no 'best fit' theme?

(This will be true whenever there is no appropriately named theme with a date substring before the date specified by the site_look_date parameter.)

In this case, the script simply uses the current version of the theme.

To accomplish this, without starting over again, ode identifies the current dated theme closest to the request during the process of attempting to find a theme that fits site_look_date. The path to this theme is squirreled away.

If the script fails to find any suitable 'best fit' theme it resorts to using the saved current version (or the baked-in error theme if it did not encounter a single dated theme matching the request).

Dated themes work in combination with the 'use_site_look_date' option set in the configuration file.

The option $config::use_site_look_date is either true or false.

If false, the script ignores dated themes and picks the named theme closest to the request. If true, the script looks for the parameter site_look_date.

For example,

?site_look_date=2009_1101

If the parameter isn't included with the request, the script looks for the current version, which is the dated version of the named theme with the latest date. If the parameter is included, it determines which dated version of the theme was in use on the date specified by the parameter.

What does this allow you to do?

Well let's say I'm going to update one of my themes. I can make a copy of the current theme and change the date suffix on the new copy to today's date (leaving the old one as it is). If a visitor doesn't request a specific date (with the parameter), they'll get the updated theme. But if they request a site_look_date earlier than today, they'll get the site as it looked on the specified date.

Of course, visitors probably won't know about the site_look_date parameter, so this is really a feature for site owners. For example they could use site_look_date to construct links that recreate the site as it looked at any point in time, or make an interface that will allow visitors to roll back the look of the site.

It's like a live local version of 'The Internet Archive Wayback Machine', and thanks to dated themes requires no additional work on the part of the site maintainer.

That's the gist of themes.

I've said that you should think of themes as templates. The template includes variables which are placeholders for values generated by the script (the title ($post_title) and body ($post_body) of your posts for example). When the script runs it replaces the placeholders with the values they represent to generate a complete page in response to a visitor request.

I haven't yet discussed what those placeholders are. I will provide a list of all of the variables suitable for use in theme files in a separate post.

There is more to Ode than just posts and themes. Let's talk about some of the subtler (i.e. less obvious) aspects.

Next

Ode allows you to target posts by date. You can for example construct a request for every post from New Year's day for the past 10 years. Learn more about date restrictions.