Anonymous
Domino 2.0 Rich Internet Applications with IBM Lotus Notes/Domino
You are here: Today » Using a QueryOpen agent to render HTML pages
« Insert HTML action buttons for the Notes Client BlogEntry form
Finding information on BlogSphere »

Using a QueryOpen agent to render HTML pages

It makes sense: with LotusScript, you have almost unlimited control. So the choice of rendering entire HTML pages on the fly with a QueryOpen agent could well be the next step in Domino web development, providing you can make it fast enough. Let's find out.

The launch form

Again: my favorite way of opening Domino applications on the web: a dummy Navigator with its $$NavigatorTemplate form. But this time, the form is almost completely empty: a few hidden fields to trap CGI variables and one RichText field:

The RichText field 'HTML' will be filled by the QueryOpen agent 'wqo-launch'. But first, I want to make a few classes: a StringBuffer class to speed things up and a HtmlPage class to help generating an HTML page. Both of them will be stored in a LotusScript library 'HtmlEngine' so that I can use them in other QueryOpen agents as well.

The StringBuffer class

This class acts as a dynamic array of strings: pushing elements to an array is a lot faster than concatenating strings. BlogSphere developers: if your code is running slow: I noticed you use string concatenating all the time, so using a StringBuffer class is a big quick win.

(see elswhere for the StringBuffer class code)

The HtmlPage class

I invented this class to make it easy to work with HTML. Being an expert IBM Lotus Notes developer does not imply that you are also an expert in HTML, CSS or JavaScript. Everyone has his domain of expertise. The HtmlPage class is also fun to work with: you can add things to the metadata, include things in the head, add things to the body and even add debug information as you go along. So if you come across a content block that needs additional CSS or JavaScript, you can simply add it to the head tag instead of being forced to put them in the body. When finally the HTML is rendered, the class sorts the different parts of the page out and displays them in the correct place:

Class HtmlPage
    Private meta As StringBuffer
    Private include As StringBuffer
    Private body As StringBuffer
    Private debug As StringBuffer
    Private docType As String
    Private pageTitle As String
    Private debugMode As String
    Private showTime As String
    
    Sub New(Byval dType As String)
        Set meta=New StringBuffer(100)
        Set include=New StringBuffer(100)
        Set body=New StringBuffer(500)
        Set debug=New StringBuffer(500)
        docType=dType
        debugMode="on"
        showTime="yes"
        pageTitle="(undefined)"
    End Sub
    
    Sub setTitle(Byval value As String)
        pageTitle=value
    End Sub
    Sub setDebug(Byval value As String)
        debugMode=value
    End Sub
    Sub displayTime(Byval value As String)
        showTime=value
    End Sub
    
    Sub addMeta(Byval key As String, Byval value As String)
        meta.push |<meta name="| + key + |" content="| + value + |" />|
    End Sub
    Sub addStyle(Byval href As String)
        include.push |<link rel="stylesheet" type="text/css" href="| + href + |" />|
    End Sub
    Sub addScript(Byval src As String)
        include.push |<script type="text/javascript" src="| + src + |"></script>|
    End Sub
    Sub addInclude(Byval value As String)
        include.push value
    End Sub
    Sub add(Byval value As String)
        body.push value
    End Sub
    Sub trace(Byval value As String)
        debug.push value
    End Sub
    
    Sub render(Byval RtItem)
        If docType="strict" Then
            RtItem.AppendText |<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">| + CRLF
        Else
            RtItem.AppendText |<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">| + CRLF
        End If
        RtItem.AppendText |<html><head><meta http-equiv="content-type" content="text/html;charset=UTF-8" />| + CRLF
        RtItem.AppendText |<title>| + pageTitle + |</title>| + CRLF
        RtItem.AppendText meta.collapse(CRLF)
        RtItem.AppendText include.collapse(CRLF)
        RtItem.AppendText |</head><body>|+CRLF
        RtItem.AppendText body.collapse(CRLF)
        If debugMode="on" Then
            RtItem.AppendText |<fieldset><legend>Debug Info</legend>|+CRLF
            RtItem.AppendText debug.collapse(CRLF)
            RtItem.AppendText |</fieldset>|+CRLF    
        End If
        If showTime="yes" Then
            RtItem.AppendText |<p>Page rendered in | + Cstr(Round(Timer-startTime, 2)) + | sec.</p>| + CRLF
        End If
        RtItem.AppendText |</body></html>|
    End Sub
End Class

The QueryOpen agent

The sample page is just a test... but it renders a valid HTML page. There is error trapping, but be very careful not to break the HtmlPage class. If this breaks, you also lose your error trapping.

Option Public
Option Declare
Use "HtmlEngine"

Sub Initialize
    Dim session As New NotesSession
    Dim doc As NotesDocument
    Dim page As HtmlPage
    Dim dbPath As String
    
    Set doc=session.DocumentContext
    Set page=New HtmlPage("strict")
    page.trace "Agent started."
    On Error Goto catch
    
    dbPath=doc.DbPath(0)
    page.setTitle "Test QueryOpen"
    page.addMeta "description", "This is the description"
    page.addStyle DbPath & "default.css"
    page.addScript DbPath & "common.js"
    page.add |<h1>Test QueryOpen</h1>|
    page.add |<p>DbPath: | + DbPath + |</p>|
    'page.setDebug "off"
    'page.displayTime "no"
    
    Goto finally
