Hosted by Three Crickets

Diligence
Web Framework
For Prudence and MongoDB

Diligence is still under development and incomplete, and some this documentation is wrong. For a more comprehensive but experimental version download the Savory Framework, which was the preview release of Diligence.

Diligence logo: sleeping monkey

Forms Service

Forms are an important feature for any GUI application. As for web applications, forms are supported in HTML, but many web applications also use JavaScript to send forms to the server in the background ("AJAX"). Diligence goes a long way towards making it easier for you to use both models, each with its own complexities and subtleties, through a unified API. Allowing for both AJAX and HTML client forms with the same server code makes it easy to support "legacy" clients that can't use AJAX.
Diligence explicitly supports Ext JS Forms, and recommends Ext JS as a client-side framework. See the section on Sencha Integration for full details.

Client-side Validation vs. Server-side Validation

Like all good form frameworks, Diligence's Form Service makes it especially makes it easy to implement form validation, both on the server and the client, using an extensible system of field types. Due to the fact that Diligence is a server-side JavaScript framework, you can actually share the exact same validation code on both the client and the server! This marvelous advantage makes using forms in Diligence less cumbersome as compared to other frameworks.
What are the advantages of each kind of validation? Why you would want both?

Setup

Make sure to check out the API documentation for Diligence.Forms.
Every form is an instance of "Diligence.Forms.Form" or its subclasses. This class inherits "Diligence.REST.Resource," and thus can immediately be hooked to your URI-space. Indeed, much of the Forms Service power comes from such a setup, so we'll go over it here. However, note that is also possible to use the form instance without hooking it up to a URI, as we'll show in "Usage," below.
First, let's configure the URI-space in your application's "routing.js". Add the following to app.routes and app.dispatchers:
app.routes = {
	...
	'/multiply/': '@multiply'
}
​
app.dispatchers = {
	...
	javascript: '/manual-resources/'
}
We can now configure our resources in "/libraries/manual-resources.js":
document.executeOnce('/diligence/service/forms/')
​
var multiplyForm = {
	fields: {
		first: {
			type: 'number',
			label: 'A number',
			required: true
		},
		second: {
			type: 'integer',
			label: 'An integer',
			required: true
		}
	},
	process: function(results) {
		if (results.success) {
			results.values.result = Number(results.values.first) Number(results.values.second)
			results.msg = '{first} times {second} equals {result}'.cast(results.values)
		}
		else {
			results.msg = 'Invalid!'
		}
	}
}
​
resources = {
	...
	multiply: new Diligence.Forms.Form(multiplyForm)
}
Let's look more closely at this setup below.

Fields and Validation

Each field has at least a name (the key in the dict) and a type (defaults to "string"). If that's all the information you provide, then no validation will occur: any value, including an empty value, will be accepted.

Validator Functions

Let's look at such a function in the context of a field definition:
first: {
	required: true
	validator: function(value, field, conversation) {
		return value % 1 == 0 ? true : 'Must be an integer'
	}
}
The return value, as stated before must be true to signify a valid value. Otherwise, the value will be considered invalid and the return value will be used as the error message.
The arguments are as follows:
The function is called with an implicit "this" object, which obviously refers to different objects on the server and the client, but you can expect these fields:
Through accessing the "field" and "conversation" arguments as well as "this.form", you can do some very sophisticated server-side validation. For example, you can query MongoDB and check against data, check for security authorization, etc. And, of course, you can use similar sophistication for client-side frameworks according to the features they provide.
(At this point, you might be wondering how exactly client-side validator functions get to be called on the client, since we are defining them on the server. We'll talk about that in "Usage," below, but the solution is simple: we send the source code directly as text!)

Types

The Forms Service comes with a few basic types to get you started, all defined under "Diligence.Forms.Types":
You can also provide your own types:
var serviceForm = {
	types: {
		bool: {
			validator: function(value, field, conversation) {
				value = String(value).toLowerCase()
				return (value == 'true') || (value == 'false')
			}
		}
	}
	fields: {
		enabled: {
			type: 'bool',
			label: 'Whether the service is enabled'
		}
		...
	}
	...
}

Text and Internationalization

If you don't need internationalization, then just use the "label" key in the field definition to set up the text directly. If unspecified, it will default to the field name.
Otherwise, read about the Diligence Internationalization Service to understand how to set it up. We will use the "labelKey" key instead of "label", and also set up the list of other keys we might need using the "textKeys" key:
first: {
	labelKey: 'myapp.myform.field.first',
	textKeys: ['myapp.myform.validation.integer.not'],
	required: true
	validator: function(value, field, conversation) {
		return value % 1 == 0 ? true : this.textPack.get('myapp.myform.validation.integer.not')
	}
}
The above code will work on both the client and the server, because "textKeys" ensures that all those text values are sent to the client.

Processing

Let's look at our processing function again:
process: function(results) {
	if (results.success) {
		results.values.result = Number(results.values.first) Number(results.values.second)
		results.msg = '{first} times {second} equals {result}'.cast(results.values)
	}
	else {
		results.msg = 'Invalid!'
	}
}
The function will be called after validation happens, with "results" being a pre-defined dict, ready for you to modify, with the following keys:
As stated, you can modify any of these results as you need, including settings "results.errors" to extra per-field error messages, beyond what was performed in validation.
Indeed, you can use the processing function to do extra validation, which might have to take into consideration the form as a whole, rather than individual fields. For example, what if a start-date field in the form is set to be after an end-date field? You can find that out here and set "results.success" to false, with "results.errors.endDate" to a suitable error message.
The return value of this function is ignored.

Usage

If you've set up the resource as instructed above, you should be able to access it at the specified URI. By default, it will only support the HTTP POST operation, for which it expects an entity in the "application/x-www-form-urlencoded" media type, as is used by HTML forms.
Later on, we'll show you below how the Forms Service can help you render an HTML form, complete with validation error messages and internationalization support.

HTML Forms

For now, let's just start with a straightforward, literal HTML example:
<html>
<body>
<form action="<%= conversation.pathToBase + '/multiply/?mode=redirect' %>" method="post">
	<p>First value: <input name="first" /></p>
	<p>Second value: <input name="second" /></p>
	<p><input type="submit" value="Multiply!" /></p>
</form>
</body>
</html>
You'll notice that added a "mode" query parameter to the action URI. This lets us select one of the following modes of behavior supported by the resource:
When creating your resource instance, you can change the default to be something other than "json" by setting the "mode" key. JSON was chosen as a default because it's easiest to test and produces the least amount of side-effects due to unintentional access to the resource.

Testing Your Form Resource with cURL

cURL is an HTTP command line tool based on the cURL library, available for a great many Unix-like operating systems as well as Windows. It's especially useful for testing RESTful APIs. Here's a quick tutorial to get you started with using cURL with the Forms Service.
Try this command to send a POST to your form:
curl --data-urlencode first=5 --data-urlencode second=6 "http://localhost:8080/myapp/multiply/?human=true"
Note that using the "data-urlencode" switch will automatically set the method to POST and the entity type to "application/x-www-form-urlencoded."
Because the resource's default mode is JSON, you should get this result:
{
	"success": true,
	"msg": "5 times 6 equals 30"
}
If you're using AJAX to POST to the resource, then you'll have to parse these JSON results accordingly. See "Processing" above for the exact format of the results.
Also note that this format is immediately usable by Ext JS forms! See Diligence's Ext JS Integration for more details.
You can also use cURL to test redirect mode:
curl -v -e "http://my-referring-url" --data-urlencode first=5 --data-urlencode second=6 "http://localhost:8080/myapp/multiply/?mode=redirect"
You should see the redirected URL in the "Location" header, as well as an HTTP status of 303.

Redirect Mode

Redirect mode will by default redirect the client to the referring URI, using HTTP status 303 ("See Other").
But, you can explicitly set the redirection URI to something specific in "/libraries/resources.js":
var multiplyForm = {
	...
	redirectUri: '/multiply/results/',
	mode: 'redirect' // we'll make this the default mode (instead of 'json')
}
You can also set "redirectSuccessUri" and "redirectFailureUri" separately.
Or, you can set the URI dynamically by setting "results.redirect" in your processing function.
This should go without saying, but client redirections means that a whole new HTTP GET request will be sent by the client, such that all your conversation data will be gone. Of course, often the resulting page should depend on the result of form processing. There are two good strategies for handling this:

Capture Mode

Capture mode may seem similar to redirect mode: you supply a new URI which gets displayed to the client. The difference is that "redirection" happens on the server, rather than the client. That means that the URI for the client will remain the same. This is more efficient in that an extra round trip from the client is avoided. However, it creates serious problems for bookmarking: the result URI ends up being the same as the form URI. Think carefully about the pros and cons of each approach in terms of what would provide the best user experience. (Also see manual mode, below, which is similar in behavior to capture mode.)
You can access the form and the captured page using "Diligence.Forms.getCapturedForm" and "Diligence.Forms.getCapturedResults". This API will only work in a captured page. Let's see how this works by creating a "/mapped/multiply/results.d.html" for our results:
<html>
<body>
<%
document.executeOnce('/diligence/service/forms/')
​
var form = Diligence.Forms.getCapturedForm(conversation)
var results = Diligence.Forms.getCapturedResults(conversation)
​
if (results && results.success) {
%>
<p><%= results.msg %></p>
<% } else { %>
<form method="post">
	<p>First value: <input name="first" /></p>
	<p>Second value: <input name="second" /></p>
	<p><input type="submit" value="Multiply!" /></p>
</form>
<% } %>
</body>
</html>
We can specify the capture URI when we create the resource, in "/libraries/resources.js":
var multiplyForm = {
	...
	captureUri: '/multiply/results/',
	mode: 'capture' // we'll make this the default mode (instead of 'json')
}
You can also set "captureSuccessUri" and "captureFailureUri" separately.
Or, you can set the URI dynamically by setting "results.capture" in your processing function.
Finally, while it's not entirely necessary, you can hide the URI. This will guarantee that it's only available for capturing, but the user won't be able to reach it by entering the URL in their browser. You do this in your application's "routing.js":
app.routes = {
	...
	'/multiply/results/': 'hidden'
}

Manual Mode

If you go back to the code for the simple HTML form we've provided above, you might wonder if having the form as a separate resource is necessary. While it does provide a cleaner separation between the form processing resource and the HTML view resource, it would be more efficient if we could avoid that extra client redirect and do the processing and viewing in the same resource.
Before we consider if this is a good idea or not, let's see how this would be easily done with Diligence:
<html>
<body>
<%
document.executeOnce('/diligence/service/forms/')
​
var form = Diligence.Forms.getForm('/multiply/')
var results = form.handle(conversation)
if (results && results.success) {
%>
<p><%= results.msg %></p>
<% } else { %>
<form method="post">
	<p>First value: <input name="first" /></p>
	<p>Second value: <input name="second" /></p>
	<p><input type="submit" value="Multiply!" /></p>
</form>
<% } %>
</body>
</html>
A few points to explain:
So, is manual mode a good idea or not? If can provide a straightforward, quick-and-dirty way to implement a form. Compact, too: you can create the instance, do all the processing, and put all the view code in a single file. There's no need to set up routing for a resource.
But, there are a few disadvantages:
You can mitigate some of these problems by using capture mode instead. Capture mode will let you use a separate page for results, which can be cached (on the server, at least: a POST will never cache on the client), while keeping the URI the same.

Low-Level Manual Mode

So, this "mode" actually does not use the Diligence Forms Service at all, instead it relies directly on the Prudence API. We thought it would be a good idea to include it here for the sake of completion. Sometimes, even manual mode may not be quick-and-dirty enough! Note that validation is very, very basic: if the value cannot be converted, you will simply get a null.
Here's how it would look:
<html>
<body>
<%
document.executeOnce('/prudence/resources/')
document.executeOnce('/sincerity/objects/')
document.executeOnce('/sincerity/templates/')
​
var form
if (conversation.request.method.name == 'POST') {
	form = Prudence.Resources.getForm(conversation, {
		first: 'float',
		second: 'int'
	})
}
​
if (form && Sincerity.Objects.exists(form.first) && Sincerity.Objects.exists(form.second)) {
	form.result = Number(form.first) Number(form.second)
%>
<p><%= '{first} times {second} equals {result}'.cast(form) %></p>
<% } else { %>
<form method="post">
	<p>First value: <input name="first" /></p>
	<p>Second value: <input name="second" /></p>
	<p><input type="submit" value="Multiply!" /></p>
</form>
<% } %>
</body>
</html>

Rendering an Internationalized HTML Form

In all the above examples, we explicitly entered the HTML for the form and its fields. But, Diligence Forms can also generate the HTML for you, and moreover use the Internationalization Service, in conjunction with the Authorization Service, to render the correct text for the user's preferred language.
The rendered HTML is very straightforward: it's a simple <input> tag when using "htmlText" (or a <textarea> tag when usng "htmlTextArea"), with a connected <label> prepended. If the field failed validation then an extra <div> is appended with the validation error message. Furthermore, in case of validation error, all tags for the field will get the "error" class, allowing you to use CSS in order to stylize validation errors.
You should add the "results" of the form if you have them (they are available in capture mode and manual mode) to the method calls. This will render errors properly, and also set the values of the form to the previous values, making it easier for the user to correct the form.
Here's an example using manual mode, which also uses CSS to stylize form errors:
<html>
<head>
<style>
form input.error {
	border: 1px solid red;
}
form div.error {
	color: red;
	display: inline;
	padding-left: 5px;
}
</style>
</head>
<body>
<%
document.executeOnce('/diligence/service/forms/')
​
var form = Diligence.Forms.getForm('/multiply/')
var results = form.handle(conversation)
%>
<form method="post">
	<div><%= form.htmlText({name: 'first', conversation: conversation, results: results}) %></div>
	<div><%= form.htmlText({name: 'second', conversation: conversation, results: results}) %></div>
	<div><input type="submit" value="Multiply!" /></div>
</form>
</body>
</html>

The Diligence Manual is provided for you under the terms of the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. The complete manual is available for download as a PDF.

Download manual as PDF Creative Commons License