pREST

Authors

pREST Form Tutorial

pREST framework allows creating forms with a validation support and pREST provides an easy way how to work with them. Let's see, the way how to work with HTML forms.

Let's demonstrate the work with HTML forms on an example of page which is used for user's profile attributes modification.

Server side form implementation and validation

Let's have a project with the following structure:

demo
|-- docs
|-- lib
|   `-- prest.jar
|-- src
|   `-- demo
|       |-- DemoApplication.java
|       `-- controllers
|           `-- ProfileController.java
|-- web
|   |-- META-INF
|   |-- WEB-INF
|   |   `-- web.xml
|   `-- index.html
|-- build.properties
`-- build.xml

Our goal is to create a simple page, which is created by one form with some input components and with a submit button. After submitting the data our goal is to handle the submitted data, validate whether the values were in a correct format, and then save this data.

pREST framework is based on MVC (Model - View - Controller) Design Pattern principles. In our case the Model will be a class which will be created with appropriate attributes and it will bear demo.model.Profile name. The next thing is to create a Controller class demo.controllers.ProfileController, which will manage a page logic. This controller will be mapped in an application class demo.DemoApplication in initialize() method to an URI (let's say): /user

import prest.core.Application;
import prest.core.ApplicationException;
import demo.controllers.ProfileController;

public class DemoApplication extends Application {

	@Override
	public void initialize() throws ApplicationException {
		mount("/user", new ProfileController());
	}

}

We will add a public ProfileForm profile(ProfileForm form) method into the controller class. The action of the same as method's name will be mapped into this method and will be exposed on a URL: http://localhost:8080/demo/user/profile. The input parameter of this method is a class which extends prest.validators.form.Form type and this type is also a return value of the method. This class is also a form model we are working with.

package demo.controllers;

import demo.model.Profile;
import prest.core.Controller;
import prest.core.annotations.Action;
import prest.core.annotations.Doc;
import prest.core.annotations.Key;
import prest.validators.form.Form;
import prest.web.annotations.View;

public class ProfileController extends Controller {

	@Action(name = "profile", httpMethod = {"GET", "POST"})
	@Doc("Profile form page")
	@View(template = "/templates/profile.jsp")
	public ProfileForm profile(@Doc("Profile Form") ProfileForm form) {
		return form;
	}
}

We want to make a validation of received data. The validation is a process of checking the syntactic and semantic correctness of the received data, which has been submitted from a web browser throughout HTML form. Let's show how easy and elegant is the process of data validation and how easy is to affect the application flow according to validation result.

In our case, the form and his validation will be represented by a demo.forms.ProfileForm class extending prest.validators.form.Form. The first thing to do is to define form entries and corresponding validators inplementing public void addEntries() method. By calling public void addEntry(String key, Validator validator, String description) the concrete type of a validator will be bound to HTTP form entry which is identified by key attribute. Calling public void addEntrySubmit(String key) defines which form entry is mandatory and then must be send to consider form as submitted. During the form definition it is possible to use some of the build-in validators, or it is also possible to create own implementation, which extends prest.validators.common.Validator type.

package demo.forms;

import prest.validators.EmailValidator;
import prest.validators.LongValidator;
import prest.validators.RegexpValidator;
import prest.validators.form.Form;
import demo.model.Profile;

public class ProfileForm extends Form {

	@Override
	protected void addEntries() {
		addEntry("name", new ProfileNameValidator(), "Full name");
		addEntry("email", new EmailValidator("@"), "E-mail address");
		addEntry("age", new LongValidator(), "Age");
		addEntrySubmit("submit");
	}

}

class ProfileNameValidator extends RegexpValidator {
	public ProfileNameValidator() {
		super("\\w{4,}");
		setRegexpErrorMessage("at least 4 alphanumeric characters");
	}
}

Presentation view or View (For simplicity let's have a JSP template) web/templates/profile.jsp may looks like this:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<%@ page
	language="java"
	contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"
	isELIgnored="false"
	import="prest.validators.form.Form"
	%>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="sk" lang="sk">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<meta http-equiv="content-language" content="sk" />
		<title>Profil</title>
	</head>
	<body>
		<h1>Profile</h1>

		<form id="profile_form" method="post" action="">

			<% ProfileForm form = (Form) request.getAttribute("ProfileForm"); %>

			<div>
				<label for="name">Name:</label>
				<input type="text" id="name" name="name" value="<%= form.getEntryValue("name") %>"/>
				<span id="name_message" class="error"><%= form.getEntryErrorMessage("name") %></span>
			</div>
			<div>
				<label for="email">E-mail:</label>
				<input id="email" type="text" name="email" value="<%= form.getEntryValue("email") %>" />
				<span id="email_message" class="error"><%= form.getEntryErrorMessage("email") %></span>
			</div>
			<div>
				<label for="age">Age:</label>
				<input id="age" type="text" name="age" value="<%= form.getEntryValue("age") %>" />
				<span id="age_message" class="error"><%= form.getEntryErrorMessage("age") %></span>
			</div>
			<div>
				<input id="submit" type="submit_button" name="submit"  value="save" />
			</div>
		</form>

	</body>
</html>

After the first call of URL http://localhost:8080/demo/user/profile the controller method profile() is called. Output of action method profile() is used as data for HTML template listed in @View annotation. After the first call there were no form entries sent, so the form is empty. After filling out the form, it is a good practice to submit it to the same URL (to the self), where the data will be processed. In case of defective input data the form is showed again with error messages. This process repeats cyclically until valid data are obtained, which are processed afterwards. The action method profile() input parameter ProfileForm form is automatically initialized by HTTP request form parameters. During the process of initialization the validation is performed. From point of view pREST engine the action method's profile() input parameter ProfileForm form is an aggregated type.

Often we face a situation in which we need to fill the form at the begining with some sort of data - either with data acquired from a database or with other dynamic data. In this case it is appropriate to add a logic, which covers the initial filling of the form and form data extraction - setProfile() and getProfile() respective. This methods enable the transformation of valid form (page model) directly into business logic model.

package demo.forms;

import prest.validators.EmailValidator;
import prest.validators.LongValidator;
import prest.validators.RegexpValidator;
import prest.validators.form.Form;
import demo.model.Profile;

public class ProfileForm extends Form {

	public ProfileForm() {
	}

	@Override
	protected void addEntries() {
		addEntry("name", new ProfileNameValidator(), "Full name");
		addEntry("email", new EmailValidator("@"), "E-mail address");
		addEntry("age", new LongValidator(), "Age");
		addEntrySubmit("submit");
	}

	public void setProfile(Profile profile) {
		setEntryValue("name", profile.getName());
		setEntryValue("email", profile.getEmail());
		setEntryValue("age", String.valueOf(profile.getAge()));
	}

	public Profile getProfile() {
		String username = (String) getValidator("name").getValue(0);
		long age = (Long) getValidator("age").getValue(0);
		String email = (String) getValidator("email").getValue(0);

		Profile result = new Profile(username,  email, age);
		return result;
		
	}
}


class ProfileNameValidator extends RegexpValidator {
	public ProfileNameValidator() {
		super("\\w{4,}");
		setRegexpErrorMessage("at least 4 alphanumeric characters");
	}
}

The logic for initialization and processing of the form are added into a controller method, for the case where there was no data submited by the client.

package demo.controllers;

import prest.core.Controller;
import prest.core.RedirectException;
import prest.core.annotations.Action;
import prest.core.annotations.Doc;
import prest.json.annotations.Json;
import prest.web.annotations.View;
import demo.applogic.Profiles;
import demo.forms.ProfileForm;
import demo.model.Profile;

@Doc("Profile controller")
public class ProfileController extends Controller {

	@Action(name = "profile", httpMethod = {"GET", "POST"})
	@View(template = "/templates/profile.jsp")
	@Doc("Profile form page")
	public ProfileForm profile(@Doc("Profile form") ProfileForm form) {

		if (form.isSubmitted()) {
			if (form.isValid()) {
				// extract profile (business model)
				// from HTTP form (page model)
				Profile profile = form.getProfile();
				// call business logic to handle data
				Profiles.setProfile(profile);
			} else {
				// form is invalid, try next iteration
			}
		} else {
			// fill the form if it is possible
			Profile profile = Profiles.getProfile();
			if (profile != null) {
				form = new ProfileForm();
				form.setProfile(profile);
			}
		}

		return form;
	}

	
	@Action(httpMethod = "GET")
	@Doc("Redirects to the profile")
	public void index() throws RedirectException {
		redirect(getApplicationPath() + getControllerPath() + "/profile");
	}

}

Finaly we are done with server side HTTP form and his validation.

Client side form validation

While pREST server framework is intended for creation of Rich Internet Applications (RIA), pREST client offers a set of tools including a form validation. pREST client enables validation of values of single arrays on the browser side.

Input HTML form entries, are abstracted by JavaScript objects. During object creation it is necessary give to form entries identifier as an object constructor parameter under which is the element represented in the DOM structure. So the object can be created following way:

var name_input = new prest.widgets.forms.TextInput("name");
var submitButton = new prest.widgets.forms.Button("submit_button");

pREST client javaScript library implements Signal-Slot Design Pattern. Validators are objects they extends prest.validators.Validator type. Hence all validators emits two kinds of signal:

We have to implement corresponding slots, one for valid and one for invalid signal emited as a result of validation process. Let us implement slots for RegexValidator.

var name_validator = new prest.validators.RegexValidator(/^[a-zA-Z]+$/, "Bad name format");

name_validator.signal_valid.connect(
	function(value) {
		document.getElementById("name_message").innerHTML = "";
	}
);

name_validator.signal_invalid.connect(
	function(error_message, value) {
		document.getElementById("name_message").innerHTML = error_message;
	}
);

It is possible, similarly as in the case of server component, to associate validators to the HTTP form entries by using prest.validators.FormValidator. Individual validators are bound into FormValidator by calling an add_form_object() method. The meaning of the parameters are:

add_form_object(form_object, validator, connect)

During the creation of FormValidator, it is possible to specify whether the HTTP form entry value changes should be immediately signaled to the validator by using a third optional parameter of the add_form_object() method. In that case the validation will be running in a real time, after every change of the value of the HTTP form entry. On the other hand the validation will take place after the explicit call of FormValidator method validate().

Client side HTTP form validator can be implementid like this:

var form_validator = new prest.validators.FormValidator();

//realtime validation
form_validator.add_form_object(name_input, name_validator, true);
//validate only on submit
form_validator.add_form_object(email_input, email_validator);
form_validator.add_form_object(age_input, age_validator);

The complete client side validation for our demo profile form will be:

<head>
	<script type="text/javascript" src="${pageContext.request.contextPath}/javascript/prest.js"></script>
	<script type="text/javascript" src="${pageContext.request.contextPath}/javascript/prest/dev.js"></script>
	<script type="text/javascript" src="${pageContext.request.contextPath}/javascript/prest/calendar.js"></script>
	<script type="text/javascript" src="${pageContext.request.contextPath}/javascript/prest/validators.js"></script>
	<script type="text/javascript" src="${pageContext.request.contextPath}/javascript/prest/widgets/forms.js"></script>

	<script type="text/javascript">

	prest.queue_load_event(init);

	function init() {
		// name
		var name_input = new prest.widgets.forms.TextInput("name");

		var name_validator = new prest.validators.RegexValidator(/^[a-zA-Z]+$/, "Bad name format");
		name_validator.signal_valid.connect(
			function(value) {
				document.getElementById("name_message").innerHTML = "";
			}
		);
		name_validator.signal_invalid.connect(
			function(error_message, value) {
				document.getElementById("name_message").innerHTML = error_message;
			}
		);

		// email
		var email_input = new prest.widgets.forms.TextInput("email");

		var email_validator = new prest.validators.EmailRegexValidator("Bad email format");
		email_validator.signal_valid.connect(
			function(value) {
				document.getElementById("email_message").innerHTML = "";
			}
		);
		email_validator.signal_invalid.connect(
			function(error_message, value) {
				document.getElementById("email_message").innerHTML =
					"Invalid value " + value + " ( " + error_message + " )";
			}
		);

		// age
		var age_input = new prest.widgets.forms.TextInput("age");

		var age_validator = new prest.validators.RegexValidator(/^\d{1,3}$/, "Bad age format");
		age_validator.signal_valid.connect(
			function(value) {
				document.getElementById("age_message").innerHTML = "";
			}
		);
		age_validator.signal_invalid.connect(
			function(error_message, value) {
				document.getElementById("age_message").innerHTML =
					"Invalid value " + value + " ( " + error_message + " )";
			}
		);

		// form
		var form_validator = new prest.validators.FormValidator();
		//realtime validation
		form_validator.add_form_object(name_input, name_validator, true);
		//validate only on submit
		form_validator.add_form_object(email_input, email_validator);
		form_validator.add_form_object(age_input, age_validator);

		var submitButton = new prest.widgets.forms.Button("submit_button");
		submitButton.signal_click.connect(function()
			{
				//if (form_validator.validate()) {
				//	document.getElementById("profile_form").submit();
				//}
			}
		);

	}
	</script>
</head>

Definitly we are done with client side HTTP form validation now.