Simple Chat Application

This tutorial uses a simple chat application to show you how to build dynamic web pages with revIgniter using ASYNergy, a JavaScript framework for making network requests and changing things on the page.

Note: This tutorial replaces the "Simple Chat Application" tutorial which was dependent on the JQuery library and JQuery. The JQuery library is hereby declared deprecated and is no longer being developed.

Introduction

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


 
To set up this application, 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, build the table using the table structure below and store the SQLite file in application/db/. If you like you can use the tutorials.sqlite database located in the application/db folder.

Top of Page

The Stylesheet

*,
*::before,
*::after {
  box-sizing: border-box;
}

:root {
  -moz-tab-size: 4;
  tab-size: 4;
}

html {
  line-height: 1.15;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
}

body {
  color: rgb(199, 199, 199);
  font-family:
  system-ui,
  -apple-system,
  'Segoe UI',
  Roboto,
  Helvetica,
  Arial,
  sans-serif,
  'Apple Color Emoji',
  'Segoe UI Emoji';
  font-size: 16px;
}

hr {
  height: 0;
}

b,
strong {
  font-weight: bolder;
}

button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
}

button,
select {
  text-transform: none;
}

button,
[type='button'],
[type='reset'],
[type='submit'] {
  -webkit-appearance: button;
}

::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

:-moz-focusring {
  outline: 1px dotted ButtonText;
}

#login,
#logo,
#chat {
  margin-left: auto;
  margin-right: auto;
}

#logo {
  display: block;
  width: 162px;
  margin-bottom: 20px;
  margin-top: 20px;
}

.formRow,
#login,
#chat {
  display: flex;
}

.formRow {
  justify-content: flex-end;
}

.formRow,
.formRow > input {
  padding: .5em;
}

.formRow > label {
  padding: .5em .5em .5em 0;
}

.formRow > input {
  flex: 5;
}

.formRow > label,
input#submitbtn {
  flex: 1;
}

#userinput,
#submitbtn {
  margin: .5em;
}

ul#list {
  padding: 0;
}

hr {
  color: rgba(199, 199, 199, 0.5);
}

body {
  padding-left: 20px;
  padding-right: 20px;
}

body,
input[type=text] {
  background-color: rgb(43, 49, 55);
}

#login,
#chat {
  flex-direction: column;
}

#login {
  max-width: 400px;
  margin-top: 20px;
  padding: 1em;
}

#login,
#chat,
input[type=submit],
input[type=text] {
  border: 1px solid rgb(199, 199, 199, 0.5);
  border-radius: 8px;
}

#login p {
  margin: 0px;
  padding: 0 .5em .5em;
}

input[type=submit] {
  background-color: rgb(199, 199, 199);
  border: none;
  cursor: pointer;
}

input[type=submit]:hover {
  background-color: rgb(118, 205, 0);
  transition: background-color 0.8s;
}

input[type=text] {
  color: rgb(255, 255, 255);
}

a,
input[type=submit] {
  text-decoration: none;
}

#chat {
  max-width: 500px;
}

#chatheader {
  height: 40px;
}

p#username,
p#logout {
  font-size: 83.33%;
  margin-top: 12px;
}

p#username {
  float: left;
  margin-left: 1.5em;
}

p#logout {
  text-align: right;
  float: right;
  margin-right: 1.5em;
}

a {
  color: rgb(118, 205, 0);
}

a:hover {
  text-decoration: underline;
}

#chatbody {
  height: 214px;
  overflow: auto;
  border-bottom: 1px solid rgb(118, 205, 0);
  border-top: 1px solid rgb(118, 205, 0);
}

#chatbody li {
  background: rgb(107, 107, 107);
  border-top-left-radius: 5px;
  border-top-right-radius: 5px;
  color: rgb(224, 224, 224);
  font-size: 75%;
  height: 30px;
  padding-left: 12px;
  padding-top: 1px;
  margin: 6px 1.5em 0 1.5em;
  list-style-type: none;
}

#list {
  margin: 6px 0 6px 0;
}

#userinput {
  width: 100%;
}

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

Top of Page

The Table (SQLite)

CREATE TABLE "chat" (
  "id" INTEGER NOT NULL,
  "user" TEXT(20) NOT NULL,
  "msg" TEXT NOT NULL,
  "time" INTEGER(9) NOT NULL,
  PRIMARY KEY("id" AUTOINCREMENT)
);

