community.roxen.com
Not logged in Date: March 12, 2010
 DEMO  DOCS  PIKE
 COMMUNITY  DOWNLOAD
Home Articles Web forum www.roxen.com

A simple web forum

Author: Martin Nilsson <nilsson@roxen.com>
Last modified: 2000-11-06 3:06:32


Simply put there are two kinds of sites, those with database applications and those without. And the difference isn't small either. Powering database applications is one of the major uses of RXML, and in this article I'll try to show why by demonstrating how you can make an online forum application with very few lines of code.

Setting up the database

One of the essentials in database driven applications is a database. Which one you use is not essential for as simple applications as this. Usually a MySQL database will do. It has a good price/performance relationship, but lacks many features found in more expensive databases. You will most probably not miss them unless you know about them, so I'll drop the subject.

When you have your database installed, make sure that the Pike you use for your WebServer has support for your database complied. You can find out about what features are in your Pike by looking at the "Pike module list" under the "Tasks" tab in the WebServer administration interface. Since we are going to use the SQL tags you must also add the "SQL tags" module to the site in which the application should run. Note that you can set the default database in the settings for the SQL tags module. This is the database URL that will be used if no host attribute is given to the SQL tags. While looking at SQL modules you can also take a look at the "SQL databases" module, which lets you define aliases for database URLs. This is handy if you have several database applications using different databases, since don't need to write as much in your RXML and you can easily change the database for an application. As a bonus you don't have to include any passwords in your RXML pages, if a password is part of your database URL. A better way might be to not have any password to your database ans use user and host for security instead.

Martin Nilsson
The author, Martin Nilsson
<nilsson@roxen.com>

Now everything should be set for us to begin with the actual database. A fairly minimal database should be able to keep the following content:

  • Message ID
  • Subject
  • Message
  • Sender
  • Response to message ID
If we interpret this in SQL we get (assuming you have already created a database):


CREATE TABLE post (id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
                   subject VARCHAR(255),
                   message TEXT,
                   sender  VARCHAR(255),
                   parent  INT UNSIGNED NOT NULL DEFAULT 0);

The parent field is a reference to the message that this is a comment to. We say that all top messages have the parent 0.

Post message

First we must have a way of inserting new content into our database. The workflow is as follows:

  1. User get an interface to fill in new content
  2. The content is sent to the server
  3. The content is added to the database
  4. The user is redirected to a place where he can continue

The first stage doesn't give us much options. We have to display an HTML form in which values can be filled in. The only design desicion to make is if the same page should handle the content or if a new page should handle it. Let's say we take the two page approach, then a simple form code might look like this:



<form action="post.html" method="post">
  Subject: <input type="text" name="subject" /><br />
  Sender: <input type="text" name="sender" /><br />
  Message: <textarea name="message"></textarea><br />
  <input type="submit" name="new" value="Post!" />
</form>

The action attibute in the form tag tells the browser where to go when the user presses a submit button. That is of course where we'll put the code that puts the user input into the database. We really only need one <sqlquery> tag that performes an input with the user data, followed by a <redirect> that takes the user somewhere more interesting. An alternative, and more annoying approach is to only write "Thank you!" on the page and let the user use her back button in her browser to get away.


<sqlquery query="INSERT INTO post (subject,message,sender) VALUES
                 ('&form.subject:mysql;','&form.message:mysql;',
                  '&form.sender:mysql;')" />
<redirect to="index.html" />

Note that the form variables are quoted with :mysql to ensure that no dangerous characters are inserted, e.g. an ' in any variable would cause an error if it wasn't quoted. Note that with the default settings you will not get a visible error message in the web page if there is any problem with the database unless you put a <debug on="1"/> before the database call. This is explained in more detail in the Errors! article.

A more compact way of doing the exact same thing is to use the same page for both the form and the insertion. We must then only try to do an insertion if the submit button has been pressed. When it is pressed the form-scope will contain e.g. the variable "new" with the value "Post!", which we can look for. We'll also drop the action attribute to the form tag, since the default is to send the result to the same page. The complete page code would then be:



<if variable="form.new">
  <sqlquery query="INSERT INTO post (subject,message,sender) VALUES
                   ('&form.subject:mysql;','&form.message:mysql;',
                    '&form.sender:mysql;') />
  <redirect to="index.html" />
