Inheritance in TurboStan
Filed under: turbostanA longstanding limitation in TurboStan has been that inheritance could only be one level deep. I finally got tired of this and made TurboStan recursively render parent templates. The results were surprising.This limitation seemed a minor nit early in TurboStan's development. After all, Stan itself doesn't even support the concept of inheritance[*]. I added inheritance for two reasons: 1) Kid, TurboGear's primary templating engine supports it and I didn't want TurboStan to lack anything users of Kid might be used to and 2) the CherryPy dispatch system really works best using an inheritance rather than an include model for building pages.
However, I've been trying to refine how I organize my TurboGears applications and that includes keeping the template structure tidy and reducing code duplication in templates. With this in mind, I wanted to use the following structure:
index.stan +-- public.stan +--- page1.stan
| |
| +--- page2.stan
|
+--- admin.stan +--- admin1.stan
|
+--- admin2.stan
By doing this, I could put the general layout in index.stan, fill in general stuff from public.stan, and provide the content and other custom stuff from each page's template. Further, the admin interface inherits from index.stan, and so acquires the same look as the public face, but can remove or add sections in admin.stan, etc.
So far so good.
Except that this model requires that page[12].stan and admin[12].stan be able to inherit from public.stan and admin.stan which in turn inherit from index.stan. This wasn't doable with TurboStan prior to 0.8.6. So I fixed it by having templates recursively render parent templates. All in all only an hour or so of work, not much.
However, the benefits were immediately noticeable. The target structure has become very elegant, with child templates doing the bare minimum to fill in slots in their parents and index.stan truly controlling the layout of the site. I knew this would be the result, but until I'd actually converted my templates to use the new layout I guess I just failed to realize what a boon this would truly be.
# index.stan
html [
head [
title [ 'example ],
link ( href = "/css/style.css", rel = "stylesheet", type = "text/css" ),
slot ( 'css' )
],
body [
div ( id = 'menu' ) [ slot ( 'menu' ) ],
div ( id = 'content' ) [ slot ( 'content' ) ],
div ( id = 'sidebar' ) [ slot ( 'sidebar' ) ]
]
]
The public view fills in the content, menu and sidebar slots:
# public.stan
inherits ( 'index' ) [
override ( 'css'), # we'll just use the default stylesheet
override ( 'menu' ) [
ul [
li ( href = '/' ) [ 'Home' ],
li ( href = '/page1' ) [ 'Page 1' ],
li ( href = '/page2' ) [ 'Page 2' ],
li ( href = '/admin' ) [ 'Admin' ]
]
],
override ( 'content' ) [
xml ( ''
<p>
Hello, world<br />
This is some content. It could have also
been provided via a variable or several other
methods, but this keeps it simple.
</p>
'' )
],
override ( 'sidebar' ) [
p [ 'This is some sidebar text' ]
]
]
The admin view will remove the sidebar and set a custom menu:
# admin.stan
inherits ( 'index' ) [
override ( 'css' ) [
# we'll add some custom CSS for the admin interface here
link ( href = "/css/admin.css", rel = "stylesheet", type = "text/css" )
],
override ( 'menu' ) [
ul [
li ( href = '/admin1' ) [ 'Admin 1' ],
li ( href = '/admin2' ) [ 'Admin 2' ],
li ( href = '/' ) [ 'Main Site' ]
]
],
override ( 'content' ) [
div [
h3 [ 'Hello, administrator' ],
'Please do your admin duties here'
]
],
# sidebar will disappear from the generated output
override ( 'sidebar' )
]
And finally, page1.stan and admin1.stan will replace the content slot but leave the rest intact:
# page1.stan
inherits ( 'public' ) [
override ( 'content' ) [
div [ 'This is the page1 template.' ]
]
]
# admin1.stan
inherits ( 'admin' ) [
override ( 'content' ) [
div [ 'This is the admin1 template.' ]
]
]
Because you can inherit to arbitrary depths, children can "delete" slots in parent templates or add new content with ease. Also, note that the overrides on the css slot cause the <link> tags to get injected between the <head> tags as they should be.
You can download the latest TurboStan here.
The current version (as of this writing) is 0.8.6.
- To be clear, Stan has the slot directive which does the heavy lifting and is what I've leveraged to implement inheritance, but it was meant to be used procedurally from Python code; that is, you would define a slot in Stan, then some Python function would call fillSlots( ). Since a goal for TurboStan was to keep the syntax as a pure s-expression within templates, I added the inherits keyword as a way to express this concept directly within the template without having to resort to doing it procedurally.






