ASYNergy Form Validation

This tutorial shows how to validate form data and provide real-time feedback to the user without reloading the page using ASYNergy, a JavaScript framework for network requests and changes on the page.

Introduction

The page consists of a registration form where you enter an email address, password and a password confirmation.

When the email input field loses focus while filling out the form, ASYNergy sends an AJAX request to ensure that the entered string is a valid email address and that the address is not already stored in the database. If something is wrong, an error message will be displayed right below the email input field.

While typing in the password field ASYNergy sends AJAX requests to check if the length of the entered string is between 8 and 24 characters. If this requirement is not met, an error message will be displayed right below the password input field.

When typing in the password confirmation input field ASYNergy sends AJAX requests to check if the two password strings match. As long as this requirement is not met, an error message will be displayed right below the password confirmation input field.
 

form


 
To set up this form, 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/asynReg.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;
}

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

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

a:hover {
  text-decoration: underline;
}

.container {
  padding-left: 15px;
  padding-right: 15px;
}

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

.main,
section a {
  display: flex;
}

.main {
  flex-direction: column;
  max-width: 400px;
  margin-left: auto;
  margin-right: auto;
  padding: 36px;
}

form {
  margin-bottom: 8px;
}

section a {
  align-self: center;
  color: rgb(118, 205, 0);
}

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

input[type=text],
input[type=password],
input[type=submit] {
  margin: 8px 0 20px;
  width: 100%;
}

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

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

section,
input[type=submit],
input[type=text],
input[type=password] {
  border-radius: 8px;
}

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

section,
input[type=text],
input[type=password] {
  border: 1px solid rgb(199, 199, 199, 0.5);
}

#footer {
  font-size: .75em;
  text-align: center;
}

hr {
  color: rgba(199, 199, 199, 0.5);
  margin: 40px 15px 0 15px;
}

.errorMessage {
  color: rgb(250, 80, 80);
  font-size: .75em;
  margin-top: -10px;
}

Top of Page

The Table (SQLite)

CREATE TABLE "users" (
  "id" INTEGER NOT NULL,
  "email" TEXT NOT NULL,
  "password" TEXT NOT NULL,
  PRIMARY KEY ("id")
);

Top of Page

Controller

The controller consists of two handlers commonly used in controllers and six additional ones. The first handler asynRegister 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 that checks if the string entered into the email input field is a valid email address, a handler that validates the password entered, one that validates the confirmation password, a handler that is called by clicking on the "Register" button, and finally a "success" handler that loads the page confirming that the registration was successful.

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 "asynRegister,index" into gControllerHandlers


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

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



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

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

Top of Page

The asynRegister Handler

The controller handler asynRegister 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"]. The Form helper needed to generate the form action markup is loaded by the Formvalidation library. So there is only one helper left to be loaded. The URL helper, which is used to generate a link.

rigLoadHelper "url"

Since we don't want page redraws while the user fills out the form and the input is validated, we use AJAX requests with the help of ASYNergy and therefore we load the ASYNergy library. Also, of course, we need the Formvalidation library and the Encryption library to encrypt the passwords:

put "ASYNergy,Formvalidation,Encrypt" into tLibs
rigLoaderLoadLibrary tLibs

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 "users" table. Add the following line to the asynRegister handler:

get rigLoadDatabase()

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

rigLoadModel "asynregistermodel"

Finally we call the rigAnchor() function to generate a link used on the success page that points back to the registration page. We save this link in the global variable gData. If, in the view, enclosed in double square brackets, like [[gData["againAnchor"]]], the values of this array will be automatically merged with the view:

put rigAnchor("asynRegister", "Try it again!") into gData["againAnchor"]

Your asynRegister handler should now look like this:

command asynRegister
  local tLibs
  
  # HELPERS NEEDED, THE ASSET HELPER IS
  # LOADED BY THE ASYNERGY LIBRARY, THE
  # FORM HELPER IS LOADED BY THE
  # FORMVALIDATION LIBRARY
  rigLoadHelper "url"
  
  # LIBRARIES NEEDED
  put "ASYNergy,Formvalidation,Encrypt" into tLibs
  rigLoaderLoadLibrary tLibs
  # DATABASE
  get rigLoadDatabase("asynRegister")
  # MODEL
  rigLoadModel "asynregistermodel"
  
  # LINK ON THE SUCCESS PAGE THAT POINTS BACK TO THE REGISTRATION PAGE
  put rigAnchor("asynRegister", "Try it again!") into gData["againAnchor"]
