Update: Fixed some of the code examples to remove a stray block
Pancake uses template inheritance to embed content in a root template. This is similar to Rails layouts, but with inherited templates, you can have different content blocks, each having their own default content. Inspiration for this feature has come from Django and Rango which also use an inheritance concept. There’s really not an easy way to explain in words so lets see what it looks like:
# base.html.haml
!!!
%html
%body
%h1 Welcome to base
- content_block :content do
%p Default Base Content
# foo.html.haml
- inherits_from :base
- content_block :content do
%p Foo Content
If we had a stack that looked like this:
class FooStack < Pancake::Stacks::Short
add_root(__FILE__)
get "/" do
render :base
end
get "/foo" do
render :foo
end
end
When we visit the “/” url we’d get output like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<body>
<h1>Welcome to base</h1>
<p> Default Base Content </p>
</body>
</html>
When we visit “/foo” we’d get something like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<body>
<h1>Welcome to base</h1>
<p> Foo Content </p>
</body>
</html>
See how the content block for :content in foo was used when we inherited from :base? It’s got “Foo Content” in the inherited one. Templates in a Pancake Short stack can all use inherited templates. By suppling inherited_from with a template name, the current template will inherit from it. You can supply a different template name on each request, and then inherit from a different template each time if you need to.
That’s cool and all. We can inherit from a different template each time. Pancake will take care of finding the right template when you inherit a stack also, so that if a child stack hasn’t defined it, it will look in the parent stack.
Wouldn’t it be cool to be able to append to the parent content rather than just overwrite it though? Well a Pancake Short stack lets us do that too.
# foo.html.haml
- inherits_from :base
- content_block :content do
%p Foo Content
= content_block.super
By calling super on the content block, you’re inserting the content of the parent block inside the child. The output of running that template would look like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<body>
<h1>Welcome to base</h1>
<p> Foo Content </p>
<p> Default Base Content </p>
</body>
</html>
One of the troubles with an inherited approach though, is that if I have 2 stacks, Foo and, Bar inside a master container stack MyMasterStack, all three of these will inherit from different places. I’ve just added in Pancake version 0.1.26 the ability to specify a stack name and template name in inherits from. So you could have something like this:
- inherits_from UiStack, :base
- content_block :content do
%p Some content
So we’re inheriting this template from the :base template of the UiStack. Kinda cool, but there’s still a problem. I need prior knowledge that it’s the UiStack that I want to inherit from. That’s ok when it’s your app, but if you write a stack for general consumption you may not be able to make that assumption. Pancake sorts this for you. You can tell Pancake, at a global level, which Stack you’d like to set as the master template stack. By default this is the container stack when using pancake stand alone. If you’re just adding it somewhere else though, you’d start the container stack like this:
MyStack.stackup(:master => true)
This sets the stack as the master which adds the “master” directory as another root for the stack, but also sets the stack to be master of templates. To manually set the stack as master of templates you do this:
MyStack.before_build_stack do
Pancake.master_templates = UiStack
end
This sets the UiStack as the master for the whole pancake graph, which lets us do this:
- inherits_from :default!
- content_block :content do
%p content for this view
Using the :default! template name, will tell pancake to inherit from the master template stack, in this case UiStack, with the :base template. You can tell it to use a different one but by default, :base is the one that gets used.
By using this mechanism, a collection of different shared stacks can share the same look and feel by inheriting from the global Pancake master templates stack. When writing plugins and shared stacks, you can just set the stack templates to inherit from :default! and everything should just work.
Tags: inheritance, Pancake, templates
November 28, 2009 at 12:03 pm |
Great stuff. This is the killer feature of Django and I missed it in ruby world.
I would like to see inheritable templates also in Merb. Maybe I’ll start working on it
November 28, 2009 at 4:24 pm |
Marcin, I was already thinking about it. I’ll be happy to help with the feature. We was actually already talking about it with Pavel Kunc (merboutpost) and he probably would like to have it for Merb 2, at least as an option, but we haven’t discussed it with other Merb devs so far.
December 14, 2009 at 5:50 pm |
Thanks to Rango it’s fairly easy to get template inheritance into your favourite framework. You can just use Rango template rendering layer (you don’t need anything else from Rango) to get it working.
See an examples:
Merb: http://github.com/botanicus/rango-in-merb (just few lines of code, really easy!)
Rails Plugin I created yesterday for this: http://github.com/botanicus/rails-template-inheritance, example app http://github.com/botanicus/rango-in-rails
December 21, 2009 at 10:31 am |
Hi, Thanks for interesting posts. I’m new to web frameworks – trying to get a handle on which-does-what. I am wondering if Thor’s tasks/action can’t be explicitly leveraged, so inherits_from and content_block would be an alias to Thor’s invoke. By writing tasks you’d get Thor’s name spacing fu, inheritence of class and method options, etc, etc and invoke can be employed quite flexibly. Main benefit would be compounding Thor task know-how into pancake and vice-versa.
Just a thought.
HTH?