[Grok-dev] some ideas concerning skins and theming
faassen at startifact.com
Wed Mar 21 18:18:30 EDT 2007
Just now Lennart Regebro and I had a discussion on what Grok's approach
to theming might look like. Here I jot down some of the ideas we had.
The aims is to allow theming while being template language independent.
We do want theming to be in-process, as we don't want to require
something like deliverance to be used
A skin is like a Zope 3 skin, identified by an interface that can
inherit other skins. A skin in Grok can however have a post-processing
step associated with it. This means that any views that declares it is
in this skin will be post-processed by that skin. Let's look at what
that looks like:
using the 'grok.skin' directive we say that this page is in our skin.
How we define skins and such we haven't really fleshed out in detail,
but one approach is sketched out in a Grok design document:
(this is certainly not set in stone; I think we need to deal with skin
inheritance and skins-as-interfaces, for instance. Possibly we just make
a special ISkin interface and then just grok anything that subclasses it
as a skin?)
The post-process step can then mess about with the HTML before it
finally goes back to the user. This will allow us to do theming.
What will this look like? We could have a postprocess grokker to
identify the postprocessing step (or the skin?) so we can write this:
# possibly also allow pointing to individual views for this?
# do we have a parallel with grok.context going on here, but
# then for skins or views?
def postprocess(self, html):
return html.replace('h1', 'h2')
Now the replace will be done for any HTML generated by any view that's
in the skin. This means that any text 'h1' will change into 'h2'.
Obviously this is a very coarse implementation. Better would be if the
template generated a stream of events (along the line of sax) and for
the postprocessor to transform the streams instead.
Genshi happens to have an implementation of this with interesting
features, called Markup Streams. This is not the templating language
itself, but a library underlying it:
The interesting feature is that they can do xpath on these streams. If
we were to generate such a Markup Stream for our templates, we could
post-process stuff. We may be able to reuse this bit of Genshi for our
purposes, as Genshi is a pure-python library shipped as an egg. Note
that I don't mean we use Genshi the template language here (that's
another topic), just bits of Genshi's implementation.
Still, postprocessing a markup stream by hand sucks. We want to make
this easier. What if we could use xpath to match bits of the output, and
then replace these with something else? That would allow us to do
something similar to what you do with macros and slots in Zope, but
without actually having to define any slots. You basicaly just have
fill-slot. :) What would that look like in grok terms?
What if we had views that didn't apply to a model, but instead applied
to bits of HTML? The context is *not* a model, but instead a HTML tree.
Let's imagine that that could look like:
grok.match('head/title') # xpath expression
return 'ME GROK ' + self.select('text()').upper()
Now for all views in the skin 'some_skin', we'd change the title of the
page (<head><title>hello</title></head>, for instance) from 'hello' (or
whatever it may be) to 'ME GROK HELLO'.
Of course instead of render, we could use Grok's existing view
mechanisms to also use a template to render the content instead. Note
the existence of a special 'select' method on the grok.MatchView to
select some bits (in the current context).
I imagine a matcher could also be used to insert viewlets and so on.
Using a template would be something like this:
and then in boxinserter.pt
This is a box with arbitrary stuff, like the weather, or a stock ticker,
<tal:block replace="python:view.select('*|text()')" />
(or should we allow context.select() here?)
This would insert the box and then put in the original content (this is
what select('*|text()') should do. This is modeled after Genshi match
templates, but associated with skins instead of included in a document.
This is a document describing Genshi match templates:
When you write templates while building your application, you make can
life easier for those trying to theme your application by using id= or
name= on tags in your HTML. Those are easier for a theme writer to match.
The nice thing about this design however is that you don't really have
to: we actually put no requirements on the template writer at all, and
it could still be themed (with a bit more of a hassle). This means that
we put very little burden on the application developer - a positive
compared to the requirement to define the same slots everywhere.
The standard skin postprocessing logic would apply all the MatchViews
associated to that skin. We need to think a bit about application order
here - what if multiple expressions match? What if we have a skin that
inherits from another one? Is there a way to disable this for a skin and
use a different set of matchers altogether?
If this approach works, there are also some performance aspects we need
to think about (how many parsing/serialization steps are necessary in
order to make this work? is self.select() efficient enough on a view, or
Note that this mechanism is inspired by Genshi's match templates:
I think with a bit of luck we can reuse much of Genshi's implementation
to come up with a prototype for all this.
More information about the Grok-dev