Your webapp would normally have some dynamic pages and a defined layout. A layout defines where to put the banner, the main navigation, dynamic content, footer, and other secondary contents like announcements. As a developer, you could be assigned to work one dynamic page (e.g. ‘projects’), your buddy on another page (e.g. ‘tasks’), and the designer on the main navigation and banner image.
The most common approach (at least from my experience) is to put the different parts of the layout in separate files and agree how each part will be included in every dynamic page. For example, let’s have a simple layout where we have a ‘header’, ‘content’, and ‘footer’. Let’s say you are assigned to work on the ‘projects’ page. Let us also assume you are using PHP for this application. In you ‘projects.php’, you will then have the following code:
<?php include('header.php') ?> This is where you put the list of projects. <?php include('footer.php') ?>
Now, your buddy who is working on the ‘tasks’ page will have a similar code in ‘tasks.php’.
<?php include('header.php') ?> This is where you put the list of tasks. <?php include('footer.php') ?>
Sidebar: The popular blog tool WordPress uses this approach in customizing its templates.
What’s wrong with this approach (or why I don’t like it)?
If you are working on 100 dynamic pages, each page will have to repeat this pattern over and over again. If you forgot to include the ‘header’, then your page will not display properly. Of course, that can be easily remedied by copy-and-paste.
If you were assigned to work on ‘projects’ page, the ideal scenario is you focus on the content and not worry about whether there is a left sidebar (or a right), or the ‘header’ comes before the ‘footer’. As much possible, you must be isolated from the details of the layout.
Let’s enhance our original layout and add support for a sidebar. Unlike a ‘footer’, the content of a ‘sidebar’ is usually dynamic and is dependent on the main content. For example, if you are working on a ‘projects’ page, you may want to have a list of ‘done projects’ on the sidebar. A sensible approach would be to use the ‘<div>’ tag to group the main contents and sidebar contents. In your ‘projects.php’, your code will look something like this:
<?php include('header.php') ?> <div id="main"> This is where you put the list of projects. </div> <div id="sidebar"> This is the list of done projects. </div> <?php include('footer.php') ?>
Not only you have to repeat this pattern for every dynamic page, every page is now tied to a specific structure. What if you decided to change ‘<div>’ attribute from ‘id’ to a ‘class’. Or what if you just found out about the IE box model bug and now have to use another enclosing ‘<div>’ inside ‘main’ and ‘sidebar’? To work around the box model bug, your code can look like this:
<div id="main"> <div class="content"> This is where you put the list of projects. </div> </div> <div id="sidebar"> <div class="content"> This is the list of done projects. </div> </div>
Aside from the repetition and dependency problem, the implementation of the ‘header’ and ‘footer’ is ugly. This is how your header.php would look like.
<div id="header"> Banner and navigation here. </div> <div id="content"> <!-- note this tag is not properly closed -->
This is your footer.php
</div> <!-- yikes! --> <!-- yikes again! --> <!-- enough! I get the point -->
The Ruby on Rails way
In the approach we have just discussed, in my opinion you are not actually defining a layout. Instead, you are just suggesting conventions that you hope everybody in your team would follow. You can’t stop a developer from including the ‘footer’ before the ‘header’, right?
Ruby on Rails uses an approach that isolates the contents with the structure of the layout. CakePHP, according to my buddy Evan, also has this approach to layouts. Using RoR, you define a layout that looks like this:
<html> <body> <%= @content_for_layout %> <!-- every content goes here --> </body> </html>
In your ‘projects’ page (in RoR that would be ‘projects.rhtml’), all you need to do is put your content.
These are all my projects.
That’s it. For every page, you focus on displaying the contents; there is no need to include ‘header” or “footer”. Another nice thing about RoR layout is when you look at the layout file, you can see the whole thing in a single file. Unlike in the previous approach where the header and footer are in two files. In RoR, it is easier to check whether you have the right HTML tags (notably the start and end tags) in place.
Of course, you can also modify the layout to add more elements like top navigation and banner.
<html> <body> <div id="banner"> This is my banner </div> <div id="topnav"> These are top links. </div> <%= @content_for_layout %> <!-- do not forget to include this --> <div id="footer"> This is the footer. </div> </body> </html>
Now, how would we define the placeholder for sidebar contents? We cannot go back to the previous approach because that would make the code dependent on the structure. In RoR, we can make our own ‘content_for’ markup to separate the different content types. First, we define where the ‘contents’ would be placed in our layout file.
<html> <body> <div id="header"> This is the header </div> <div id="main"> <%= @content_for_main %> <!-- our own 'content' markup --> </div> <div id="sidebar"> <%= @content_for_sidebar %> <!-- this one also --> </div> <div id="footer"> This is the footer. </div> </body> </html>
In your ‘projects’ page, you would then decide what contents to put in the ‘main’, and what to put in the ‘sidebar’.
<% content_for("main") do -%> These are the projects. <% end -%> <% content_for("sidebar") do -%> These are other information about the projects <% end -%>
Your existing dynamic pages are not entirely free of changes but with this approach, you are not dependent on the structure of the page. It does not matter if the ‘sidebar’ is coded before the ‘main’ because it is up to the layout to decide where to put the contents. If you want to add more tags, you just adjust the layout. You can also experiment on a variety of layouts (e.g. sidebar on the left) without affecting the dynamic content pages.
After I’ve written this post (using Notepad++ on WinXP), I checked the online Ruby on Rails API documentation and found out that ‘@content_for’ is deprecated. The preferred way to do this now is ‘<%= yield :main %>’ in place of ‘@content_for_main’.
In my next Ruby on Rails post, I will show how to use Ruby’s ‘yield’ to compose the different parts of your web page in a reusable manner.