XML Discussion Forum
November 20, 2000
Note The source code for this article requires the MSXML 3.0 be installed in replace mode.
Did you know that Microsoft Internet Explorer 5.5 has built-in support for editing HTML? I've always wanted a nice, XML-based tool for online discussions, kind of like newsgroups, but more structured so that I can add new features easily.
Some features that I've always wanted in such a tool include:
- Easy maintenance—The ability to delete my own postings, or the ability to have configured administrators who can easily delete postings. Xcopy deployment and management of these discussions.
- User ratings—The ability for end users to rate postings based on how useful they are, making it easy for other users to find valuable content.
- Speed—Newsgroups are always too slow for intranet brainstorming scenarios. I wanted a small, lightweight solution that a small team could use to brainstorm online and see each other's postings immediately.
- Total control over UI—XSL is the obvious solution here.
- Rich text support—So many Web-based forums are plain text only.
The only problem was how to do nice, rich text editing. Well, once IE 5.5 solved this for me, I was able to put the following together, based on a great prototype my friend Jonathan Marsh developed:
Figure 1. Prototype XML discussion list
You can also see the user rating feature; the figure above shows that the selected posting is also the most popular, with a maximum rating of 4. To rate a message, the user simply clicks on the links beside the heading RATE THIS MESSAGE and the page sends the rating info to the server and shows the new average. A lot more could be done with this; the cool thing is that it is very easy to use and fast enough that users will actually use it.
When you click REPLY, the details frame turns into an HTML rich text editor as follows:
Figure 2. Rich text editor interface
There are actually many more features of the IE 5.5 built-in editor that are not exposed here. A much richer user interface could easily be created using toolbars, pop-up menus and what have you. I actually got the code for this from another MSDN article, Positioning and Editing.
The first step (as with all XML-based Web Services) was to design a simple schema for the discussion forum that contains the index of message threads. In this case, I decided to use a traditional document type definition (DTD):
<!ELEMENT discussion (title, threads*)> <!ELEMENT title (#PCDATA)> <!ELEMENT threads (message*)> <!ELEMENT message (subject, body, author, posted, rating, replies)> <!ELEMENT replies (message*)> <!ATTLIST message id CDATA #REQUIRED> <!ELEMENT subject (#PCDATA)> <!ELEMENT body EMPTY> <!ATTLIST body src CDATA #REQUIRED> <!ELEMENT subject (#PCDATA)> <!ELEMENT author EMPTY> <!ATTLIST author name CDATA #IMPLIED email CDATA #IMPLIED> <!ELEMENT rating EMPTY> <!ATTLIST rating users CDATA #IMPLIED average CDATA #IMPLIED>
This code captures the hierarchy of threaded discussions, where messages contain replies that in turn contain more messages, etc. The body of each posting is stored in a separate XHTML file located by the src attribute of the <body> element.
This index is stored in a file called index.xml in the discussion forum directory, which you provide as a URL parameter called root to the discuss.asp page. So, for example, the above pages were displayed using the following URL:
This way, the same discussion forum Web application can maintain any number of separate forums on your site. For example, I have one forum for discussing this application itself (which you see above) and another for discussing new features of the product I'm working on.
Also in the discussion forum directory on the server is a file called admin.xml, which contains the names and e-mail aliases of those users who are allowed to delete postings:
<admin> <user> <name>clovett</name> <permission>all</permission> </user> <user> <name>jmarsh</name> <permission>all</permission> </user> </admin>
Right now, the only permission implemented is "all." Clearly this could be expanded in a lot of ways. It would also be nice to present a user interface for maintaining this administration information.
When you download the sources, you will see the following files:
|Common.asp||Some common ASP functions for managing shared resources|
|Cookies.js||Standard client-side cookie management code|
|Defaults.xsl||Default stylesheet for displaying XML (used for debugging only)|
|Delete.asp||The ASP script that deletes messages by first checking administrator privileges|
|Detail.css||The cascading style sheet for the message detail frame|
|Detail.xsl||The XSL transform for building the detail frame UI|
|Discuss.asp||The main entry point to the application; takes a URL parameter called "root" that points to the discussion forum directory on the same server|
|Discuss.css||A shared CSS that governs the overall look and feel of the discussion forum|
|Error.xsl||A small stylesheet for formatting error information|
|Expand.js||Client side JScript code for managing expansion and collapse of the outline view|
|Global.asa||Manages the shared objects stored in ASP Application scope|
|Left.html||The left frame, which contains only debugging links right now|
|Message.asp||Generates the detail view of a selected message|
|Outline.asp||The ASP script that generates the outline view|
|Outline.css||A CSS that controls the look of the outline view|
|Post.asp||An ASP script that manages posting of new messages|
|Relevance-filter.xsl||An XSL transform that filters the forum index and returns only those messages that are rated a certain way|
|Reply.asp||The ASP script that builds the reply user interface for a selected message|
|Reply.js||Some client-side Jscript® code for managing the reply|
|Reply.xsl||The client-side XSL transform that builds the reply frame user interface|
|Unload.asp||The ASP script that unloads all the shared Application scope objects on the server|
|User-rating.asp||The ASP script that processes an individual user rating|
|View-data.asp||The ASP script that returns the XML currently stored in memory for a given discussion forum|
|Xmlupdates.asp||A shared ASP script that implements some simple XML updategram merging used by post.asp|
The above XSL transforms are http://www.w3.org/1999/XSL/Transform transforms that also use the msxsl:script extension provided by MSXML 3.0.
Figure 3. Overall message flow
The above diagram illustrates the overall message flow for this application:The index.xml file is cached in ASP Application scope for better performance and is saved out to disk from time to time so changes are not lost. This is managed by a shared ASP script called common.asp, and by global.asa and unload.asp.
The top outline frame is generated by outline.asp, which runs a server-side XSL transform to filter out the messages that the user wants to see (based on ratings). It then sends the filtered index content to the client with an attached outline.xsl transform that performs client-side XSL transformation to generate the DHTML user interface.
When you select a message from the outline, the bottom detail frame is populated by running message.asp, which finds the specified message from the specified forum and returns the message detail with an attached detail.xsl transform that builds the detail DHTML user interface.
From the detail view, you can delete the message that invokes delete.asp and checks whether you have administrator permission, or your can reply to that message. Reply.asp builds a template message and uses the reply.xsl transform to display the rich text editor. From the editor view, you can post your reply to the post.asp script, which adds the info about your message to the shared index and saves the message body to a separate file on the server.
As you can see from the amount of source code included, this was not an entirely trivial exercise. This is starting to push the limits of maintainability with JScript ASP code on the server, very complex XSL transforms, and tricky DHTML client-side UI code. Some of the trickiest aspects were:
Indentation—The indentation in the outline view is done using a CSS style called padding-left. The value is calculated by the nesting depth of the <message> elements. This calculation is performed in the outline.xsl transform using the following XPath expression:
Local Time—The server generates the message timestamp in post.asp and stores it in Universal Coordinated Time, which is the number of milliseconds between the specified date and midnight January 1, 1970. This is done using the JScript Date() object getUTC* methods. Then on the client, the various XSL transforms use the Date object getTimezoneOffset method to adjust this time back to local time for display as a localized string, using toLocaleString. All this is done in an msxsl:script block inside the XSL/T transforms.
Multi-forum Support— One discussion forum application can be used to participate in multiple forums on the same server. You select the forum using the "root" URL parameter. The index.xml files for each forum are then cached in Application scope. In order to manage all this, a master list of all the loaded forums is maintained in another dynamically generated, free-threaded XML document object. This way, the Unload() method in common.asp can find all the forums that have been loaded and unload them all when the Application_OnEnd event fires in global.asa.
Expand/Collapse—The outline view is actually a single, flat table of rows. To implement the heirarchical expand/collapse, the HTML needed to contain enough information for the DHTML script code to be able to figure out the parent-child hierarchy between rows. This was done by generating id, thread, parent and depth expando properties on each <TR> element. This is done by the outline.xsl transform. The generation of these values is quite tricky and uses XPath parent and ancestor axes. This information is then used in expand.js to implement the expand/collapse functionality. The current expansion state is also maintained and saved in a cookie so that it can re-expand the forum to the same state you left it. This is done by maintaining a list of message IDs for the currently expanded messages.
Having reached this point, it would be pretty easy to add the following new features:
- Porting this to the new .NET Frameworks and a strongly typed language such as C# would be a big win in both performance and maintainability.
- Giving an administrator the ability to re-organize postings, perhaps even using DHTML drag-and-drop features, would help turn the random chaos of a newsgroup into a valuable knowledge base that would be easy to navigate.
- Creating more sophisticated user permissions and allowing users to delete their own postings, maybe by hooking up to NT authentication for guaranteed user identity.
- Easily configuring different "views" over the data. We currently have a filtered outline view, and a message detail view, but other kinds of filtering and sorting would also be interesting, as would summary views that show all the postings in an entire thread.
- Being able to select fonts, colors, font sizes, and so forth. This should be easy to hook up.
- Enhancing the user rating feature with more sophisticated options and protecting against users who repeatedly rate the same posting.
- Automatic polling for updates for a more collaborative experience, along the same lines as the ListEditor application I wrote about earlier.
Chris Lovett is a program manager for Microsoft's XML team.