(Wed Aug 06, 2008) [/Tools] #
Let's suppose that last week your long-lost cousin Bubba called to see if
you'd design the website for his new bait and tackle shop. (Hey, he's a
progressive redneck.) You figured it would be a quick and easy job—he
just needed a few "purty pages to git my new bidness on the World Wide
Web." So you hacked together a couple static HTML pages with pictures of Bubba
and his charming bait, and uploaded them to his GoDaddy account. Bubba was
pleased as punch. You reckoned (hoped) you'd never hear from him again.
But it turns out Bubba was just getting started. Once he saw those first pages
light up, the ideas started flowing like tobacco to a spittoon. Then he asked
for new pages for all sorts of stuff: a mission statement, a series of tips on
how to fish like a pro, the GPS coordinates of his favorite fishing holes, and
so on.
Now you're in a pickle. The site was only supposed to have a couple pages, and
so you played the ol' copy/paste trick on the header, footer, and sidebar.
With all these new pages coming down the pike, you need to DRY things up in a
hurry.
Being a Rails programmer, the first thing that comes to mind is layouts,
helpers, and partials. But let's face it, managing a full Rails application is
overkill for this job. (Bubba's not that progressive.) Instead, you'd
prefer to simply use a familiar layout, helper, and partial style for now with
the option of easily migrating to Rails later, if necessary.
Hello, Webby!
Webby (created by Tim Pease) takes
text files written in your favorite markup language, combines them with
layouts, helpers, and partials, and generates HTML files. You upload
these HTML files to your web server and away you go. Simply put, Webby
makes it easy to build static websites without duplication.
1. Install It
Webby is a Ruby gem, which means even Bubba could handle the install.
$ gem install webby
Don't be too alarmed by all the other gems Webby relies on to get its job done. All the dependent gems are mostly common stuff.
You can also check out Webby on GitHub.
2. Build the Default Website
To build our website with Webby, the files need to live in a directory structure that Webby
understands. (Hey, Webby has opinions, too.) There's not much to it,
and the webby generator creates everything for us.
$ webby bubbas_bait
That creates a bubbas_bait directory with a
Rakefile and several directories:
Rakefile
content/
layouts/
lib/
output/
tasks/
templates/
We'll look at each of these directories as we go. For now, let's just pull the
trigger and see what happens.
$ cd bubbas_bait
$ rake
Running the default Rake task creates a bunch of files in the output
directory, including an index.html file. Where did that file come
from? Well, it's pretty simple.
When we run rake to "build" the site, Webby copies the files in the
content directory to the output directory. Some files, such
as images, are copied verbatim into the output directory. Nothing too
tricky there. However, the content directory can also contain special
files (called pages) that include a snippet of meta-data at the top.
These pages are first "compiled" into HTML files (using our favorite markup
language) and then copied into the output directory. In this case,
Webby transformed the default content/index.txt file into the
output/index.html file. (It also copied the Blueprint CSS framework and
the S5 presentation CSS into
the output directory.)
If you want to clear out the output directory and build the site from scratch, use
$ rake rebuild
If we now open the output/index.html file in our browser, we see the default page for our website. So far, so good.
3. Make a Page, Any Page
Before we dive into converting Bubba's existing HTML files into Webby-managed files, let's start by creating a new page for his mission statement.
$ rake create:page mission_statement
That creates a mission_statement.txt file in the content
directory, and opens the file in our default text editor. After changing the
default text to include Bubba's mission statement, the file looks like this:
---
title: Mission Statement
filter:
- erb
- textile
---
h1(title). <%= h(@page.title) %>
To sell world-class bait by the pound, and have fun doing it!
The YAML-formatted text at the top between the dashed lines is the meta-data
for the page. All the text after the meta-data section is content that gets
rendered into the resulting HTML file.
The meta-data section is simple, yet powerful. First, it contains page
attributes such as title in this example. We can add arbitrary page
attributes to the meta-data section and reference their values in the page
using the @page.attribute notation within an ERB expression. In this
case, we use @page.title to add the page title to the top of the
page.
The meta-data section also contains instructions that tell Webby how to
process the page. For example, the way a page is generated hinges on the
filter attribute. It lists the filters that need to be applied to the
page's contents in order to transform it into an HTML file in the
output directory. In this case, the ERB and Textile filters will be
run, in that order. We need the erb filter so that the stuff between
the <% and %> (the ERB expression) will get evaluated.
And since our page content is in the Textile format, we need the
textile filter to transform the page into HTML. If instead we wanted
to use raw HTML, which we'll use from here on, we'd simply remove the
textile filter. Other filter options include markdown,
haml, outline, basepath, and tidy. (See,
Webby isn't that opinionated.)
Having changed a page, if we rebuild the site, only those pages that have been modified are copied to the output directory.
$ rake
[13:43:04] INFO: creating output/mission_statement.html
4. Autobuild and Serve It Up
Now let's say we make another change to the
content/mission_statement.txt file. To have a look at the final HTML
file, we'd need to run the rake command to rebuild the site and open
the resulting HTML file in our browser. That's sorta tedious. Thankfully, we
can tighten up the feedback loop with Webby's autobuild feature.
$ rake autobuild
The autobuild task starts a build loop that keeps the output
directory up to date as files change. Whenever a page is created or updated,
its corresponding HTML file is automatically generated in the output
directory. Better yet, the autobuild task serves up the files in the
output directory on port 4331 using Heel , a small static
web server built using Rack and Thin. That way we can immediately
see changes reflected in our browser.
5. Frame It With a Layout
Next we need to put everything that's common to all the pages—the
header, footer, sidebar, etc.—in one file and let Webby combine it with
the contents of each page to produce the HTML file. That is, each page file
should contain only the information for that page and no more.
To do that, we update the layouts/default.rhtml file to include
our site's layout. Here's an example layout file:
---
extension: html
filter: erb
---
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title><%= @page.title %></title>
<link rel="stylesheet" type="text/css" href="/css/site.css" />
</head>
<body>
<div id="header">
Bubba's Bait and Tackle Shop
</div>
<div id="content">
<%= @content %>
</div>
<div id="sidebar">
Side Dishes
</div>
<div id="footer">
Copyright <%= Time.now.strftime('%Y') %> Bubba's Bait Shop.
Steal it, and we'll hunt you down!
</div>
</body>
</html>
Notice that the layout file contains meta-data just like regular page files.
In this case, we're using raw HTML rather than Textile, so we only need the erb filter. The extension attribute specifies the
filename extension that will be appended to every file this layout generates.
The layout file's job is to wrap the page-specific content in a consistent
layout. The content of each page is accessible in the layout via the
@content variable. So, we simply add the <%= @content %> line where we want the page content to show up in the layout. In
addition to the @content variable, the layout also has access to all
the attributes of the current page being rendered via the @page
variable. For example, we're using @page.title to set the page title in the HTML header.
By default, the layouts/default.rhtml will be used as the layout file. However, if we need to generate a certain page with a specific layout, we can explicitly set the layout file for any page using the
layout page attribute. For example, perhaps the mission statement page should have a more corporate feel to it. To change the layout, the
mission_statement.txt page's meta-data would look something like this:
---
title: Mission Statement
layout: corporate
filter:
- erb
- textile
---
This assumes we have a layout file called _corporate.rhtml in the
layouts directory.
6. Summons a Helper
Bubba likes to show off by quoting the weight of fish landed by his aromatic bait. These weights are listed on various pages around the site. We need the weights to be formatted consistently. Specifically, the weights get reported in ounces, but should be displayed in pounds and ounces.
Rather than inline the code for the weight conversion in several pages, we can write a helper method and call it from any page. To do that, we create a weight_helper.rb file, for example, in the lib directory. Just like in Rails, a helper in Webby is a Ruby method in a module.
module WeightHelper
def format_weight(weight_in_ounces)
lbs, ounces = weight_in_ounces.divmod(16)
sprintf("%d lbs, %d ounces", lbs, ounces)
end
end
Webby::Helpers.register(WeightHelper)
Unlike Rails, helpers must be explicitly registered as we did on the last line.
Then, wherever we need to display a weight on a page, we call the helper method in an ERB expression like this:
<%= format_weight(70) %>
Pretty cool.
7. Paginate A Gogo
Remember that series of pro fishing tips that Bubba wrote? To let the drama
build, we can use pagination to spread them across multiple pages. Let's
assume that the individual tip pages are in a directory called tips
in the content directory. First we create a top-level tips page:
$ rake create:page tips
We want the contents of the tips.txt page to display the individual
tip pages ten at a time in reverse chronological order. To do that, we can use
the @pages.find method to fetch all the pages inside the
tips directory and sort them. Then we pass the collection of pages,
and the number of tips per page, to the paginate method. Here's the
full tips.txt file:
---
title: Pro Fishing Tips
filter:
- erb
- textile
---
<h1><%= h(@page.title) %></h1>
<%
tips = @pages.find(:all,
:in_directory => "tips",
:recursive => true,
:sort_by => "mtime",
:reverse => true)
paginate(tips, 10) do |page|
%>
<div class="tips">
<%= render(page) %>
</div>
<% end %>
<%= link_to("Prev", @pager.prev) if @pager.prev? %>
<%= link_to("Next", @pager.next) if @pager.next? %>
The paginate method hands each tip page to the block where we simply
render the page within a styled div. Finally, at the bottom of the page, we
use the link_to method to generate the pagination links. The
@pager object was created when we called the paginate
method. It has methods including prev and next to navigate
the pages.
8. Reuse Snippets with Partials
Let's say we end up including pagination on a couple more pages. Along the way we add page numbers and tart up the style of the pagination links. Instead of duplicating the pagination links on each page, we'd like to
encapsulate the pagination links in one file and reuse it on all the pages that have pagination. That's where partials come in handy.
First we create the partial file:
$ rake create:partial pagination
That generates a content/_pagination.txt file. (Note that partial
filenames begin with an underscore and they aren't copied to the
output directory.) Inside the partial file, we add the HTML that generates
the pagination links:
---
filter: erb
---
<div id="pagination" class="rounded">
<strong><%= name %> Pages</strong>:
<%= link_to("Prev", @pager.prev) if @pager.prev? %>
<% 1.upto(@pager.number_of_pages) do |page_num| %>
<% if @pager.number == page_num %>
<%= page_num %>
<% else %>
<%= link_to(page_num, @pager.page(page_num).url) %>
<% end %>
<% end %>
<%= link_to("Next", @pager.next) if @pager.next? %>
</div>
Then we use the render method at the bottom of our
content/tips.txt page, for example, to include the partial into the
page.
<%= render(:partial => "pagination", :locals => {:name => 'Tip'}) %>
Looking back at the _pagination.txt file, we see two variables:
@pager and name. Remember that the @pager object
was accessible in the original content/tips.txt page, so it's
accessible in this partial as well. We use the @pager object here to
generate navigation links and display page numbers. The name variable
is accessible because we added it to the locals hash when calling the
partial. We use it to change the text that appears next to the pagination
links.
If you're a Rails programmer, you'll notice that the partial syntax is exactly the same.
9. Generate Stylesheets, Too!
So far we've only used Webby to generate HTML files, but in fact Webby can
transform any kind of text into something else. For example, let's say we have
our site's stylesheet in the content/css/site.css file. When we build
the site, Webby will simply copy it over to the output/css/site.css
file. However, if we add meta-data to the top of the stylesheet file, Webby
will transform it for us.
What's this good for? Well, do you ever get tired of typing (and remembering)
those hex color codes in your CSS files? If so, you're not alone. Here's an
example content/css/site.css file that uses page attributes to name
the colors:
---
extension: css
filter: erb
layout: nil # no layout
color:
bubba_blue: "#3366FF"
bubba_brown: "#996600"
---
body {
font-family: Verdana, "Bitstream Vera Sans", sans-serif;
background: <%= @page.color['bubba_blue'] %>;
}
table {
border: 4px solid <%= @page.color['bubba_brown'] %>;
}
th,td {
border: 2px solid <%= @page.color['bubba_brown'] %>;
}
The color attribute is a hash of color names and hex codes that we
can reference in the page using the @page.color[] syntax. Notice that we also set the layout attribute to nil because the
resulting output/css/site.css doesn't need a layout.
10. Deploy It
Deploying our website is as easy as building the site and uploading the
contents of the output directory to our web server. Remember,
everything except partial files are copied from the content directory
to the output directory when we build the site. Your content directory can include
an arbitrary number of subdirectories to organize your content any way you
like. (Consider removing the s5 and css/blueprint
directories if you aren't using them.)
If we don't already have an easy way to upload our files, Webby gives us a few Rake tasks to automate that process using rsync or ssh.
In the top-level Rakefile, we need to add three variables:
SITE.user = 'bubba'
SITE.host = 'bubbas-bait.com'
SITE.remote_dir = '/path/to/webserver/root/'
Then to upload the contents of the output directory using rsync, use
$ rake deploy
By default, the deploy task will use rsync to copy the files
to the directory specified in the SITE.remote_dir variable. (Look in the tasks/deploy.rake file for details.) If you'd prefer uploading via ssh, use the deploy:ssh task.
And that's all there is to it! We have a simple, yet flexible, way to remove duplication from our content and upload it to our web server.
Importing Existing Files
Now that we've created pages, layouts, helpers, and partials, importing our existing static HTML files into Webby is a breeze. Each existing file currently includes all the layout and page-specific HTML. To DRY it up, we just repeat these steps for each file:
-
Copy the layout code into the layouts/default.rhtml file.
(Most of the files will likely have the same default layout, and you can
create custom layout files for those that are different.)
-
Copy the page-specific content into a corresponding .txt file
under the content directory. You can mirror the directory
structure of your existing site in the content directory.
-
Write helper methods to encapsulate view logic and keep the pages DRY.
-
Extract reusable snippets into partials so you can render them
from multiple pages.
Finally, copy any asset directories into the content directory. Webby
will copy it over wholesale. For example, if you have an images
directory in your existing site, just copy it into the content
directory.
Writing Your Own Tasks
Webby includes a number of Rake tasks for common chores when building a website. We've used several of them:
$ rake -T
rake autobuild # Continuously build the website
rake blog:post # Create a new blog post
rake build # Build the website
rake clobber # Delete the website
rake create:atom_feed # Create a new atom_feed
rake create:page # Create a new page
rake create:partial # Create a new partial
rake create:presentation # Create a new presentation
rake deploy # Deploy the site to the webserver
rake deploy:rsync # Deploy to the server using rsync
rake deploy:ssh # Deploy to the server using ssh
rake growl # Send log events to Growl (Mac OS X only)
rake heel:start # Start the heel server to view website (not for ...
rake heel:stop # Stop the heel server
rake rebuild # Rebuild the website
rake validate # Alias to validate:internal
rake validate:external # Validate hyperlinks (include external sites)
rake validate:internal # Validate hyperlinks (exclude external sites)
As you continue to work on your site, you may find opportunities to automate other chores. Just create a file with a .rake extension in the tasks directory and add your Rake tasks to it. It's worth looking at the built-in task files in the tasks directory for examples.
The Bubba Blog
Ok, we're going a little overboard here, but creating a blog illustrates the power of Webby. We said that Webby can be used to generate any kind of text, and that includes generating XML files for RSS or Atom news feeds. So if Bubba decides one day that he'd like to do some blogging, we can generate a blog post page:
$ rake blog:post i_got_me_a_blog
That creates a content/blog/2008/08/05/i_got_me_a_blog.txt file, for example, where we can add the text for a blog post. Then we generate an Atom feed file using
$ rake create:atom_feed bubba_blog
If we then build the site, we'll end up with an output/bubba_blog.xml file that includes all the blog entries found in the output/blog directory.
Transitioning to Rails
If at some point we need the full power of Rails, it's fairly easy to make the
transition. Webby and Rails both use ERB to render pages, so converting the
page files into Rails view files is straightforward. As well, the layout,
helper, and partial syntax match very closely to the same facilities in Rails.
It's great to be able to take incremental steps like this rather than jumping
straight to Rails before you need it.
So the next time you're faced with building a static website, give Webby a
whirl! I've happily used it to build several static websites now. Frankly, I'm a bit surprised it hasn't received more press. Perhaps this tutorial
will help Webby get the attention it deserves.
If you're transitioning to Rails and you're looking for high-quality
training, consider attending an upcoming Rails Studio or Advanced Rails
Studio. We also have screencast video tutorials on a
variety of timely topics, including the ExpressionEngine content
management system for building dynamic websites.