Tuesday, August 29, 2006

Spyce will not waste your time: code reuse

Most frameworks today have pretty good support for code/markup reuse towards providing a common look to a site without the "include a header, include a footer" clunkers you used to see. (Spyce uses "parent tags," which are as elegant as any and more powerful than most.)

But what I want to talk about today is code/markup reuse in the small, at a level that corresponds to functions or methods in Python.

Most frameworks today still suck at this. Approaches vary, but the important thing they have in common is not letting you use the same techniques you use in the rest of the framework as well as generally being clunky.

For instance, with Rails, you define functions which you can then use from your views/templates/presentation layer, but within those functions you're back in 1996, stringing HTML together manually. Something like

def faq_html(question, answers)
    html = '''
    <table class="faq">
    ''' % question
    for a in answers:
        html += '<tr><td>A.</td><td>%s</td></tr>' % a
    html += '</table>'
    return html

(Edit: Rails also has a code reuse mechanism called "partials," which behave a lot more like normal rhtml code.)

In Spyce, you can still do things this old-fashioned way if you really want. But a much better way is defining "active tags," which take parameters like functions but are defined with the same tools you build the rest of your page with, i.e., Python and other tags. Such as:

[[.begin name=html singleton=True ]]
[[.attr name=question ]]
[[.attr name=answers ]]

<spy:table class="faq" data="(('A.', a) for a in answers)">
  <tr><th>Q.</th><th>[[= question ]]</th></tr>

[[.end ]]

In a tag library named "faq", this active tag could then be used as follows: (the "=" sign means, "evaluate this parameter as a Python expression instead of as a string)

<faq:html question="=question.body" answers="=[a.body for a in answers]" />

The more complex your code becomes, the more you'll appreciate the Spyce way. You'll find that with the increased programmer-friendliness active tags provide, you'll abstract more common code than you would have with clumsier tools, with the all the improved maintainability that entails over code-by-copy-and-paste.

But what's really cool is you can encapsulate handlers with your tags, creating self-contained bundles of render and control logic. This is crucial: there's no way to do this with the traditional "helper function" approach. Splitting functionality out into active tag components like this allows both reuse and encapsulation, just as classes provide similar benefits in pure Python code.

(We talked about Spyce handlers yesterday. Briefly: Spyce allows you to attach a python function to a form submit action.)

To continue our FAQ example, let's add a "Create a new FAQ" handler, like this:

def faq_new(self, api, question, answer):
    q = api.db.questions.insert(body=question)
    api.db.answers.insert(question_id=q.id, body=answer)

[[.begin name=faq_html singleton=True ]]
[[.attr name=question ]]
[[.attr name=answers ]]

<spy:table class="faq" data="(('A.', a) for a in answers)">
  <tr><th>Q.</th><th>[[= question ]]</th></tr>

<h2>New FAQ</h2>
<div><f:text name=question label="Question:" /></div>
<div><f:text name=answer label="Answer:" /></div>
<div><f:submit handler=self.faq_new value="Create" /></div>
[[.end ]]

Spyce provides friendlier and more powerful tools for code reuse than either other view-oriented frameworks or MVC frameworks. Give it a try; you won't want to go back.

Tomorrow I'll explain the Spyce authentication system.


Anonymous said...

Two comments:

Piecing together an html page from HTML fragments is not what most templating languages do ... your argument that Spyce does it better thus is lacks convincing power ... I pity the fool who generates html by adding via +=

If you want to convice people that the Spyce way is better, compare it to Cheetah, Django or Kid.

just an opinion

Jonathan Ellis said...

It sounds like you're saying, "Compare yourself to these other things, but limit your comparison to the things they can do too." Not a very interesting comparison, to my mind.

If you've never felt the need to reuse a section of template -- or split it up into sections that "do one thing well" -- the way you reuse or split Python code into functions or classes, all I can say is, try it, you've been missing out. :)

Tim Lesher said...

I think what "anonymous" was trying to say is that most templating languages nowadays provide better mechanisms than helper functions requiring manual HTML assembly.

But even if you are down to a helper function, ElementTree beats string concatenation.

Jonathan Ellis said...

... I've been trying to avoid rubbing others' noses in how Spyce is better than [specific other framework]. Which may be a mistake, but I think it would just sound like sour grapes and turn people off.

Honestly, what I'd like is for some of those other frameworks' designers to see this and think, "Huh, that's a good idea. I'll figure out a way to make my stuff at least that elegant." Then everybody wins.

Jonathan Ellis said...

Tim: perhaps you are right, but his inclusion of Cheetah makes me think not.

Jonathan Ellis said...

Edited the post to make it clear that I didn't mean all non-Spyce frameworks take the same approach.

Anonymous said...

Ruby on Rails has what it calls "Partial templates". They use the same template language as normal page templates, but can be included within a page template. They focus on "doing one thing well" and do not require stringing pieces of HTML together in code.

Aaron Bentley said...

To me, your faq_html looks like a strawman. I don't doubt your sincerity, but I think you're not looking very closely at the competition.

Because in Kid, it would be

<table py:def="faq(question, answers)">
<tr><th>Q. $question</th></tr>
<tr py:for="a in answers">
<td>A. $a</td>

Which looks at least as readable as your Spyce example, but is also valid XHTML.

Jonathan Ellis said...

Aaron, perhaps I am wrong, but my impression from the Kid documentation (http://kid-templating.org/language.html#named-template-functions-py-def) is that named template functions may only be used in the template they are defined in. That's pretty limited reuse.

(Edit: ah, you're supposed to inherit from "utility templates" and use them that way. Okay, Kid gets a pass on that part. :)

Sanford Rosser said...

Just ran across this and it looks intriguing. If I can wrap my brain around it, I'll give it a try. :)