Top of Page

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 and helpers 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:

<?lc

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


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

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



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

Save this script as "chat.lc" in 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).

Top of Page

The Chat Handler

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

Actually we would need to load the Asset helper to generate JavaScript and CSS location html code, but this helper is automatically loaded by the ASYNergy library to get the required ASYNergy JavaScript code which is then stored in gData["asynergyScript"]. So, there are two helpers left to be loaded. The URL helper, which used to generate a link, and the Form helper, which is needed to generate the form action markup.

put "url,form" into tHelpers
rigLoadHelper tHelpers

Since we don't want page redraws while sending new chat messages, we use AJAX requests with the help of ASYNergy and therefore we load the ASYNergy library:

rigLoaderLoadLibrary "ASYNergy"

To store the user name, so that all chat 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 connection 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 gRigA["activeRecord"] is set to TRUE and that your database contains the "chat" table. Add the following line to the chat handler:

get rigLoadDatabase()

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

rigLoadModel "chatmodel"

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 merged with the view:

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

Finally we add the login form action markup to gData:

put rigFormOpen("chat") into gData["loginFormOpen"]

This will generate a HTML markup like:

<form action="https://example.com/chat" method="post" accept-charset="utf-8">

or, in case you enabled CSRF protection:

<form action="https://example.com/chat" method="post" accept-charset="utf-8">
<input type="hidden" name="csrfTokenName" value="bb8e156c-9aa5-42be-b573-dc0b533ca3da" asyn:csrf="csrfTokenName" />

Your chat handler should now look like this:

command chat
  # HELPERS NEEDED
  put "url,form" into tHelpers
  rigLoadHelper tHelpers

  # LIBRARIES NEEDED
  rigLoaderLoadLibrary "ASYNergy"
  rigLoaderLoadLibrary "Session"

  # DATABASE
  get rigLoadDatabase()

  # MODEL
  rigLoadModel "chatmodel"

  # SET PAGE TITLE
  put "ASYNergy Chat Application Tutorial" into gData["pageTitle"]

  # LOGIN FORM ACTION
  put rigFormOpen("chat") into gData["loginFormOpen"]
end chat

Top of Page

The Index Handler

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. First we call the rigAnchor() function to generate a link used on the Chat page to load the Login view by calling the "logout" handler.

put rigAnchor("chat/logout", "Logout") into gData["logoutAnchor"]

Then we add code that creates the form's action markup including an ASYNergy HTML attribute asyn:submit.prevent whose value, in this case "addMsg", is set to the name of the handler to be called or to the value of the ASYNergy HTML attribute of the mutable HTML element respectively. Adding this attribute to the form tells ASYNergy to process it before submission. So, this prevents the normal submit action when the user submits the form.

put "addMsg" into tFormAttrA["asyn:submit.prevent"]
put rigFormOpen("addMsg", tFormAttrA) into gData["chatFormOpen"]

This will generate a HTML markup like:

<form action="https://example.com/addMsg" method="post" accept-charset="utf-8" asyn:submit.prevent="addMsg">

or, in case you enabled CSRF protection:

<form action="https://example.com/addMsg" method="post" accept-charset="utf-8" asyn:submit.prevent="addMsg">
<input type="hidden" name="csrfTokenName" value="bb8e156c-9aa5-42be-b573-dc0b533ca3da" asyn:csrf="csrfTokenName" />

Within the index 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) and (tPOSTname <> empty) then
      
    put textEncode(tPOSTname, "UTF-8") into tPOSTname
      
    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 view, otherwise we set a flag to load the login view.

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 view, otherwise the chat view:

get rigLoadView("chatHeaderView")
if tLogin is TRUE then
  get rigLoadView("chatLoginView")
else
  # GET DATA FROM DATABASE
  put getMsgData() into gData["msgList"]
  put textEncode(gData["msgList"], "UTF-8") into gData["msgList"]
  get rigLoadView("chatContentView")
end if
get rigLoadView("chatFooterView")

The getMsgData() function gets the chat messages list from the database. This function needs to be implemented in the model later.

Your index handler should now look like this:

command index
  # LINK TO THE LOGIN PAGE
  put rigAnchor("chat/logout", "Logout") into gData["logoutAnchor"]
  
  # GENERATE FORM ACTION MARKUP
  put "addMsg" into tFormAttrA["asyn:submit.prevent"]
  put rigFormOpen("addMsg", tFormAttrA) into gData["chatFormOpen"]

  # CHECK IF WE NEED TO SEND THE LOGIN PAGE OR THE CHAT PAGE
  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) and (tPOSTname <> empty) then
      
      put textEncode(tPOSTname, "UTF-8") into tPOSTname
      
      rigSetSessUserdata "name", tPOSTname
      put tPOSTname into gData["user"]
      put FALSE into tLogin
    else
      put TRUE into tLogin
    end if
  end if

  # MAKE SURE THAT THE PAGE WILL NOT BE CACHED
  rigSetHeader "Cache-Control: no-cache"

  # LOAD THE VIEW FILES
  get rigLoadView("chatHeaderView")
  if tLogin is TRUE then
    get rigLoadView("chatLoginView")
  else
    # GET DATA FROM DATABASE
    put getMsgData() into gData["msgList"]
    put textEncode(gData["msgList"], "UTF-8") into gData["msgList"]
    get rigLoadView("chatContentView")
  end if
  get rigLoadView("chatFooterView")
end index

Top of Page

The addMsg Handler

Now we need a handler, which adds new messages to the database. It takes the chat user name from the session data and the message from the ASYNergy JSON data processed by the ASYNergy library. The handler checks that the message is not empty and passes it along with the session data to the model, that is responsible for database related tasks. The model function getMsgData() returns the chat messages from the database in form of an HTML list. Then you could call rigAsynRespond tMsgList to return the updated chat list to the client, and you were done. But we want to clear the message input field. So, we need to create the response data using the ASYNergy function rigAsynAddResponseData() and add response data for the second mutable ASYNergy HTML element. Finally calling the model handler flattenDB limits the maximum number of database table rows to 22 by deleting the oldest message if the maximum number is exceeded.

Add the following handler to the controller script:

command addMsg
  put rigSessUserdata("name") into tName
  put rigAsynElemData("msg") into tMsg
  
  if tMsg <> empty then
    _addMsg tName, tMsg
  end if
  
  put getMsgData() into tMsgList

  # IF YOU DON'T INTEND TO CLEAR THE USER INPUT FIELD
  # CALL rigAsynRespond tMsgList AND YOU ARE DONE
  # OTHERWISE...
  put rigAsynAddResponseData(tMsgList, , , "addMsg") into tResponseA
  put rigAsynAddResponseData("", , , "userinput") into tResponseA
  
  rigAsynRespond tResponseA
  
  flattenDB 22
end addMsg

Note: Don't forget to add the handler name "addMsg" to the global variable gControllerHandlers at the top of the controller script.

Top of Page

The chatList Handler

The next handler to add to the controller script is called by ASYNergy every two and a half seconds to keep the messages list, displayed on the page, updated. It fetches all chat messages from the model in the form of an HTML list, creates case-specific response data, and sends it to the client. Actually you could call rigAsynRespond tMsgList and you were done, but in this particular case the name of the handler called by ASYNergy ("chatList") is different from the value ("addMsg") of the HTML attribute of the corresponding mutable ASYNergy HTML element to be updated.

command chatList
  put getMsgData() into tMsgList
  put rigAsynAddResponseData(tMsgList, , , "addMsg") into tResponseA
  
  rigAsynRespond tResponseA
end chatList

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

Note: Add the handler name "chatList" to the global variable gControllerHandlers at the top of the controller script.

Top of Page

The Logout Handler

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) and (tName <> empty) then
    put "User" && tName && "has left the chat session." into tMsg
    _addMsg tName, tMsg, FALSE
  end if
  
  rigSessDestroy
  
  get rigLoadView("chatHeaderView")
  get rigLoadView("chatLoginView")
  get rigLoadView("chatFooterView")
end logout

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

The first line of the controller's code should now look like this:

put "chat,index,addMsg,chatList,logout" into gControllerHandlers

Top of Page

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, a model handler named _addMsg, and a model handler named flattenDB. We will now build a model consisting of these three handlers.

We start with the basic prototype of a model script only stack:

script "chatmodel"


global gRigA


on libraryStack
  if (gRigA is not an array) and (the environment is "server") then
    put "No direct script access allowed."
    exit to top
  end if

  if the short name of the target <> the short name of me then
    pass libraryStack
  end if
