logo

How To: Threaded Inline Replies - The Basics

Now that the threaded comments change has settled in and I've ironed out most of the bugs I thought I'd share the basics of how it works.

First off it's worth considering the end result that is the HTML produced. It looks something like this:

<ol id="comments" class="level_1">
    <li>
        <p>comment 1</p>
    </li>
    <li>
        <p>comment 2</p>
        <ol class="level_2">
            <li>
                <p>1st reply to comment 2</p>
            </li>
            <li>
                <p>2nd reply to comment 2</p>
                <ol class="level_3">
                    <li>
                        <p>a reply to a reply</p>
                    </li>
                </ol>
            </li>
            <li>
                <p>3rd reply to comment 2</p>
            </li>
        </ol>
    </li>
    <li>
        <p>Comment 3</p>
    </li>
</ol>

Notice how we're using semantic structured HTML rather than a table or a list of DIVs which get indented using CSS!

The beauty of using pure HTML lists is that if you remove CSS from the equation you still get a threaded discussion. With no CSS the above HTML looks like this:

image

So, how do we produce this HTML?

Well, first thing to note is that a child thread is nested inside the LI element of it's parent. You can't close the LI tag for a thread until you've done outputting all its child threads. This is why we can't use a Notes View to do this, as each document in a view is unaware of the document before and/or after it, so it can't close or open tags based on other documents around it.

We need to write the code to handle the logic that opens and closes list items as needed. To do this we need to know two things when outputting the HTML for a document -- what level of nesting are we at now and what level of nesting were we at before.

  • If the document is at a deeper level of nesting we need to open a new OL element and put the LI for the current document in there.
  • If we're at a higher level nesting then we need to close as many of the open OL elements as is needed (depends how many levels deep we were before).
  • If at the same level of nesting just output a new LI element.

Key to this is knowing what level of the thread a document is, which isn't as hard as it might sound.

Beauty is always in simplicity and to keep the solution simple I wanted to build on what was already there. What each reply document already had was a "ThreadSort" field. If you've ever seen inside a Notes discussion template you'll know what that is. Yes?

The format of a ThreadSort field is as such:

MAIN_DOC_CREATED_TIMESTAMP MAIN_DOC_UNID SUB_DOC_CREATED_TIMESTAMP SUB_DOC_UNID SUB_SUB_DOC_CREATED_TIMESTAMP SUB_SUB_DOC_UNID ETC ETC

Which looks like this:

20091015102201 9B1A931E05BFC87D86257650003374A4 20091016041313 9D253F502E386E57862576510032A631 20091016042959 B8053CB5C7FB6A148625765100342F3A

You can see from the above field value that the document it represents is two levels below the main document. You could work that out by looking at the values held it, but we can also work this out using a simple formula:

(@Elements(@Explode(ThreadSort; " "))/2)-1

The level a document is at is one less than half the number of spaces in the ThreadSort field. Simple!

All I had to do was add the above formula to a column in a view. The view below shows all the reply documents categorised by the UNID of the main document they're replies to:

image

Note that the view is sorted first by the ThreadSort field which gives the view the natural order of the discussion. Note also that the last column's value can increase by no more than +1 at a time as you move down the view, but that it can decrease by any amount at any time!

Using this new column the LotusScript in the Web Query Open agent can now output the properly-indented documents using well-formatted HTML.

Here's the LotusScript in use:

Set nav = view.CreateViewNavFromCategory(MainDocID)
Set entry = nav.GetFirstDocument
                        
Dim last_level As Integer
Dim current_level As Integer
Dim i as Integer

last_level=0

While Not(entry Is Nothing)
                        
        current_level = entry.ColumnValues(2)
        
        If current_level > last_level Then 
                'open a new OL
                print "<ol>"
        ElseIf current_level < last_level Then 
                'close some OLs
                For i=current_level+1 To last_level
                        print "</li></ol>"
                Next
        Else 
                'same level - no need to mess with OLs
                Print "</li>"
        End If
        
        'open teh LI for this document
        Print "<li>"
        
        'Output the body of the comment
        Print entry.document.Body(0)
        
        'Remember level of the last processed doc!
        last_level = current_level
        
        Set entry = nav.GetNextDocument(entry)
Wend
                                
'Finally, close any open OLs,
'based on last_indent_level
For i=1 To last_indent_level
        replies.add "</li></ol>"+New_line
Next

Obviously the actual code I'm using is a little more involved (nor do I use Print statements), but you get the idea? I just wanted to show how simple the principle is. There are probably even easier ways of doing this, but the beauty of this approach is the way is produces semantic HTML, which is part-styled as a discussion before you even get to applying any CSS to it.

Comments

    • avatar
    • Aaron Hardin
    • Tue 20 Oct 2009 11:06 PM

    Thanks for the article Jake. As always it looks great and straight forward.

    • avatar
    • Chris Brewer
    • Tue 20 Oct 2009 11:08 PM

    In your HTML you don't even need the 'class="level_x"' as you can target your levels within the CSS using '#comments ol ol ol { }'

    But then again your LS isn't spitting out the class names so you may already be down that path... :)

      • avatar
      • Jake Howlett
      • Wed 21 Oct 2009

      Hadn't thought of that Chris. Good idea. Might get a bit messy though

      When we're dealing with level_8 which would require css of:

      ol#commment ol ol ol ol ol ol ol ol{

      Not as readable as ol#comment ol.level_8{

      Show the rest of this thread

  1. Very clever! I have done something similar by reading the structure of the view but this is a much cleaner and better approach.

    Is the threadSort field something that's unique to the discussion db or is it auto-generated for responses in general?

    I can only find a ThreadID field in the discussion database (both R7 and R8 versions). There's a reference to a ThreadSort field in the (threads) view but no such field on the docs.

    Fredrik

      • avatar
      • Jake Howlett
      • Fri 26 Feb 2010 04:20 AM

      Hi Fredrik,

      I can't remember where I got the threadsort field concept from (possibly the R4.6 discussion template?), but it's been a part of this site for about 10 years now.

      Are you looking for the code to the field? Here it is:

      FIELD ThreadSort := ThreadSort;

      y:=@Text(@Year(@Created));

      m:=@Text(@Month(@Created));

      d:=@Text(@Day(@Created));

      h:=@Text(@Hour(@Created));

      n:=@Text(@Minute(@Created));

      s:=@Text(@Second(@Created));

      ThreadSort + " " + y + @If(@Length(m)=1;"0";"")+m + @If(@Length(d)=1;"0";"") + d + @If(@Length(h)=1;"0";"") + h + @If(@Length(n)=1;"0";"") + n + @If(@Length(s)=1;"0";"") + s + " " + @Text(@DocumentUniqueID)

      Show the rest of this thread

Your Comments

Name:
E-mail:
(optional)
Website:
(optional)
Comment:


About This Page

Written by Jake Howlett on Tue 20 Oct 2009

Share This Page

# ( ) '

Comments

The most recent comments added:

Skip to the comments or add your own.

You can subscribe to an individual RSS feed of comments on this entry.

Let's Get Social


About This Website

CodeStore is all about web development. Concentrating on Lotus Domino, ASP.NET, Flex, SharePoint and all things internet.

Your host is Jake Howlett who runs his own web development company called Rockall Design and is always on the lookout for new and interesting work to do.

You can find me on Twitter and on Linked In.

Read more about this site »

Elsewhere

Here are the external links posted on the same day.

More links are available in the archive »

More Content