06 - Template Formatting
As previously discussed, all templating logic takes place in the controllers. This includes standard placeholder population, form population and repeating elements such as table rows.
Basic HTML document manipulation
The simplest way to alter the template from the controller is by targeting the document's elements by their named id like this:
html.admin_email = 'firstname.lastname@example.org'
This will replace the content of the template element which looks like this:
Underscores and Dashes
In the example above, html.admin_email was interpreted to mean the html element with the id of "admin-email". Since this conversion is automatic and enabled by default, it's important to stick with the ZenTools convention and always use dashes instead of underscores for ids -- or at least for any ids which will be targeted by the controller for that template.
Implicit _content attribute and HTML attributes
In the simple example above, we see that setting an element to a value populates the content of that element. That's because the
_content attribute of that element is assumed to be implicit. In other words, this:
html.admin_email = 'email@example.com'
...is a shortcut for this:
html.admin_email._content = 'firstname.lastname@example.org'
Any other supplied attributes are assumed to be HTML attributes. This means that from your controller code, you can arbitrarily set HTML attributes. For example:
html.logo_image.src = '/images/logo.png'
html.google_link.href = 'http://google.com'
One special attribute to be aware of is the HTML
class attribute. Since
class is a reserved keyword in Python, you can't do this:
html.order_total.class = 'large-price' # This won't work
html.order_total._class = 'large-price'
So just remember to use "_class" when setting the html class attribute of an element.
Targeting elements without IDs
You can also target elements by the tag itself. For example:
html['form'].action = '/other-page'
Of course this will only be useful for pages on which there is only one form element.
Here's another example of this syntax:
html.shopping_cart['h1']._class = 'big-red-headline'
Notice that this last example is a chain of three elements. So in this case, only an
h1 tag within the
shopping-cart block is targeted. You can use this syntax for more specific targeting of elements within the DOM tree.
Now in the corresponding controller method for this template, we might do something like this:
Formatting placeholders using
While the above techniques are very useful for many situations, it's not ideal for all situations. For example, if we want to display a page of customer data consisting of a couple dozen attributes (first name, last name, address1, address2, etc, etc), then after fetching the customer record from the database, we'd have to arduously write out each line like this:
The arduous template
<div id="customer-info"> <p id="customer_first_name"></p> <p id="customer_last_name"></p> <p id="customer_address1"></p> <p id="customer_address2"></p> <!-- ...and so on --> </div>
The arduous controller
def customer_info(): this_customer = models.Customer.get() html.customer_first_name = this_customer.first_name html.customer_last_name = this_customer.last_name html.customer_address1 = this_customer.address1 html.customer_address2 = this_customer.address2 # ...and so on
Alternatively, we could use placeholders in our template and format them all at once using the
The easier template
<div id="customer-info"> <p>(customer.first_name)</p> <p>(customer.last_name)</p> <p>(customer.address1)</p> <p>(customer.address2)</p> <!-- ...and so on --> </div>
The easier controller
def customer_info(): this_customer = models.Customer.get() html.customer_info.format_with(customer = this_customer )
format_with() method takes one or more named arguments and matches the named attributes to the template. In the above example, the returned Model object is assigned to
customer so that it can be clearly seen how the
format_with() method marries the object and the template. But a common idiom is to use the same name for both like this:
def customer_info(): customer = models.Customer.get(1) html.customer_info.format_with(customer = customer)
You can also condense this down to just one line if you prefer:
def customer_info(): html.customer_info.format_with(customer = models.Customer.get())
Template placeholders are quite natural and understandable even to non-programmers who might need to occasionally make simple tweaks to the template. You can use as many (foo.bar) formatted placeholders as you want without fear of breaking anything. If there's no controller method trying to format those placeholders, they are simply ignored and treated as normal text.
What if you want to display a list of customers? This means creating a template row and duplicating that row for as many customers as we find in the database. For this, we can use the
In our controller, what we want to do is:
- Collect all the customers from the database
customer-rowfrom the template
Format each customer record as a clone of
- Replace the original template row with our clones
Don't worry, it's not as complicated as it sounds. Here's the controller:
def customers_list(): row = html.customer_row customers = models.Customer.collect() for customer in customers: row.append_clone(customer = customer) row.replace_with_clones()
And as in the template formatting example above, you can condense lines 3 and 4 into one line like this:
for customer in models.Customer.collect():
append_clone() method accepts arguments in exactly the same way as the
format_with() method. Using this technique, you're able to manipulate the data at each iteration and really do pretty much anything you might need to do as you iterate through the objects.
But if you just need to do something as simple as formatting the customer row with the customer data, then you can use the shortcut method
duplicate_with() like this:
def customers_list(): html.customer_row.duplicate_with(customer = models.Customer.collect())
That single line of code will replace your template row with fully populated clones. So not only are your HTML templates sparkling clean and easy to read, so is your controller code.
It should be mentioned while probably the most common use of these methods is for duplicating rows in a table, you can actually use them to duplicate any element of your document. It doesn't have to be a
This is often one of the most laborious tasks of web development. For a failed form submission for example, you want to populate the form items with the values submitted by the user (otherwise the user has to re-enter all of the fields). Especially troublesome is the fact that each type of form field is populated differently. Text input fields are handled differently from select menus or radio buttons for example. This last problem is handled elegantly with the
Template for set() example
<form id="customer-form" method="post" action=""> <input name="name" type="text" /> <input name="gender" type="radio" value="male" /> <input name="gender" type="radio" value="female" /> </form>
Controller method using set()
def customer_form(): if post: html.customer_form.set(name = post.name) html.customer_form.set(gender = post.gender)
set() method is very clever and can intelligently populate any named form element whether it's an input field, or a select menu or any other standard html form element.
Another even simpler way is to populate the entire form all at once is using the
populate_with() method like this:
def customer_form(): if post: html.customer_form.populate_with(post)
This works because the attributes of
post will certainly match the names of the form elements since that's where they came from in the first place. Using
populate_with(post) is a common idiom. Here's how the process works:
Form is submitted with
<input name="first_name" type="text" />
Submitted value is available to the controller as
Form is populated with post:
For each named form element which has a corresponding attribute in the specified object, the
set()method is automatically applied. In other words, since the form contains an element named
first_name, and since
postalso has an attribute of
first_name, the form element will have its value set to the same value which was posted.
Next: 07 - Models