end libraryStack


--| END OF chatmodel.livecodescript
--| Location:  ./application/models/chatmodel.livecodescript
----------------------------------------------------------------------

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

Top of Page

The getMsgData Handler

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

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 first line of 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
      
      put textDecode(tRowData["user"], "UTF-8") into tRowData["user"]
      put textDecode(tRowData["msg"], "UTF-8") into tRowData["msg"]

      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
    repeat with i = 1 to tQueryResult["numrows"]
      put rigDbRow(i) into tRowData
      
      put tRowData["time"] into tTime
      convert tTime to short time
      
      put textDecode(tRowData["user"], "UTF-8") into tRowData["user"]
      put textDecode(tRowData["msg"], "UTF-8") into tRowData["msg"]

      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

Top of Page

The _addMsg Handler

Add the _addMsg handler to the model script:

command _addMsg pName pMsg pEncode
  put pName into tData["user"]
  put pMsg into tData["msg"]
  put the seconds into tData["time"]

  if pEncode <> FALSE then
    put textEncode(tData["msg"], "UTF-8") into tData["msg"]
  end if
  
  get rigDbInsert("chat", tData)
end _addMsg

This handler puts a new message along with the user name and the current time into an array and inserts this data into the "chat" table.

Top of Page

The flattenDB Handler

As we don't want to keep outdated chat messages, we limit the number of table rows stored calling the flattenDB handler. The optional parameter specifies the maximum number of messages allowed to be stored in the database table. 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.

command flattenDB pMaxMessages
  if (pMaxMessages is empty) or (pMaxMessages is not a number) then
    put 20 into pMaxMessages
  end if

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

end flattenDB

Now, if the number of table rows is greater then the maximum number of rows allowed, we loop through the rows of the chat table and delete the rows exceeding the limit. Add the following code to the flattenDB handler:

if tQueryResult["numrows"] > 0 then
    if tQueryResult["numrows"] > pMaxMessages then
      
      put tQueryResult["numrows"] - pMaxMessages into tNumLoops
      
      repeat with i = 1 to tNumLoops
        put rigDbRow(1) into tRowData
        put tRowData["id"] into tID

        rigDbWhere "id", tID
        get rigDbDelete("chat")
        
        rigDbOrderBy "id", "ASC"
        get rigDbGet("chat")
      end repeat

    end if -- if tQueryResult["numrows"] > pMaxMessages
  end if -- if tQueryResult["numrows"] > 0 	

Your flattenDB function should now look like this:

command flattenDB pMaxMessages
  local tQueryResult, tNumLoops, tRowData, tID

  if (pMaxMessages is empty) or (pMaxMessages is not a number) then
    put 20 into pMaxMessages
  end if

  rigDbOrderBy "id", "ASC"
  put rigDbGet("chat") into tQueryResult
  
  if tQueryResult["numrows"] > 0 then
    if tQueryResult["numrows"] > pMaxMessages then
      
      put tQueryResult["numrows"] - pMaxMessages into tNumLoops
      
      repeat with i = 1 to tNumLoops
        put rigDbRow(1) into tRowData
        put tRowData["id"] into tID

        rigDbWhere "id", tID
        get rigDbDelete("chat")
        
        rigDbOrderBy "id", "ASC"
        get rigDbGet("chat")
      end repeat

    end if
  end if
end flattenDB

That's all about the model script and all that is left to do is to build the view files.

Top of Page

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 particular page into three separate view files.

The Header View

Start with the header view and save the following code in application/views as chatHeaderView.lc:

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

This is just the doctype declaration and meta data. 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 stylesheet and complete the chatHeaderView.lc script:

  <? return rigCssAsset("chat.css") ?>
</head>
  <body>

rigCssAsset() is an asset helper function, which generates a CSS asset location html code.

Your chatHeaderView.lc script should now look like this:

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

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

  <? return rigCssAsset("asynChat.css") ?>
</head>
  <body>

Top of Page

The Footer View

The footer includes the ASYNergy script and an additional script which shows how to tap into the inner workings of ASYNergy using hooks. There is a JavaScript function which scrolls the chat messages list to the bottom. This function is called whenever the ASYNergy HTML element with the id "chatbody" is initialized (when the page is loaded) and whenever ASYNergy has processed a request after the user has entered a message.