</if>

<form method="post">
  Subject: <input type="text" name="subject" /><br />
  Sender: <input type="text" name="sender" /><br />
  Message: <textarea name="message"></textarea><br />
  <input type="submit" name="new" value="Post!" />
</form>

Presentation

When it comes to presentation there are a gazillion different ways to do, so I'll just pick one at random; List all the subjects and print out the whole message for the selected one. We start with the listing code. It's simply an emit tag using the SQL source.



<emit source="sql" query="SELECT id,subject,sender FROM post">
  <a href="?read=&_.id;">&_.subject;</a> (&_.sender;)
  <br />
</emit>

This is easily extended with some sort of message displaying code and a reply link.


<emit source="sql" query="SELECT id,subject,sender,message FROM post">
  <if variable="form.read == &_.id;">
   <pre>&_.subject; (&_.sender;)

   &_.message;</pre><a href="post.html?comments=&_.id;">Reply</a>
  </if><else>
   <a href="?read=&_.id;">&_.subject;</a> (&_.sender;)
  </else>
  <br />
</emit>

Reply

The difference between writing a new message and writing a reply to an existing one is only a matter of context. The form will look identical and the insertion code will do the same operation, except it must also store a parent id. Hence we'll extend the post page we already have. The only change that we need to do in the form is that we need to store the parent id. This is sent to the page in the form variable "comments" from the code above. Thus the new form code looks like this:



<form method="post">
  <input type="hidden" name="parent" value="&form.comments;" />
  Subject: <input type="text" name="subject" /><br />
  Sender: <input type="text" name="sender" /><br />
  Message: <textarea name="message"></textarea><br />
  <input type="submit" name="new" value="Post!" />
</form>

The insertion code will need a little more tweaking than the form code. Here we have two cases;

  1. This is a new post and should have the parent field set to 0.
  2. This is a reply post and should have the parent field set to the content of the parent form variable.


<if variable="form.new">
  <if variable="form.parent < 1">
    <set variable="form.parent" value="0" />
  </if>
  <sqlquery query="INSERT INTO post
                   (subject,message,sender,parent) VALUES
                   ('&form.subject:mysql;','&form.message:mysql;',
                    '&form.sender:mysql;',&form.parent:mysql;)" />
  <redirect to="index.html" />
</if>

Finally we might also want to show the text being commented, so the complete page looks like this:



<if variable="form.new">
  <if variable="form.parent < 1">
    <set variable="form.parent" value="0" />
  </if>
  <sqlquery query="INSERT INTO post
                   (subject,message,sender,parent) VALUES
                   ('&form.subject:mysql;','&form.message:mysql;',
                    '&form.sender:mysql;',&form.parent:mysql;)" />
  <redirect to="index.html" />
</if>


<emit source="sql" query="SELECT subject,message,sender FROM
                            post WHERE id=&form.comments;">
  <pre>&_.subject; (&_.sender;)

  &_.message;</pre>
</emit>


<form method="post">
  <input type="hidden" name="parent" value="&form.comments;" />
  Subject: <input type="text" name="subject" /><br />
  Sender: <input type="text" name="sender" /><br />
  Message: <textarea name="message"></textarea><br />
  <input type="submit" name="new" value="Post!" />
</form>

Message tree

Finally I want to give you some code to experiment with that takes use of the parent field in the database and creates a threaded message view. With these code examples and some artistic work I hope you can create a decent web forum for whatever needs you might have, given that they are small enough. As a final tip, if you use <expire-time now="1"/> on all your pages you avoid that the user gets an old page, which often is a problem with highly dynamic content.



<define tag="treeview">
<dl><dd>
<emit source="sql" scope="outer" query="SELECT id,subject,sender
                            FROM post WHERE parent=&_.parent;">
  <a href="read.html?message=&_.id;">&_.subject; (&_.sender;)</a>
  <br />
  <emit source="sql" query="SELECT id FROM post WHERE
                               parent=&_.id; LIMIT 1">
    <treeview parent="&outer.id;"/>
  </emit>
</emit>
</dd></dl>
</define>

<treeview parent="0"/>