Wednesday, October 15, 2008

High performance readable conditional templates

My search for the perfect templating solution for webapps seems to be neverending.

In my latest concoction, I feel I've got a decent mix between readability, speed and flexibility.


It started out with my feeling that using "readable lookup columns" was a bit to verbose/data-heavy for templating.

I like pipe-separated column values, but on their own, in a template, they can be hard to read.

template = "<tr><td>$1</td><td>$2</td></tr>"

What I want to write is something like this:
template = "<tr><td>$name</td><td>$address</td></tr>"

In the demoapp I'm currently working on, I pick the field-labels (e.g. $name) from the first line in the lookup-column (fields := ...) using the Formula-property of NotesColumn.

fields := "firstName" : "lastName" : "company" : "address";
@Implode( @Transform( fields ; "fieldname" ;
@Text( @GetField( fieldname ) ) ) ; "|" )

This way, I don't have to maintain documentation of the indexes of the different values, nor the position of the values.


To get the most performance out of the rendering-engine, one more or less has to use evaluated formula-code. Iterating over an array of column-values using Forall/Replace is slower. It's more and more noticable the more documents/values one is putting through the rendering engine. If it's slow with one user, it's going to get a lot slower with hundred users/etc.

If all you were rendering were static content, and have a decent caching-engine, performance per render would be less of a problem.

My current solution is to join the array of column-values to a formula-interpretable-list. Due to Evaluate's limit on length of returned data, I have to process the data in snippets.

E.g. LS-array:
array = ["Party","Hard"] '(brackets to indicate an array)
formulaListString = |"| + Join( array, |":"| ) + |"|
'result: "Party":"Hard"

All the templates are "compiled" to @ReplaceSubstring. You can see how I compile/process the templates and data when the demoapp is complete.


Another downside to using pure LS-processing of templates is that it's hard to do conditional templating without slowing down the rendering-engine/writing a lot of code.

Since all data are being processed by Evaluate/Formula, it's quite easy to add conditional compiling as well (@If condition -> conditionalTemplate ). If you want, you can use one template for all the data, or you can have multiple conditions, and a default template.


Example-code from an agent printing a table from 499 documents

Result - odd/even-template

Result - mixed conditional template

At the bottom of the above HTML-files, you can see the amount of values/rendering-time. The time includes all processing, getting field-indexes, columnvalues, rendering, etc. The processing is done on localhost, on my laptop (Core 2 Duo 2GHz/2GB RAM) in Windows XP.

I'm not done yet with the implementation. The current engine is comprised of two classes. One that renders arrays of pipe-separated values (could also process CSV-files), and a subclass that processes the columnvalues of a view.

I plan to add another subclass to the first. This would let you use ft-search on a view, and sort the resulting columnvalues on a field/value in the resulting columnvalues.

If you have further suggestions/wishes/are curious about the code, contact me. Preferably throught comments. :)

I can't say when the demoapp is complete. I'll probably have another update or two before the app is ready for the masses.