catch:
    page.trace "Error " & Err & " in line " & Erl & ": " & Error$
    Resume finally
finally:
    page.trace "<br />Agent finished."
    page.render doc.GetFirstItem("HTML")
End Sub

The HTML rendered:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html><head><meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Test QueryOpen</title>
<meta name="description" content="This is the description" />
<link rel="stylesheet" type="text/css" href="/blog-lotusnotes/htmlengine.nsf/default.css" />
<script type="text/javascript" src="/blog-lotusnotes/htmlengine.nsf/common.js"></script>
</head><body>
<h1>Test QueryOpen</h1>
<p>DbPath: /blog-lotusnotes/htmlengine.nsf/</p>
<fieldset><legend>Debug Info</legend>
Agent started.
<br />Agent finished.
</fieldset>
<p>Page rendered in 0 sec.</p>
</body></html>

On screen, it looks like:

What next?

Next step maybe would be: making a BlogSphere lite version that includes all the optimisation for performance and Search Engines I have in mind. Maybe the BlogSphere developers will allow me to join :-)

Have fun with the sample database (don't forget to sign the agent and LotusScript library):

Download

HtmlEngine.zip (84 kB)

Star rating

0%

Comments

  1. 04/07/2007 07:45:25, Erwin Heeren

    Are you sure this will not cause performance problems on a heavily loaded server? I always assumed that @Functions and a rich text field were to be prefered over agents and saved the Web Query Open technique for those cases where there are no @fomula alternatives.

  2. 04/07/2007 17:41:41, Michel Van der Meiren

    Hello Erwin. Yes, that's what I also heard all the time in my more than ten years of Lotus Notes/Domino development. But it seems to become a habit. If we could persuade BlogSphere and IBM Blog users to add a timer to their QueryOpen agents, we would know. Maybe the Domino server performance regarding LotusScript agents has improved enormously while we were not looking?

  3. 06/07/2007 03:16:43, Kevin S Pettitt

    Hi Michel, sorry I wanted to comment sooner but I'm still having trouble with the comment system...This is a really great post. I'm happy you took the time to dig into Blogsphere like this, and I'm sure Declan would welcome any contribution to improving the template you'd like to make. I know I would.

    You really should have a conversation with him as he put a lot of thought into the new "render engine" that drives the wqo agents in version 3. There were some good reasons for going in that direction, but I'm not the one to explain them.

    Feel free to contact me directly for further Blogsphere assistance. Among other things, I'd like to point out that earlier versions of Blogsphere had dozens of skins built in, and for some reason I think later versions did not. I can send these all to you. Also, there is a configuration wizard which kicks in when you first launch a new Blogsphere database that doesn't have a "blogconfig" profile document. It sounds like you didn't see the wizard when you tried it out, which probably means yours had a profile already.

  4. 05/08/2007 01:28:39, Ben Poole

    You've got some great posts on this site!

    I use a WebQueryOpen agent to render the core page content in DominoWiki. It works quite well, and seems to behave under stress. The next version of DominoWiki optimises the approach taken by only parsing live page links in the Queryopen agent, whereas currently live page links and wiki mark-up are both parsed by the agent code.

    Web agents aren't the bogeymen they once were: I think the agent manager has had quite a lot of spit and polish over the years (especially with Notes 7).

    Note: I have no empirical evidence to back any of this up :)

  5. 05/08/2007 22:29:12, Michel Van der Meiren

    Hello Ben. You are right: when I started developing, using a WebQueryOpen agent was considered as the worst you could do. I did some rough tests, and indeed, there has been spectacular improvement. And in some cases, using a WebQueryOpen agent is almost the only solution, like for computing the live page links for Wiki pages. I am really looking forward to the next DominoWiki release.

  6. 13/08/2007 08:05:04, Ayhan Sahin

    Iam developing on a wCMS for 8 years. Iam working with HTML-Templates, they are rendered by the client and i set some flags these tell the server, if a wqo is needed to fired. Especially in the app-common language (like in ben pooles dominowiki) i need some regexp to e.g. evaluate formulas and so on...

    i tried the html-class and it rocks ;-)

  7. 09/10/2007 17:53:31, Eric Romo

    Have you tried using List(s) instead of Array(s), perhaps like so:

    Class StringBufferLst As StringBuffer

    Private lst List As String

    Sub New(Byval a As Integer)

    increment = a

    count = 0

    Erase lst

    End Sub

    Sub push(Byval a As String)

    count = count+1

    lst(count) = a

    End Sub

    Function collapse(Byval a As String) As String

    Dim sTempLst As String

    sTempLst = Join(lst, a)

    collapse = sTempLst

    End Function

    End Class

  8. 10/09/2007 05:56:47 PM, Eric Romo

    FYI: Sorry, but my previous post looked better before Submit

  9. 09/10/2007 20:16:16, Michel Van der Meiren

    @Eric: great stuff. Sometimes we just plain overlook the obvious...

  10. 10/09/2007 09:15:49 PM, Eric Romo

    NOTE: you'll need to change the Join call to use a function like the following to convert the list to an array. Apparently Lotuscript doesn't allow a List datatype to be used as an array for the Join function.

    Function listToArray(tLst As Variant, count As Integer) As Variant Dim tmpArr() As String Redim tmpArr(count) Dim idx As Long For idx = 1 To count tmpArr(idx) = tLst(idx) Next listToArray = tmpArr End Function

To add a comment, log in or register as new user. It's free and safe.