Save the following code as "chatFooterView.lc" in application/views:

  <div id="footer">
    <hr />
    <p>ASYNergy Chat Application Tutorial</p>
  </div>
    
  <script>
  document.addEventListener("DOMContentLoaded", () => {
    ASYNergy.hook('element.initialized', (el, agent, eventType) => {
      if (el.id === 'chatbody') { scrollToBottom('#' + el.id) };
    });
    
    ASYNergy.hook('message.processed', (el, agent, eventType) => {
      if (eventType !== 'poll') { scrollToBottom('#chatbody') };
    });
  });
  function scrollToBottom(elID) {
    document.querySelector(elID).scroll({ top: document.querySelector(elID).scrollHeight, left: 0,behavior: "smooth" });
  };
  </script>
  
  [[gData["asynergyScript"] ]]
  </body>
</html>

Top of Page

The Login View

There is nothing special with the content of the login view. So, save the following code in application/views as "chatLoginView.lc":

<div class="container">
  <img src="https://revigniter.com/assets/img/tutorialLogo.png"  width="162" height="38" alt="revIgniter Logo" id="logo" />

  <div id="login">
    <p>Please enter your name.</p>
    [[gData["loginFormOpen"] ]]

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

Top of Page

The Chat View

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

  <div class="container">
    <img src="https://revigniter.com/assets/img/tutorialLogo.png"  width="162" height="38" alt="revIgniter Logo" id="logo" />

    <section class="main">
    
      <div id="chat">
        <div id="chatheader">
          <p id="username">Welcome, <b>[[gData["user"]]]</b></p>
          <p id="logout">[[gData["logoutAnchor"] ]]</p>
        </div>

Here we display the name of the user, which we stored in our global variable gData["user"]. Further there is the link which we stored earlier in the gData["logoutAnchor"] variable calling the logout handler of the chat controller.

To display the messages list add the following lines:

    <div id="chatbody" asyn:poll.2500ms="chatList">
      <ul id="list" asyn:mutable="addMsg">
        [[gData["msgList"]]]
      </ul>
    </div>

This unordered list is created dynamically when the page is loaded the first time or whenever the messages data is updated by ASYNergy, replacing the data inside the ASYNergy mutable HTML element with the response data. The HTML attribute asyn:poll.2500ms="chatList" means that ASYNergy sends an AJAX request every two and a half seconds that calls the chat controller's "chatList" handler. The asyn:mutable="addMsg" attribute of the ASYNergy mutable element means that the content inside the element's opening and closing tag will be replaced with the respective response data.

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

        <div id="chatfooter">
          [[gData["chatFormOpen"] ]]
            <div class="formRow">
              <input name="userinput" type="text" id="userinput" asyn:mutable="userinput" asyn:transmit="msg" size="64" maxlength="100" placeholder="Message" />
              <input name="submitbtn" type="submit"  id="submitbtn" value="Send" />
            </div>
          </form>
        </div>
      </div>

  </section>

  </div>

There are two ASYNergy HTML attributes. asyn:mutable="userinput" marks the input field as "mutable". This ensures that this field can be set to any value after submission of the form. In our case the controller's addMsg handler sets it's value to empty. asyn:transmit="msg" is used to send the input data to the server. The controller has then access to this data calling rigAsynElemData("msg").

Your chat content view should now look like this:

  <div class="container">
    <img src="https://revigniter.com/assets/img/tutorialLogo.png"  width="162" height="38" alt="revIgniter Logo" id="logo" />

    <section class="main">
    
      <div id="chat">
        <div id="chatheader">
          <p id="username">Welcome, <b>[[gData["user"]]]</b></p>
          <p id="logout">[[gData["logoutAnchor"] ]]</p>
        </div>
        <div id="chatbody" asyn:poll.2500ms="chatList">
          <ul id="list" asyn:mutable="addMsg">
            [[gData["msgList"]]]
          </ul>
        </div>
        <div id="chatfooter">
          [[gData["chatFormOpen"] ]]
            <div class="formRow">
              <input name="userinput" type="text" id="userinput" asyn:mutable="userinput" asyn:transmit="msg" size="64" maxlength="100" placeholder="Message" />
              <input name="submitbtn" type="submit"  id="submitbtn" value="Send" />
            </div>
          </form>
        </div>
      </div>

  </section>

  </div>

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

Top of Page

Conclusion

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