Simple Chat Application

This tutorial shows you how to build dynamic web pages with revIgniter on the basis of a simple chat application.
The chat consists of a Login page, where you enter a name,

chat login

and the Chat page with a list of messages and an input field.

chat messages

In order to accomplish this we need the following files and a database table:

If you have not read about controllers, views and models in the User Guide you should do so before continuing.

Note: Before we begin save the following stylesheet in assets/css/chat.css and build the table using the table structure below.

The stylesheet:

body {
	font: 12pt "Lucida Grande", Lucida, Verdana, Geneva, sans-serif;
}

.page_margins {
	margin-top: 50px;
}

#login {
	margin-left: auto;
	margin-right: auto;
	width: 400px;
	height: 70px;
	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#E3E3E3, endColorstr=#A1A1A1);
	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#E3E3E3, endColorstr=#A1A1A1)";
	background: -moz-linear-gradient(bottom, #A1A1A1, #E3E3E3);
	background: -webkit-gradient(
	linear,
	left bottom,
	left top,
	color-stop(0, rgb(161,161,161)),
	color-stop(1, rgb(227,227,227))
	);
	-moz-border-radius: 10px;
	-webkit-border-radius: 10px;
	color: #515151;
	font-size: 83.33%;
}

#login form {
	margin-top: 0px;
}

#login form, #login p {
	padding-left: 78px;
}

#login p {
	padding-top: 8px;
	margin-bottom: 6px;
}

#chat {
	width: 500px;
	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#E3E3E3, endColorstr=#A1A1A1);
	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#E3E3E3, endColorstr=#A1A1A1)";
	background: -moz-linear-gradient(bottom, #A1A1A1, #E3E3E3);
	background: -webkit-gradient(
	linear,
	left bottom,
	left top,
	color-stop(0, rgb(161,161,161)),
	color-stop(1, rgb(227,227,227))
	);
	margin-left: auto;
	margin-right: auto;
	-moz-border-radius: 10px;
	-webkit-border-radius: 10px;
	color: #515151;
}

#chatheader {
	background-color: transparent;
	height: 40px;
}

p#username {
	float: left;
	margin-left: 20px;
	font-size: 83.33%;
	margin-top: 12px;
}

p#logout {
	text-align: right;
	width: 100px;
	float: right;
	margin-right: 20px;
	font-size: 83.33%;
	margin-top: 12px;
}

a {
	text-decoration: none;
	color: #367ec9;
}

a:hover {
	text-decoration: underline;
}

#chatbody {
	height: 214px;
	overflow: auto;
	background-color: transparent;
	padding-top: 20px;
	padding-bottom: 20px;
	border-bottom: 1px solid #48a2ff;
	border-top: 1px solid #48a2ff;
	clear: both;
}

#chatbody li {
	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#3B3B3B, endColorstr=#4A4A4A);
	-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#3B3B3B, endColorstr=#4A4A4A)";
	background: -webkit-gradient(
	linear,
	left bottom,
	left top,
	color-stop(0, rgb(59,59,59)),
	color-stop(0.1, rgb(107,107,107))
	);
	background: -moz-linear-gradient(
	center bottom,
	rgb(59,59,59) 0%,
	rgb(107,107,107) 10%
	);
	-webkit-border-top-left-radius: 5px;
	-webkit-border-top-right-radius: 5px;
	-moz-border-radius-topleft: 5px;
	-moz-border-radius-topright: 5px;
	color: #e0e0e0;
	font-size: 75%;
	height: 30px;
	padding-left: 12px;
	padding-top: 1px;
	margin: 6px 20px 0 -20px;
	list-style-type: none;
}

#chatfooter {
	background-color: #919191;
	margin-top: 20px;
	-webkit-border-bottom-left-radius: 10px;
	-webkit-border-bottom-right-radius: 10px;
	-moz-border-radius-bottomleft: 5px;
	-moz-border-radius-bottomright: 5px;
	padding: 20px;
	height: 20px;
}

#userinput {
	width: 360px;
	float: left;
}

#submitbtn {
	width: 60px;
	margin-left: 28px;
}

#footer {
	font-size: 58.33%;
	text-align: center;
	margin-top: 80px;
}

The table:

CREATE TABLE `chat` (
	`id` int(7) NOT NULL auto_increment,
	`user` varchar(255) NOT NULL,
	`msg` text NOT NULL,
	`time` int(9) NOT NULL,
	PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Controller

The controller consists of two handlers commonly used in controllers and three additional ones. The first handler chat is named after the file itself and is called before any other handler. It loads all required libraries, helpers and plugins plus models and the database if needed. The second handler, named index, is the default handler, which is mandatory. This handler is called automatically if no other handler is specified in the URI. Further we need a handler, which adds new messages to the database, a handler, which gets all the messages and displays them, and finally a logout handler, which ends the chat, destroys the session data and reopens the login page.

We start with the basic prototype for a controller script:

<?rev

# PUT YOUR HANDLER NAMES  INTO THE GLOBAL gControllerHandlers AS A COMMA SEPARATED LIST
put "index,chat" into gControllerHandlers


# THE CONTROLLER HANDLER
command chat
    # LOAD REQUIRED LIBRAIES, MODELS, HELPERS, PLUGINS
end chat

# THE DEFAULT HANDLER
command index
    -- do something here
end index



--| END OF chat.lc
--| Location: ./system/application/controllers/chat.lc
----------------------------------------------------------------------

Save this script as "chat.lc" in system/application/controllers. This controller is associated with an URI like this: example.com/index.lc/chat/ or, if you use a .htaccess file with appropriate mod_rewrite rules: example.com/chat/ (see revIgniter URLs).

The controller handler chat is called first. So, this is a good place to load required libraries, helpers, plugins, models and the database.

As we don't want page redraws while sending new chat messages we use Ajax requests and therefore load the Jquery library:

rigLoaderLoadLibrary "Jquery"

To store the user name, so that all messages will be sent with the associated name, we will use a cookie-based session. So we need the Session library:

rigLoaderLoadLibrary "Session"

Now load the database. Note: If the function does not contain any information in the first parameter it will connect to the group specified in your database config file. For most people, this is the preferred method of use. Make sure that all these settings are correct, that gActiveRecord is set to TRUE and that your database contains the "chat" table. Add the following line to the chat handler:

get rigLoadDatabase()

Further we will need the Asset helper to generate javascript and css location html code:

rigLoadHelper "asset"

So far we have not built the model, but we include it here anyway and write the corresponding code afterwards:

rigLoadModel "chatmodel"

Finally we save the page title in the global variable gData. If, in the view, enclosed in double square brackets, like [[gData["pageTitle"] ]], the values of this array will be automatically combined with the view:

put "revIgniter Chat Application Tutorial" into gData["pageTitle"]

Your chat handler should now look like this:

command chat
	rigLoaderLoadLibrary "Jquery"	
	rigLoaderLoadLibrary "Session"

	get rigLoadDatabase()

	rigLoadHelper "asset"

	rigLoadModel "chatmodel"

	put "revIgniter Chat Application Tutorial" into gData["pageTitle"]
end chat

If no handler is specified in the URI the default handler index is called. This is the handler, which does all the work when the page is loaded the first time. With this handler we check if either the user sent a name via the login form or if there is an item "name" in the session data. Insert the following code into the index handler:

if rigSessUserdata("name") <> FALSE then
	put rigSessUserdata("name") into gData["user"]
	put FALSE into tLogin
else
	put rigVarPost("name", TRUE) into tPOSTname
	if tPOSTname <> FALSE then
		rigSetSessUserdata "name", tPOSTname
		put tPOSTname into gData["user"]
		put FALSE into tLogin
	else
		put TRUE into tLogin
	end if
end if

If there is an item "name" in the session data we save the name in the global variable gData and set a flag to skip the Login page and to load the Chat page instead. If the item "name" is missing in the session data we check if the user sent a name via the login form. This is done with the help of the rigVarPost function.

Note: revIgniter comes with a Cross Site Scripting Hack prevention filter which can either run automatically to filter all POST and COOKIE data that is encountered, or you can run it on a per item basis. In this case the filter is called by setting the second parameter of rigVarPost() to "TRUE".

If the POST data contains a value for "name" we save it in the session data as well as in the global variable gData and set a flag to load the Chat page, otherwise we set a flag to load the Login page.

As indicated above we use jQuery to do the Ajax requests. It is beyond the scope of this tutorial to explain the following statements, which build the jQuery part, in detail. So, if you would like to get more information please read about the Jquery library in the User Guide. Basically the code below implements:

Now add the following code (mind line breaks) to the index handler:

if tLogin is FALSE then
	# USER SENT A NAME
	rigJQopen
		# LOAD MESSAGES
		rigJQfunctionOpen "loadMessages"
			rigJQvar "previousScroll," & rigJQgetR("'#chatbody'", "attr", "'scrollHeight'")

			put "chat/view" into tAjax["url"]
			put "false" into tAjax["cache"]
			put rigJQsetR("'#chatbody'", "html", "data") & \
			rigJQvarR("currentScroll," & rigJQgetR("'#chatbody'", "attr", "'scrollHeight'")) & \
			rigJQconditionR("currentScroll > previousScroll", rigJQanimateR("'#chatbody'", "scrollTop: currentScroll", "'normal'")) into tAjax["success"]
			rigJQajax tAjax
		rigJQcb

		# ADD MESSAGE TO MESSAGES LIST
		rigJQeventOpen "'#submitbtn'", "'click'", TRUE
			rigJQxhRequest "post", "chat/addmsg", "userinput:" && rigJQgetR("'#userinput'", "attr", "'value'")
			rigJQset "'#userinput'", "attr", "'value', ''"
		rigJQeventClose

		# LOGOUT
		rigJQeventOpen "'#logout'", "'click'"
			rigJQvar "logout,confirm('Are you sure you want to end the chat session?');"
			rigJQcondition "logout==true", "window.location = 'logout';"
		rigJQeventClose

		rigJQaddStatement "setInterval (loadMessages, 2500);"

	# THE FOLLOWING COMMAND RETURNS THE JQUERY SCRIPT
	rigJQclose
	put the result into gData["JQscript"]

	# GET DATA FROM DATABASE
	put getMsgData() into gData["msgList"]

end if

Note: If you use jQuery version >= 1.6 replace the first two jQuery attr methods with the jQuery prop method.

The last statement gets the messages list from the database with the help of the getMsgData function. This function needs to be implemented in the model later.

We need to make sure that the page will not be cached. So, we set a server header, which the Output library will send for you when outputting the final rendered display. Add the following line to the index handler:

rigSetHeader "Cache-Control: no-cache"

All that is left to do is to load the view files, which we will build later. As mentioned earlier we split the page into view files, which represent the header, the content and the footer. Header and footer are identical as to the Login page and the Chat page. The content is different. So, if there is no user name specified, we load the login content, otherwise the chat content:

get rigLoadView("chatheader")
if tLogin is TRUE then
	get rigLoadView("chatlogin")
else
	get rigLoadView("chatcontent")
end if
get rigLoadView("chatfooter")

Your index handler should now look like this:

command index
	if rigSessUserdata("name") <> FALSE then
		put rigSessUserdata("name") into gData["user"]
		put FALSE into tLogin
	else
		put rigVarPost("name", TRUE) into tPOSTname
		if tPOSTname <> FALSE then
			rigSetSessUserdata "name", tPOSTname
			put tPOSTname into gData["user"]
			put FALSE into tLogin
		else
			put TRUE into tLogin
		end if
	end if

	if tLogin is FALSE then
		rigJQopen
			rigJQfunctionOpen "loadMessages"
				rigJQvar "previousScroll," & rigJQgetR("'#chatbody'", "attr", "'scrollHeight'")

				put "chat/view" into tAjax["url"]
				put "false" into tAjax["cache"]
				put rigJQsetR("'#chatbody'", "html", "data") & \
				rigJQvarR("currentScroll," & rigJQgetR("'#chatbody'", "attr", "'scrollHeight'")) & \
				rigJQconditionR("currentScroll > previousScroll", rigJQanimateR("'#chatbody'", "scrollTop: currentScroll", "'normal'")) into tAjax["success"]
				rigJQajax tAjax
			rigJQcb

			rigJQeventOpen "'#submitbtn'", "'click'", TRUE
				rigJQxhRequest "post", "chat/addmsg", "userinput:" && rigJQgetR("'#userinput'", "attr", "'value'")
				rigJQset "'#userinput'", "attr", "'value', ''"
			rigJQeventClose

			rigJQeventOpen "'#logout'", "'click'"
				rigJQvar "logout,confirm('Are you sure you want to end the chat session?');"
				rigJQcondition "logout==true", "window.location = 'logout';"
			rigJQeventClose

			rigJQaddStatement "setInterval (loadMessages, 2500);"

		rigJQclose
		put the result into gData["JQscript"]

		put getMsgData() into gData["msgList"]

	end if

	rigSetHeader "Cache-Control: no-cache"

	get rigLoadView("chatheader")
	if tLogin is TRUE then
		get rigLoadView("chatlogin")
	else
		get rigLoadView("chatcontent")
	end if
	get rigLoadView("chatfooter")
end index

Note: If you use jQuery version >= 1.6 replace the first two jQuery attr methods with the jQuery prop method.

Now we need a handler, which adds new messages to the database. It takes the POST data, checks if the message is not empty and, along with the session data, hands it over to the model, which is responsible for database related tasks. Add the following handler to the controller script:

command addmsg
	put rigSessUserdata("name") into tName
	put rigVarPost("userinput") into tMsg
	
	if (tMsg <> FALSE) and (tMsg <> "") then
		_addMsg tName, tMsg
	end if
end addmsg

You may notice that the name of the model handler _addMsg has an underscore as the name prefix. This is to hide certain handlers from public access. These handlers will not be served via a URL request.

Note: Don't forget to add the handler name "addmsg" to the global variable gControllerHandlers at the top of the page.

The next handler to add to the controller script gets the data from the model, stores it in the global variable gData and loads the view chatlist.lc, which consists of the messages list only:

command view 
	put getMsgData() into gData["msgList"]

	get rigLoadView("chatlist")
end view

Of course, later we need to make sure that the model contains a getMsgData function.

Note: Add the handler name "view" to the global variable gControllerHandlers at the top of the page.

The last handler is used to end the chat session. If there is a user name, it sends the user name and a "User has left ..." message to the model to add this data to the database. Then it destroys the session data as it is not needed anymore and loads the appropriate view files to display the login page again. Add the following code to the controller:

command logout
	put rigSessUserdata("name") into tName
	if tName <> FALSE then
		put "User" && tName && "has left the chat session." into tMsg
		_addMsg tName, tMsg
	end if
	
	rigSessDestroy
	
	get rigLoadView("chatheader")
	get rigLoadView("chatlogin")
	get rigLoadView("chatfooter")
end logout

Note: Add the handler name "logout" to the global variable gControllerHandlers.

Model

The model is responsible for database related tasks. In this case our model serves two purposes: It gets messages from the database and it stores new messages in the database. As described above, the controller calls a model function named getMsgData and a model handler named _addMsg. We will now build a model consisting of these two handlers.

We start with the basic prototype of a model script:

<?rev
put gBASEPATH into gBASEPATH

if gBASEPATH is "gBASEPATH" then
	put "No direct script access allowed."
	exit to top
end if


--| END OF chatmodel.lc
--| Location:  ./system/application/models/chatmodel.lc
----------------------------------------------------------------------

As specified in the chat handler of the controller script, name this file "chatmodel.lc" and save it in system/application/models.

Add the following lines to build the getMsgData function, which is used to get messages from the database:

function getMsgData
	rigDbOrderBy "id", "ASC"
	put rigDbGet("chat") into tQueryResult

end getMsgData

First we set an ORDER BY clause, which orders the query result ascending by a column named "id". As this column is auto-incrementing this means that new messages will be located at the bottom and old messages at the top of the list. Then we retrieve the data from the database and store the result, an array, in a variable.

Next, we check if the number of rows of the table is greater than 0. If not, there are no messages and we simply return FALSE. Insert the following lines right after "tQueryResult":

	if tQueryResult["numrows"] > 0 then

	end if
	return FALSE

As we don't want to keep outdated chat messages we limit the number of table rows stored to 20. We do this by deleting 11 messages if the number of rows is greater than 20. Note: These figures are arbitrary. Insert the following code right after the conditional statement:

		if tQueryResult["numrows"] > 20 then
			get rigDbQuery("DELETE LOW_PRIORITY FROM chat ORDER BY id ASC LIMIT 11")

			rigDbOrderBy "id", "ASC"
			put rigDbGet("chat") into tQueryResult
		end if

Now we loop through the rows of the chat table and build the messages list adding all the necessary html tags. This list will be returned by the function. Complete the function by inserting the following lines right after the code above:

		repeat with i = 1 to tQueryResult["numrows"]
			put rigDbRow(i) into tRowData
			put tRowData["time"] into tTime
			convert tTime to short time

			if tRowData["msg"] is "User" && tRowData["user"] && "has left the chat session." then
				put "<li><i>(" & tTime & ")" && tRowData["msg"] & "</i></li>" & return after tMsgData
			else

				put "<li>(" & tTime & ") <b>" & tRowData["user"] & "</b>: " & tRowData["msg"] & "</li>" & return after tMsgData
			end if
		end repeat
		return tMsgData

Note: The table row data is stored in an array where the keys are the table field names.This array can be accessed with the help of the rigDbRow function.

Your getMsgData function should now look like this:

function getMsgData
	rigDbOrderBy "id", "ASC"
	put rigDbGet("chat") into tQueryResult

	if tQueryResult["numrows"] > 0 then
		if tQueryResult["numrows"] > 20 then
			get rigDbQuery("DELETE LOW_PRIORITY FROM chat ORDER BY id ASC LIMIT 11")

			rigDbOrderBy "id", "ASC"
			put rigDbGet("chat") into tQueryResult
		end if
		repeat with i = 1 to tQueryResult["numrows"]
			put rigDbRow(i) into tRowData
			put tRowData["time"] into tTime
			convert tTime to short time

			if tRowData["msg"] is "User" && tRowData["user"] && "has left the chat session." then
				put "<li><i>(" & tTime & ")" && tRowData["msg"] & "</i></li>" & return after tMsgData
			else
				put "<li>(" & tTime & ") <b>" & tRowData["user"] & "</b>: " & tRowData["msg"] & "</li>" & return after tMsgData
			end if
		end repeat
		return tMsgData

	end if
	return FALSE
end getMsgData

Add the following code to complete the model script:

command _addMsg pName pMsg
	put pName into tData["user"]
	put pMsg into tData["msg"]
	put the seconds into tData["time"]
	get rigDbInsert("chat", tData)
end _addMsg

This handler is called by the controller. It puts a new message along with the user name and the current time into an array and inserts this data into the "chat" table.
Now, that's all about the model script and all that is left to do is to build the view files.

View

As mentioned above, the Login page and the Chat page use the same header and footer. The content is different. So, we split the pages into three separate view files. Start with the header view and save the following code in system/application/views as chatheader.lc:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">

This is just the doctype declaration and a meta description. Now add the page title:

<title>[[gData["pageTitle"] ]]</title>

Remember: We stored the page title earlier, when we built the controller, in the global variable gData.

Add the jQuery framework:

<? return rigJQuerySource() ?>

Here, we call a Jquery library function, which returns a html string linking to jQuery.

Add the stylesheet:

<? return rigCssAsset("chat.css") ?>

This is an asset helper function, which generates a CSS asset location html code.

Add the jQuery script, the code, which is needed to do all the XHTML requests:

[[gData["JQscript"] ]]

Complete the chatheader.lc script with the following lines:

</head>
	
	<body>
		<div class="page_margins">

Your chatheader.lc script should now look like this:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">

	<title>[[gData["pageTitle"] ]]</title>

	<? return rigJQuerySource() ?>

	<? return rigCssAsset("chat.css") ?>

	[[gData["JQscript"] ]]

</head>

<body>
	<div class="page_margins">

There is not much to say about the footer. Just save the following code as "chatfooter.lc" in system/application/views:

	<div id="footer">
		<hr />
		<p>revIgniter Chat Application Tutorial</p>
	</div>

	</div>
</body>
</html>

There is nothing special with the content of the Login page either. So, save the following code in system/application/views as "chatlogin.lc":

<div id="login">
	<p>Please enter your name.</p>
	<form action="http://example.com/chat/" method="post">

		<label for="name">Name:</label>
		<input type="text" name="name" id="name" size="20" maxlength="12" placeholder="Name" />
		<input type="submit" name="loginbtn" id="loginbtn" value="Login" />
	</form>
</div>

Note: Don't forget to replace the action attribute with the proper URL.

Now save the code below as "chatcontent.lc" in system/application/views:

<div id="chat">
	<div id="chatheader">
	<p id="username">Welcome, <b>[[gData["user"] ]]</b></p>
	<p id="logout"><a href="#">Logout</a></p>
</div>

Here we display the name of the user, which we stored in our global variable gData. Further there is a link, which is used by the jQuery script to end the chat session.

To display the messages list add the following lines:

	<div id="chatbody">
		<ul>
		[[gData["msgList"] ]]
		</ul>
	</div>

This unordered list is built dynamically when the page is loaded the first time or whenever the message data is updated by the jQuery script, which loads the chatlist view to replace the data in this list with new data.

To complete the chatcontent view add the HTML form, which is used to send messages:

	<div id="chatfooter">
		<form action="http://example.com/chat/addMsg" method="post">
			<input name="userinput" type="text" id="userinput" size="64" maxlength="100" placeholder="Message" />
			<input name="submitbtn" type="submit"  id="submitbtn" value="Send" />
		</form>
	</div>
</div>

You may replace the action attribute with the proper URL, if you like, but it is not necessary because the jQuery script prevents the default action anyway. So, this attribute could even be empty. The URI is correct though. It specifies a controller named "chat" and a handler named "addMsg". Check out the jQuery script, and you will notice, that it uses this URI to post new messages.

Your chatcontent view should now look like this:

<div id="chat">
	<div id="chatheader">
		<p id="username">Welcome, <b>[[gData["user"] ]]</b></p>
		<p id="logout"><a href="#">Logout</a></p>
	</div>
	<div id="chatbody">
		<ul>
		[[gData["msgList"] ]]
		</ul>
	</div>
	<div id="chatfooter">
		<form action="http://example.com/chat/addMsg" method="post">
			<input name="userinput" type="text" id="userinput" size="64" maxlength="100" placeholder="Message" />
			<input name="submitbtn" type="submit"  id="submitbtn" value="Send" />
		</form>
	</div>
</div>

As mentioned earlier, the jQuery script contains a function, which loads the messages list. This is done by sending a request to chat/view. Then the view handler puts the messages list into the gData variable and loads the chatlist view, which is used by the Ajax request to replace the HTML code inside the div with the id "chatbody".

This view consists of the following three lines of code, which you should save as "chatlist.lc" in system/application/views:

<ul>
	[[gData["msgList"] ]]
</ul>

That's it, your chat application should work as expected.

Conclusion

There is still work to be done to turn this into a full featured chat application, but nonetheless this sample illustrates: