Newbies - The place for learning to commence!
Building a SilverStripe Theme from a Static TemplateTweet
26 March 2009 | | | Supports v2.4, v2.3
In this tutorial we are going to create a SilverStripe theme from a static template. While completing this tutorial, you will build a good foundation for using the SilverStripe templating language and be able to create themes with dynamic menus and content areas.
The example template provided with this article was created using the CSS framework in this article and so is structured correctly for turning into a silverstripe theme. If this is the first time you are creating a SilverStripe theme we would recommend using this template as it will make following the tutorial a lot easier. However you don't have to use this template as long as your theme is structured correctly. Below is the reccomended theme folder structure:
To use the included template, download and extract the zip file attached above. Copy the contents of the brushedsilver_START folder into the new directory; themes/brushedsilver/ of your SilverStripe install. We then need to tell SilverStripe to use this theme as the default, which is done in the mysite/_config.php file. You will see a line setting the theme to blackcandy which we need to replace with the name of our theme. In this example our theme folder is called brushedsilver so we change this line in _config.php to:
Note. As of version 2.4, you can now set the theme via the SiteConfig page in the CMS. However if you don't want CMS users to be able to edit the theme you will need to set it here and likely want to remove the option within site config. see this post.
The first thing we need to do is create a single theme template which we can then break down into smaller pieces. Create the file themes/brushedsilver/templates/Page.ss and copy the contents of themes/brushedsilver/index.html into it. We now have a static HTML template under a .ss file. This will still work in silverstripe, just without any dynamic content. This gives us some indication as to how flexible SilverStripe's templating system is, we can use as little or as much regular HTML as we like all the way to having a pure HTML page! Obviously we don't want that so lets start making our template dynamic.
The first thing to do is edit the <head> section. We need SilverStripe to dynamically generate the meta-tags, the page title and also include any files we specify in our page controllers. Here is the same <head> section converted into a silverstripe template:
<!DOCTYPE html> <html lang="en"> <head> <% base_tag %> <title>$Title » $SiteConfig.Title</title> $MetaTags(false) <link rel="shortcut icon" href="/favicon.ico" /> <% require themedCSS(layout) %> <% require themedCSS(typography) %> <% require themedCSS(form) %> <!--[if IE 6]> <style type="text/css"> @import url(themes/brushedsilver/css/ie6.css); </style> <![endif]--> </head>
So lets run through this. The first thing we do is include the <% base_tag %> which is required for many of SilverStripes functions to work and set the title to hold the variable $Title which will be replaced by the title of whichever page we are on. This is followed by $Metatags(false) which adds the meta-tags set, the false specifying that we don't want it to include a <title> tag of its own. If we wanted our page title to just use the MetaTitle under the meta-data tab in the CMS, we could set this to true and remove our <title> tag. SilverStripe would then include the entire title line for us, but we would not be able to set a persistent part of it like the name of our site, which we do in this instance by grabbing it from the SiteConfig. At first this may look a little odd: $SiteConfig.Title What we are doing here is saying , get the 'Title' value from the SiteConfig object. This sort of notation is used often, but don't worry if your not quite clear about it yet, were just starting!
Finally add <% require themedCSS() %> tags for each of our CSS files and adjust the IE6 conditional path to be relative to the root of the site. And thats it, our <head> section is now ready for SilverStripe's dynamic magic.
Top Level Navigation
The navigation in the satic HTML is in the form of an unordered list. We are going to emulate this structure using a SilverStripe control loop which will loop over the top level pages allowing us to create a new list item and link for each one. It looks like this:
<body> <div id="container"> <div id="navbar"> <a class="home" href="/" title="go to home"><span>Home</span></a> <ul> <% control Menu(1) %> <li> <a class="$LinkingMode" href="$Link" title="Go to $Title">$MenuTitle</a> </li> <% end_control %> </ul> </div> ...
The control loop <% control Menu(1) %> tells SilverSripe to loop through the pages in the top level menu, creating whatever HTML is between this starting control and the <% end_control %> for each one. So in this case we create a <li> followed by a <a> and then a closing </li>. Within the <a> we use a few SilverStripe variables, namely $LinkingMode, $Link, $Title and $MenuTitle. The key to understanding how this works and how control loops work in general is understanding that within this control loop you are in the context of whatever item is being looped through, in this case pages in the top level of the site tree. So when we loop through and use the $Link variable in the href, SilverStripe inserts the link to the page whose context we are currently in. Likewise for $LinkingMode, $Title and $MenuTitle, while outside the control we would be getting the values from the actual page we are on, inside it the values returned are of the items in the loop.
$LinkingMode inserts a class based on whether the link we are creating is for the current page or whether the current page is a child of this link. This way we can style a.current and a.section to be highlighted to show the user they are on that page or in that section.
For more information on the built in variables and functions you can use in a template see this page: http://doc.silverstripe.com/doku.php?id=built-in-page-controls.
At this point, have a look at your page. You will notice the header image not showing as we havn't adjusted the path yet, but also notice that the top level navigation is now generated from your SilverStripe site. Remember that you will need to add ?flush=1 to the end of your URL each time you want to see changes made to a template. This makes sure SilverStripe clears its template cache before rendering the page. It's also worth disabling or clearing your browsers cache to avoid any confusion. If your using firefox you can download the Developer Toolbar that has a "disable cache" button among numerous other useful tools. If are using Chrome then you can do a hard refresh by hitting F5.
Adding the Sidebar and Content
Now lets get onto the main section of our page, otherwise know as the Layout. The Layout is the section of the page that changes between different types of page, so later on we will be stripping this out of our page and placing it in it's own file so that we can vary the look of our pages without having to have completely new templates. Before we do that though we want a completely working page.
First part is the banner image and the sidebar. For the banner image we just need to make the path to the image relative to the root. For the sidebar we use a control loop identical to the one we used for the top navigation, only this time we call Menu(2) instead of Menu(1).
<div id="layout"> <img id="headerImage" src="themes/brushedsilver/images/header_color.jpg" alt="header Image"> <div class="typography"> <div id="sidebar"> <div class="sidebarBox"> <h3>$Level(1).Title</h3> <ul id="menu2"> <% control Menu(2) %> <li> <a class="$LinkingMode" href="$Link" title="Go to $Title">$MenuTitle</a> </li> <% end_control %> </ul> </div> </div><!-- /sidebar --> ...
The only extra thing to notice here is that we use the variable $Level(1).Title for our sidebar's header. As mentioned before, this type of notation ($a.b) is used to get single values from within single objects, in this case we are getting the Title of the top level page in this section. This is actually a shorter way of writing <% control Level(1) %>$Title<% end_control %>.
Next up is the actual content area. This is really quite simple as all we need to do is insert our $Content variable along with the $Form (for the login form etc.) and $PageComments (for pages where comments are enabled in the CMS) and then make sure our typography, layout and container divs are closed. We also want to get our breadcrumbs in there which is as simple as using $Breadcrumbs. Gotta love SilverStripe :)
... <div id="content" > <div id="Breadcrumbs"> <p>$Breadcrumbs</p> </div> $Content $Form $Pagecomments </div><!-- /Content --> </div><!-- /Typography --> </div><!-- /Layout -->
There's not a whole lot to our footer, and even less that needs to be dynamic. We will however add a nifty variable to make the copyright year dynamic. $Now.Year will return the current year, so we can just stick that strait into our template.
... <div id="footer"> <div class="footerContent"> <p>© SomeCompany $Now.Year | Provided by <a href="http://ssbits.com">SSbits.com</a></p> </div> </div> </div><!-- /Container --> </body> </html>
So now we have a complete dynamic template which pulls in our page content and dynamically generates the menus. However, as a single file it's not particularly useful, as if we wanted a page with a different layout, for example without a sidebar, we would have to do it all over again in another file! Of course there is a simple solution that SilverStripe provides and that is to use a Layout template.
Creating the Layout Template
To create a Layout template we need to pick a <div> which we can always place into the main template, very much like you would with PHP includes. In this example were going to use the Layout <div> section and place it and its contents in a seperate file, allowing us to use different versions of that Layout for different page types. So first cut everything within the <div id="layout"> </div> tags pasting it into a new file themes/brushedsilver/templates/Layout/Page.ss and replacing it in the main template with $Layout like so:
<img id="headerImage" src="themes/brushedsilver/images/header_color.jpg" alt="header Image">
<% control Menu(2) %>
<a class="$LinkingMode" href="$Link" title="Go to $Title">$MenuTitle</a>
<% end_control %>
</div><!-- /sidebar -->
<div id="content" >
</div><!-- /Content -->
</div><!-- /Typography -->
Then in place of that in your main Page.ss template:
<div id="layout"> $Layout </div>
Now when SilverStripe draws a page it first grabs the main template from themes/brushedsilver/templates/Page.ss and when it encounters the $Layout variable it looks in the templates/Layout/ folder for a layout which matches the name of the current page type. If it can't find one or the current page type is Page then it just uses themes/brushedsilver/templates/Layout/Page.ss. It then resumes drawing from the main template for the footer etc.
Creating the Includes
We can also take this a step further and remove some things from our Layout template so that we can then re-use them in other templates. Let's remove the sidebar and the breadcrumbs. Create two files in the themes/brushedsilver/templates/Includes/ folder, Sidebar.ss and Breadcrumbs.ss. Cut everything inside and including the <div id="sidebar"> </div> and paste it into Sidebar.ss. Now we can simply replace it with an <% include %> tag, as well as introducing a condition to decide whether we add it to our template or not.
<% control Menu(2) %>
<a class="$LinkingMode" href="$Link" mce_href="$Link" title="Go to $Title">$MenuTitle</a>
<% end_control %>
And back in our Layout/Page.ss this is replaced by
... <% if Menu(2) %> <% include Sidebar %> <div id="content"> <% end_if %> ...
You will notice that we are using an <% if %> statement here. What we are doing is testing to see whether there is a second level menu to display and if there is we add our sidebar and our <div id="content> which limits the size of our content area. If not, then everything contained within the <% if %> is ignored leaving out the sidebar all together and letting the content fill the whole page.
Note. we are moving the <div id="content> to be inside this conditional statement, so if you copied and pasted the code above make sure you don't have another content <div> below it.
We also need to repeat this conditional <% if %> for the closing content <div>, otherwise we will have an extra closing element when not including the sidebar. So replace the </div> <!-- /Content --> line this at the end of your Layout/Page.ss template with the following:
... <% if Menu(2) %> </div><!-- /Content --> <% end_if %> ...
We now do a similar thing with our breadcrumbs so that they are only displayed if we are on the second level. Cut the contents of and including the <div id="breadcrumbs">...</div> and paste it into the Breadcrumbs.ss file. In its place put this code into themes/brushedsilver/templates/Layout/Page.ss
<% if Level(2) %> <% include Breadcrumbs %> <% end_if %>
This code has a subtle difference to the one we used for the sidebar. In our If statement we use Level(2) instead of Menu(2). The difference here is that with the breadcrumbs we only want to display them when the user is actually on the second level or greater, while with the sidebar we want to show it on the first level only if a second level exists.
This process of separating page elements into Includes is good practice that I recommend repeating for the Footer and Header sections as well as any custom blocks you use repeatedly in your templates.
So that pretty much wraps up the tutorial. You should now have a fully fledged SilverStripe theme that can be extended as you create new page types with different data. So Say you now want a seperate page template for the HomePage page type, you would add the file Layout/HomePage.ss and add the markup for that page. SilverStripe would then include that Layout file when viewing pages of type HomePage. As I am sure you have realised SilverStripe's templating system is not only very simple for non-programmers to understand but it is also unobtrusive and allows you to build your templates in a fashion very similar to those of a static HTML page, leaving all the complicated code where it should be, in the Model and Controller php classes.
Sometimes you may want to use a template that was created by somone else who did not have SilverStripe in mind. Here are a few pointers to preparing it for conversion into a SilverStripe theme.
- Use the blackcandy theme bundled with SilverStripe as a guide to the file structure of a theme. All your files should be in one folder with the name of your theme, containing three folders: css, images and templates. The templates folder should then contain two folders: Layout and Includes. Place all your images in the images folder and all your style sheets into the css folder.
- Although not essential it's good practice to separate layout, typography and form CSS into their own files. This makes editing easier as well as encouraging re-use in later projects.
- If you are using active states for navigation buttons so that they are highlighted if you are on that page check to make sure they are using the correct CSS selectors. Silverstripe uses .current and .section classes, returned when using the $LinkingMode variable.