end asynRegister

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 save the page title in the global variable gData.

put "Asynergy Registration" into gData["pageTitle"]

Then we add code that generates the markup for the logo:

put "logo" into tLogo["id"]
put "Logo" into tLogo["alt"]
put "162" into tLogo["width"]
put "38" into tLogo["height"]
put rigImageAsset("logo.png", tLogo, , TRUE) into gData["logo"]

Note: The fourth parameter of the rigImageAsset() function determines if asset file names should include the timestamp of last modification. Setting this parameter to true avoids issues with browser cached data whenever you modify static files like images, CSS files, JavaScript files or favicons without changing the file name.

Now let's add code that creates the form's action markup including an ASYNergy HTML attribute asyn:submit.prevent whose value, in this case "signup", is set to the name of the controller handler to be called when the user clicks onto the submit button. 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 "signup" into tFormAttrA["asyn:submit.prevent"]
put rigFormOpen("asynRegister", tFormAttrA) into gData["formOpen"]

This will generate a HTML markup like:

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

or, in case you enabled CSRF protection:

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

Then we add code that generates the submit button markup:

put rigSubmitButton("register", "Register") into gData["submit"]

In case a user has JavaScript disabled for some reason so that ASYNergy cannot be loaded, we set up the default form validation mechanism within the index handler. This requires the generation of error messages and the storage of input data for re-populating the form in case the data entered was incorrect. If the entered data is correct, we store it in the database and load the "success" page, otherwise we reload the registration page and display the corresponding error messages.

There is a small but crucial difference to normal form validation, namely we add asyn:mutable attributes to the markup of error messages. These "directives" are used by ASYNergy to display the error messages without reloading the whole page.

Following the code for form validation as described:

# VALIDATE FORM DATA AND GENERATE ERROR MESSAGES
# NO NEED TO SPECIFY THE RULE GROUP WITH THE FIRST PARAMETER
# OF THE rigFormValidRun() FUNCTION BECAUSE IT IS AUTOMATICALLY
# SET TO THE CONTROLLER NAME WHICH IS THE NAME OF THE
# APPROPRIATE RULE GROUP SET IN config/validation.lc
if rigFormValidRun() is FALSE then
  # GET VALIDATION ERRORS
  put rigFormError("email", "<p class=" & quote & "errorMessage" & quote && \
    "asyn:mutable=" & quote & "email" & quote & ">", "</p>") into gData["emailError"]

  put rigFormError("password", "<p class=" & quote & "errorMessage" & quote && \
    "asyn:mutable=" & quote & "password" & quote & ">", "</p>") into gData["pwError"]

  put rigFormError("passwordConfirm", "<p class=" & quote & "errorMessage" & quote && \
    "asyn:mutable=" & quote & "passwordConfirm" & quote & ">", "</p>") into gData["pwConfError"]

  # IF VALIDATION FAILES RE-POPULATE THE FORM
  # AND LOAD PAGE AGAIN
  put rigSetValue("email") into gData["email"]
  put rigSetValue("password") into gData["password"]
  put rigSetValue("passwordConfirm") into gData["passwordConfirm"]

  get rigLoadView("registerMainView")
else
  # OTHERWISE ADD NEW DATA TO THE DATABASE
  # AND LOAD THE SUCCESS PAGE
  put rigVarPOST("email") into tEmail
  put rigVarPOST("password") into tPassword
  put rigEncode(tPassword, "myKey") into tEncPW
    
  if tEncPW <> FALSE then
    insertUser tEmail, tEncPW
    get rigLoadView("registerSuccessView")
  else
    get rigLoadView("registerMainView")
  end if

end if

The insertUser handler inserts the email address and the password of new users into the database. This handler needs to be implemented in the model later.

Your index handler should now look like this:

command index
  local tLogo, tFormAttrA, tEmail, tPassword, tEncPW

  # SET PAGE TITLE
  put "Asynergy Registration" into gData["pageTitle"]
  
  # GENERATE LOGO MARKUP, NO NEED TO LOAD THE ASSET HELPER
  # IT IS LOADED BY THE ASYNERGY HELPER
  put "logo" into tLogo["id"]
  put "Logo" into tLogo["alt"]
  put "162" into tLogo["width"]
  put "38" into tLogo["height"]
  put rigImageAsset("logo.png", tLogo, , TRUE) into gData["logo"]


  # GENERATE FORM OPEN AND SUBMIT BUTTON MARKUP
  put "signup" into tFormAttrA["asyn:submit.prevent"]
  put rigFormOpen("asynRegister", tFormAttrA) into gData["formOpen"]
  put rigSubmitButton("register", "Register") into gData["submit"]
  

  # VALIDATE FORM DATA AND GENERATE ERROR MESSAGES.
  # NO NEED TO SPECIFY THE RULE GROUP WITH THE FIRST PARAMETER
  # OF THE rigFormValidRun() FUNCTION BECAUSE IT IS AUTOMATICALLY
  # SET TO THE CONTROLLER NAME WHICH IS THE NAME OF THE
  # APPROPRIATE RULE GROUP SET IN config/validation.lc
  if rigFormValidRun() is FALSE then
    # GET VALIDATION ERRORS
    put rigFormError("email", "<p class=" & quote & "errorMessage" & quote && \
      "asyn:mutable=" & quote & "email" & quote & ">", "</p>") into gData["emailError"]

    put rigFormError("password", "<p class=" & quote & "errorMessage" & quote && \
      "asyn:mutable=" & quote & "password" & quote & ">", "</p>") into gData["pwError"]

    put rigFormError("passwordConfirm", "<p class=" & quote & "errorMessage" & quote && \
      "asyn:mutable=" & quote & "passwordConfirm" & quote & ">", "</p>") into gData["pwConfError"]

    # IF VALIDATION FAILES RE-POPULATE THE FORM
    # AND LOAD PAGE AGAIN
    put rigSetValue("email") into gData["email"]
    put rigSetValue("password") into gData["password"]
    put rigSetValue("passwordConfirm") into gData["passwordConfirm"]

    get rigLoadView("registerMainView")
  else
    # OTHERWISE ADD NEW DATA TO THE DATABASE
    # AND LOAD THE SUCCESS PAGE
    put rigVarPOST("email") into tEmail
    put rigVarPOST("password") into tPassword
    put rigEncode(tPassword, "myKey") into tEncPW
    
    if tEncPW <> FALSE then
      insertUser tEmail, tEncPW
      get rigLoadView("registerSuccessView")
    else
      get rigLoadView("registerMainView")
    end if

  end if
end index

Top of Page

The email Handler

Now we need handlers that validate user input and are called by ASYNergy while the user is filling out the form.

Add the following handler, which checks whether the validation rules for entering an email address are met, to the controller script. We will implement validation rules later.

# DON'T FORGET TO ADD THE NAMES OF THE HANDLERS BELOW
# TO THE LIST IN THE gControllerHandlers VARIABLE
# AT THE TOP OF THE CONTROLLER SCRIPT.
command email
  local tError

  put rigAsynValidateInput("email") into tError
  rigAsynRespond tError
end email

The rigAsynValidateInput() function included in the ASYNergy library validates input data using the form validation library. The first parameter of this function is the value of the asyn:model attribute of the email input element passed by ASYNergy. The AJAX request sent by ASYNergy also contains the value of the email form field, which is validated according to the rules we will define later.

If the validation fails an error message will be added to the response which is sent by the rigAsynRespond handler. This message replaces the current HTML enclosed by the tags of a "mutable" element whose asyn:mutable attribute has the value "email".

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

Top of Page

The password Handler

The next handler to add to the controller script is called by ASYNergy when the user writes in the password form field.

command password
  local tTransmitted, tTransmittedA, tError
  
  put rigAsynElemData("passwordConfirm") into tTransmitted

  # ONLY COMPARE THE 2 PASSWORD ENTRIES IF THE PASSWORD
  # CONFIRMATION FIELD IS NOT EMPTY
  if tTransmitted <> empty then
    put tTransmitted into tTransmittedA["passwordConfirm"]
  end if
  put rigAsynValidateInput("password", tTransmittedA) into tError
  
  rigAsynRespond tError
end password

The password is validated and if the password confirmation field is not empty, it is checked whether the entered password matches the password confirmation. The handler has access to the value of the password confirmation form field calling rigAsynElemData("passwordConfirm").

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

Top of Page

The passwordConfirm Handler

The next handler is called when the user writes in the password confirmation form field. The rigAsynValidateInput("password,passwordConfirm") function uses the form validation library validates two form fields and checks if the entered password matches the password confirmation.

Add the following code to the controller script:

command passwordConfirm
  local tError

  put rigAsynValidateInput("password,passwordConfirm") into tError

  rigAsynRespond tError
end passwordConfirm

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

Top of Page

The signup Handler

"signup" is the value of the asyn:submit.prevent attribute of the form element. This means that when the user clicks on the submit button, ASYNergy calls the signup handler of the controller:

command signup
  local tErrorA, tEmail, tPassword, tEncPW, tSegments
  
  put rigAsynValidateInput("email,password,passwordConfirm") into tErrorA
  
  if tErrorA is empty then
    put  rigAsynElemData("email") into tEmail
    put  rigAsynElemData("password") into tPassword
    put rigEncode(tPassword, "myKey") into tEncPW
    
    if tEncPW <> FALSE then
      insertUser tEmail, tEncPW
      
      # DISPLAY SUCCESS PAGE
      put "asynRegister/success" into tSegments
      rigAsynNavigate tSegments
    else
      # RELOAD THE REGISTRATION PAGE
      put "asynRegister" into tSegments
      rigAsynNavigate tSegments
    end if
    
  else
    rigAsynRespond tErrorA
  end if -- if tErrorA is empty
end signup

The rigAsynValidateInput("email,password,passwordConfirm") function validates all three input form fields at once and returns an array of error messages if validation fails. If there are no errors the signup handler stores the email address and the encrypted password in the database and loads the "success" page using the rigAsynNavigate handler of the ASYNergy library, otherwise it sends a response to the client including the corresponding error messages.

Of course, later we need to make sure that the model contains an insertUser handler.

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

Top of Page

The success Handler

The following handler will be called by the signup handler if form validation was successful. It just loads the registerSuccessView.lc view.

command success
  get rigLoadView("registerSuccessView")
end success

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

Top of Page

The emailCheck Handler

The last controller handler is an example on how to implement form validation callbacks, which permit you to extend the validation library to meet your needs.

# HANDLER CALLED BY THE CALLBACK RULE callback_emailCheck
command emailCheck pEmail
  if emailIsInDB(pEmail) then
    rigSetMessage "emailCheck", "The email has already been taken"
    return FALSE
  end if

  return TRUE
end emailCheck

The emailIsInDB(pEmail) function checks whether an entered email is already stored in the database. If so, an error message is displayed.

Later we make sure that the model contains an emailIsInDB() function.

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

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

put "asynRegister,index,email,password,passwordConfirm,signup,success,emailCheck" into gControllerHandlers

Top of Page

Validation Rules

The Form Validation library permits you to store all your validation rules organized in groups in a config file. You can organize these rules into "groups". These groups can be loaded automatically when a matching controller/handler is called.

To store the form validation rules, you need to create a file named validation.lc in the application/config folder if you don't have it already:

<?lc

local sValidationConf

# THIS FUNCTION IS MANDATORY
function rigFetchValidationConf
  return sValidationConf
end rigFetchValidationConf

Please note that you MUST add the rigFetchValidationConf() function to your configuration file as shown above.

Then add the validation rules as shown below:

rigAddValidationGrp "asynRegister"
rigAddValidationRules "email", "Email", "trim|requiredR|validEmailR|callback_emailCheck|xssClean", sValidationConf
rigAddValidationRules "password", "Password", "requiredR|minLengthR[8]|maxLengthR[24]", sValidationConf
rigAddValidationRules "passwordConfirm", "Confirm Password", "requiredR|matchesR[password]", sValidationConf

We set the name of the validation group to the name of the controller/handler, in this case to the controller, because validation is done in the index handler that is executed by calling the contoller. This means that we do not need to specify a validation group as a parameter of the rigFormValidRun() function.

The rules in detail are:

All validation rules are stored in the sValidationConf variable.

Top of Page

Model

The model is responsible for database related tasks. In this case our model serves two purposes: It checks if the provided email address is already stored in the database and it stores email addresses and the corresponding passwords in the database.

As described above, the controller calls a model function named emailIsInDB() and a handler named insertUser. We will now build a model consisting of these two handlers.

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

script "asynregistermodel"


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 asynregistermodel.livecodescript
--| Location:  ./application/models/asynregistermodel.livecodescript
----------------------------------------------------------------------

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

Top of Page

The emailIsInDB Function

Add the following lines to build the emailIsInDB() function that is used to check if the provided email address is already stored in the database:

function emailIsInDB pEmail
  local tQueryResult
  
  rigDbWhere "email", pEmail
  put rigDbGet("users") into tQueryResult
  
  if tQueryResult["numrows"] > 0 then
    return TRUE
  end if
  
  return FALSE
end emailIsInDB

First we set a WHERE clause to search for database entries in the "users" table that match the pEmail parameter. Then we run the query using the rigDbGet("users") function. If the query result contains data, i.e. the email address already exists in the database, the function returns "TRUE", otherwise "FALSE".

Top of Page

The insertUser Handler

Add the insertUser handler to the model script:

command insertUser pEmail pPassword
  local tDataA
  
  put pEmail into tDataA["email"]
  put pPassword into tDataA["password"]
  
  get rigDbInsert("users", tDataA)
	
  return it
end insertUser

This handler uses the rigDbInsert() function to insert an email address and a password into the "users" table of the database.

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

Top of Page

View

To make views easy to manage, it’s a good idea to break out some common parts, like headers and footers, into their own view files, which can then be included in other views as needed. So, we split the registration page into three separate view files that will be loaded by a "main" view.

The Header View

Start with the header view and save the following code in application/views as registerHeaderView.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 registerHeaderView.lc script:

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

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

Your registerHeaderView.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("asynReg.css") ?>
  </head>
  <body>

Top of Page

The Footer View

The footer includes the ASYNergy script.

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

    <div id="footer">
      <hr>
      <p>revIgniter ASYNergy Form Validation Tutorial</p>

    </div>
  
    [[gData["asynergyScript"] ]]
  </body>
</html>

Top of Page

The Register View

The register view includes the registration form with form labels and paragraphs for error strings stored in the gData variable.

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

<div class="container">
  
  [[gData["logo"] ]]

  <section class="main">
    [[gData["formOpen"] ]]
      <label for="email">Email Address</label>
      <input type="text" name="email" asyn:model.blur="email" asyn:transmit="email" value="[[gData["email"] ]]">
      [[gData["emailError"] ]]
      
      <label for="password">Password</label>
      <input type="password" name="password" asyn:model="password" asyn:transmit="password" value="[[gData["password"] ]]">
      [[gData["pwError"] ]]
      
      <label for="passwordConfirm">Confirm Password</label>
      <input type="password" name="passwordConfirm" asyn:model="passwordConfirm" asyn:transmit="passwordConfirm" value="[[gData["passwordConfirm"] ]]">
      [[gData["pwConfError"] ]]
      
      [[gData["submit"] ]]
    </form>
      
    <a href="#">Already have an account?</a>
  </section>

</div>

As you can see, all input elements have an asyn:model attribute. These ASYNergy directives act as trigger for AJAX requests. The corresponding asyn:mutable attributes we have previously added to the markup of the error messages, take a look at the index handler.

Please note that the asyn:model attribute of the "email" input field has a "blur" modifier. This causes an AJAX request to be sent when the input element loses focus.

All input elements have an asyn:transmit attribute. This means that the values of all these elements will be added to the request data when the user clicks on the submit button.

Top of Page

The Main View

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

<?
put rigLoadView("registerHeaderView", TRUE) into tHeader
put rigLoadView("registerView", TRUE) into tContent
put rigLoadView("registerFooterView", TRUE) into tFooter

return tHeader && tContent && tFooter
?>

The main view, which is loaded by the index handler, combines the three partial views and returns the full registration page.

Top of Page

The Success View

There is one view left. It is the one which will be displayed after saving the form data to the database.

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

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

  <title>Registration Success</title>
	
</head>

<body>
	
  <div id="container">
    <header>
      <h1>Registration Success</h1>
    </header>
    
    <div id="main" role="main">
      [[gData["againAnchor"] ]]
    </div>
    
  </div>

</body>
</html>

That's it, your registration form should work as expected. Try it out, enter something in the form fields, and see the error messages that are triggered by AJAX requests. Save an email address and a password to the database, then try entering the same email address again.

Then deactivate JavaScript in your browser and you will see that form validation still works.

Top of Page

Conclusion

This sample illustrates: