Friday, November 16, 2007

Templating: Lookups -> HTML

Update, 08.12.07: An improved, more readable technique

Inspiration, ExtJs, through Rich Waters.

I've more or less come to the conclusion that HTML in lookup-views is evil.

On one of the apps I work with, there were two access levels, reader, and editor. I used to have a column for each access, and pull HTML from the view.

<pseudocode>
col := @If( @UserRoles = "[Editor]" ; 4 ; 5 );
@Implode( @DbLookup( "" ; "" ; "(lupView)" ; key ; col ; [FailSilent] ) ; "" )
</pseudocode>

This became a headache sometimes. I'd change something in one of the columns to go with new design-desicions. Maybe it was lunch, the end of the day, or talking to a co-worker, not exactly sure, but sometimes only one column remained altered. I'd program javascript/make css for the new HTML, and think everyting is ok. Release the update. Users got JS-errors/corrupted designs/etc.

The errors wouldn't come as far as to applications in production now, as we test thouroughly before release, but it could still be a maintenance headache.

Step one to get rid of headaches:

Moving the presentation out of the view

Get rid of HTML-views, use simple lookup-views (value1|value2|value3) instead. Example of transformation to HTML.

(missing </table>)


Step two to get rid of headaches:

Using template-strings





You're probably not going to save any time writing the transformation to HTML using templates the first time, but you'll save time in the long run. Having the template in one string instead of concatenating bits of the html-per-item on several lines also increases readability of the code.

Example of simple evolution:


Result:


>> Simple demoapp

For my access-level-problem, I either write a separate template for each access (e.g. add edit link for [Editor]),
or put @If(@UserRoles = "[Editor]" ; "" ; @Return("") ) at the top (don't do anything if not editor).

15 comments:

Naibaf said...

Hey, this is the power of formula language, we don't need no stinky loops. ;-)

lup := @DbLookup(""; ""; "(lupView)"; key; col; [FailSilent]);

html := html + @Implode("
< tr>< td>" + @ReplaceSubstring(lup; "|"; "< td>"));
"< table>" + html + "
< /table>"

(I had to add spaces to the tags, so the comment function will accept them.) Of course, you could also add the closing td and tr tags, but as long as we're talking plain old HTML and not xhtml, there's no need to.

I admit, that it's getting a little harder with the other samples ...

Tommy Valand said...

I think you missed my point (maybe my example was bad)..

The template could for instance look like this, if you're generating checkboxes from a lookup:
templateCheckbox := {<label for="$1$"><input id="$1$" name="$4$" value="$2$" type="checkbox"/>$5$</label>}

Patrick said...

Hejsan, I think this a very nice and clear approach of displaying lookup results.

In the code example you wrote you forget to close the [tr] tags but in the working example you dont forget that.

Too bad you can not give an additional parameter to @dblookup and @dbcolumns with the maximum amount of results you are interested in. But what the heck you can set a limit in the @for-loop.

Thanks!

Tommy Valand said...

Should probably have written: <pseudocode>
[example-code]
</pseudocode>
The example-code was more to show the concept, than "ready-to-use" code.

I use @Subset, when I only want, for instance, the first 15 results of a lookup:
"## Get the first 15 items ##;
lup:= @Subset( [lookup] ; 15 );

Naibaf said...

No, Tommy, I knew perfectly well, that my comment was not at the heart of the matter. I just picked on that specific example for the fun of it. It is amazing what you can do in @Formulas without using loops and how compact code can get.

However, it is probably easier for most programmers to follow the logic of a loop. The templating approach already requires a certain level of abstraction, so writing too "clever code" around it might not pay off well in the future.

Patrick, why one would want to close tr or td tags in automatically generated HTML is completely beyond me.

Patrick said...

@naibaf: I don't know if you should bother about closing tr tags in automatically generated HTML, it looks okej in your browser... but probably it will cause some questions when validating the html...

Patrick said...

@Tommy

@subset works after the complete lkup is returned, I would prefer to set a limitation on the amount of data i get returned or something that enables me to navigate through the results like

@dbcolumn with parameters [1]:[15] which should return the first 15 corresponding results...

ah something to ask to Santa Claus :o)

Naibaf said...

patrick, I agree on your comments on using @Subset. It would be favorable, if we had a means to limit the "resultset" prior to returning it. But I'm afraid, that Santa will not come up with that this year.

But you are wrong concerning the tr and td tags.

See this example of a perfectly valid HTML page (I replaced the opening angle brackets with a star, to make it pass the comment validation):

*!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">

*title>My valid site*/title>

*body>
*table>
*tr>*td>OnetdTwo
*tr>*td>Three*td>Four
*/table>
*/body>

Naibaf said...

OK, I forgot one star and closing angle bracket for the td before "Two".

Anyhow, replace the missing brackets and give HTMLTidy a go ...

Naibaf said...

Aaaarrrrrrggggghhhhhh, plus I misspelled the content type. Shouldn't write stuff like that from the top of my head. Of course, it has to be:

*!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

Nevertheless, the rest is still valid.

Tommy Valand said...

Both tr's and td's have closing tags as optional in the W3C spec for HTML 4.01.

I choose to close all tags out of preference.

Chris Toohey said...

Slick approach! Great for local manipulation of retrieved data.

I do some lookups for markup (when it makes sense to do so) - but I take a different approach than most...

I hate coming up with off-the-top-of-my-head examples, but here's one that'll get completely shot apart!

1) Form w two fields: markup : key
2) key = multi-value text, with whatever categorization I'm looking for: ie., @Text("datecreated_" + @Text(@Date(@Created))) : @Text("monthcreated_" + @Text(@Month(@Created))... etc. [bad examples, but go with me here...
3) markup = conditionally formatted markup value. if the document is typeA markup takes typeA's markup formatting, etc.
4) View: bykey, which contains two columns - key (categorized) : markup.

Now, I can do a lookup for a particular key and return the values in the markup field. If I need to change the markup field, I simply do that in the backend and resave the documents.

Now, this is a solution that I use for web-based solutions where document replication storms aren't really an issue. If you run a multiple-server environment or are also developing this solution for the Lotus Notes client, you might want to take that into consideration (read: don't do it this way). Otherwise, it's a pretty functional and expandable architecture - I think.

Input welcome and appreciated!

-Chris

Tommy Valand said...

To be honest, I have no idea of what your technique is from your description.

Do you use the form to create documents with static HTML from lookups, or are they inside the form you're presenting the HTML?

Blogpost? :)

Chris Toohey said...

Sorry about that - something about working towards a deadline and not being able to see the forest from the trees. Re-reading my post, it only makes sense because I already know what I'm talking about. I'll babble on over on my end where I can let some examples do the explanation and post back.

-Chris

Tommy Valand said...

Looking forward to it!