Tuesday, September 23, 2008

Templating: Generating HTML from document collections

Sometimes it can be useful to render HTML from document collections. I believe one of the fastest ways to get values from Notes documents is using Evaluate. As a lazy person, I like to avoid writing formula code, as it's somewhat verbose compared to modern templating languages like PHP.

To avoid this, make your own template interpreter using simple formula code.

Example:
str := "Date: $#dayOfMonth#$. $?month?$.";
'"' + @ReplaceSubstring( str ;
"$?" : "?$" : "$#" : "#$" ;
'" + ' : ' + "' : '" + @Text( ' : ') + "'
) + '"'


Result: "Date: " + @Text( dayOfMonth) + ". " + month + "."

The result can be used in an evaluate statement.

A great thing about templating is that it is very maintainable/reusable. If you want to change the output, all you have to change is the template-string. To generate XML and HTML from the same collection, all you have to do is to have two different string templates. The conversion logic can be the same.

In the simple demoapp I've included, a collection of 300 documents is processed, extracting five fields. A HTML table row is generated per document. class="even"/class="odd" is added so that one can style odd/even rows. If you're somewhat familiar with LotusScript, it should not be hard to make a template-class to fit your needs.

On localhost (my laptop) it takes about 100 milliseconds to generate the HTML/write it to a PassTruHTML NotesRichTextItem.

>> Demoapp

If you want a sorted collection, you can use NotesView.AllEntries, and use NotesViewEntry.Document, to get to the document. Beware of categorized views. Test NotesViewEntry.isValid = True to be certain that the entry represents a NotesDocument. There may be other problems with using NotesViewEntry.Document as well. Proceed with caution/good error handling. :)

2 comments:

Chris Toohey said...

Hey Tommy,

I did something like this in YellowCake - but created a "rendering template". This basically allowed me to make a request like this:

yellowcake?openagent&template=uniquekey&criteria=blah

So, the uniquekey template would have the target server and application defined, as well as an evaluation formula. Basically, I grab the defined target app, do an ftsearch based on the querystring criteria, and use the template's evaluation formula to (via-Formula) render whatever markup I wanted to return.

Lastly, I'd define a content type, and this would allow me to return via Print from the agent XML, HTML, Plain Text, etc. rendered-to-my-need markup from any Domino server and application that my Agent has access to query.

There were some caching considerations built into the application - which I'll admit I've walked away from as of late... but it's pretty damned slick once you get into "templating" logic-driven markup!

Tommy Valand said...

That's more or less what I tend to do with applications at work. The content I mostly generate is parts of webpages (menus/etc). Therefore the formname, hard-coded in a computed for display-field mostly controls what is generated.

The reason I hard-code the formname is that not all forms create documents/the form name isn't available on new documents.

The greatest thing about templating is coming back to the code to change it.. Change a little bit on a string.. All "new content".. :)

--

In most of my current applications, I parse pipe-separated strings from ColumnValues. This is mostly because I want to get the best performance out of the app (the generated content isn't cached).

Example of template-string:
|<li><a href="$2$">$1$</a></li>|

The only downside is that if I add a value to the separated string, I have to update the comments describing the column in every script library/agent that uses that column.

With good caching, the performance-issue of working with document-collections becomes void, since it isn't done every time a page is loaded.

I've started caching HTML in some of the newer apps I'm working on.

On webqueryopen, check to see when the db was last updated vs a stored date in the cache-document. If the DB has changed, regenerate HTML. If not, fetch HTML from cache.

Example of performance improvement of generating html for a menu in a CMS:
Uncached - 7-800ms
Cached - ~30ms

( Big reply.. Guess I'm passionate about templating.. :D )