Square wheel*

Playing with design: Object-oriented HTML helpers

by pstradomski at 2009-03-21 17:27

HTML helpers are simple functions, that generate HTML fragments. They were made popular by Ruby on Rails (eg. link_to helper). Similarly they were adopted in Pylons as Webhelpers.

In theory helpers should be just simple functions – the output should depend just on arguments to the function. This works well for most cases, but sometimes there is a need to reach for values provided by other classes – eg. it would be nice if form-generating helper could obtain the value of CSRF-protecting field from the object that provides such protection, or access the URL-generating object (eg. in a link() helper)

One could just avoid those situations by forcing user to supply those arguments – but it can become inconvenient at some point. Another solution would be to just call functions that provide those values (URL generator or CSRF token generator) directly, but this would tie helpers to specific objects/implementations (what if user switches to another URL scheme and wants to use his own generator?).

In OOP world standard practice for such situations is to use either dependency injection or dependency lookup – but when using plain functions a new problem arises – how to inject dependency to a function? In python this is doable, one could just inject it to a module-global variable. In the toy framework I'm writing now I wanted to avoid this, because it prevents sharing the same module between two applications running in the same interpreter. I also wanted to avoid thread_local variables as much as possible.

The obvious solution I used is to make those helpers methods of some object – it is then possible to inject dependencies into that object. My current implementation has a HelperSet class that has a handle to dependency-lookup code and an abstract Helper class. HelperSet is constructed during application initialization and various helpers are added to it – depending which of them programmer wants to use:

helpers = HelperSet(config)
helpers.add_helper(FormsHelper)
helpers.add_helper(ListsHelper)

That object is later injected into view and available to templates:

${h.forms.textarea_obj(post_form['body'], id='post-body', rows=40, style='width:100%')}

${h.forms.submit('submit', 'Save')}

A call to add_helper method constructs a new instance of given helper class and exposes it through an attribute on HelperSet object. The constructed instance also gets a reference to the set so it can use it's handle to dependencies registry.

Comments

Add comment