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.
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?
- Server-side validation: You'll at least want this. It protects against user error, and can return friendly error codes so that the user will know how to correct the form. It's also important for security, to make sure that potentially damaging data will never enter the other parts of your application. For example, you can protect yourself from attacks which try to overflow your database with too much data, or attempts at SQL injection. (MongoDB injection attacks may be possible, too!) Note also that Diligence Forms will automatically catch server-side exceptions, invalidating the form and returning the error to the user, but obviously relying on exceptions is not secure enough.
-
Client-side validation: Adding this to server-side validation will enhance the user experience by providing fast, instant feedback, thus avoiding an extra round-trip to the server to validate the form data. It will also save you some bandwidth and help you scale. There are two kinds of client-side validation supported by Diligence, which when used together will offer the best user experience:
- Validation: The field's whole value will be tested before allowing the form to be submitted.
- Masking: When entering textual data, this locks the user's text field to only accept allowed characters. For example, if an integer is required, only the characters "0" to "9" and "-" (for negative integers) will be allowed.
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.
- required: The field cannot be empty, neither a null value nor an empty string will be accepted. Note that the "required" check happens before the "validator" function is called. [TODO error key]
- validator: A validating function, meant for both client- and server-side validation. It must return true to signify that the value valid. Any other return value will signify invalidity. (See "validation functions," below.)
- serverValidator: As "validator", but intended only for server-side validation.
- clientValidator: As "validator", but intended only for client-side validation.
- mask: A regular expression used for masking. This could be JavaScript literal regular expression, a RegExp object, or a string.
- serverValidation: Set to false to override the default for the form.
- clientValidation: Set to false to override the default for the form.
- textKeys: An array of text pack keys used by validator function. See "Text and Internationalization," below.
- type: Instead of providing "validator", "clientValidator", "serverValidator", "mask", "serverValidation", "clientValidation" and "textKeys" for every single field, you can specify a "type" from which these keys will be inherited. Defaults to "string". Note that even if you specify "type", you can override the inherited keys in the field definition.
- value: This is a default value assigned to the field when the form is initialized.
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:
- value: The value to be validated, most likely a string.
- field: The field definition. This is useful if you are using the same function for multiple fields, and need to validate differently per field. Note that the field definition is framework-dependent. For example, if you are on the server, it will look like the examples above, but if you're on an Ext JS client, then it will use Ext JS's definition. Because we're not using "field" in this example, we supplied just one "validator" function for both the client and the server. However, if you do need to access "field", it may be better to have separate "serverValidator" and "clientValidator" functions.
- conversation: The Prudence conversation. Only available on the server.
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:
- form: The form instance. Only available on the server.
- textPack: The currently used text pack. Always available on the server, and available on some clients, such as Ext JS if you use Diligence's Sencha Integration. See "Text and Internationalization," below, for more information.
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":
- string: All values are valid. This is the default type.
- number: Valid if the value can be converted into a JavaScript number. Masked for digits, "-" and ".".
- integer: Valid if the value can be converted into a JavaScript integer. Masked for digits and "-".
- email: Valid if the value is a standard email address. Does no masking.
- recaptcha: See reCAPTCHA.
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:
- results.success: Will be true if the form data is valid. You can change it to false during processing in order to signify an error to the user. Exceptions thrown in this function will also cause "results.success" to be false.
- results.values: A dict of the form values sent from the user. The value keys correspond to the field keys. Note that "results.values" will be deleted if "results.success" is true. The reason is that you should only need the old values if the user needs to correct the form in case of an error. If the form was successful, the form values should be reset. (In the example above we are setting "results.values.result" only for the purpose of the string template cast.)
- results.msg: A message to be displayed to the user.
- results.errors: A dict of error messages per field, as set by the field validator functions. The error keys correspond to the field keys. This dict will not exist if "results.success" is true when this function is called.
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:
- json: This is the mode you'll want to use for AJAX, as it returns the form results in JSON format. JSON mode additionally supports the "human=true" query parameter to return the JSON in multiline, indented format. Note that this is the default mode.
- redirect: After processing, the resource will redirect the client to a new URI. The default is the sending URI, but you can set up specific URIs for success and failure.
- capture: As an alternative to a redirect, you can perform a Prudence "capture" of another internal URI. The user will see the URI of the form resource itself, but the content will come from elsewhere. Note that because capturing happens in the same conversation, without a round trip to the client, you can use all the data used during processing. If you do a redirect, the client would be sending a new request and that data would be gone.
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:
- Because you can set the URI dynamically in "results.redirect", you can create a special kind of results view. For example, let's say you are implementing a search form (like Google's search engine page), which should redirect the user to the search results. You could redirect to a URI which includes the search results, for example in the URI query string. For example, searching for the phrase "cool apps" could end up redirecting to something like this: "http://myapp.org/search/?terms=cool+apps". In "/mapped/search.d.html" you would then unpack the terms and display the correct results. (You likely want to cache the search results for a while for the best user experience!)
- Another option is set a cookie, using Prudence's "conversation.createCookie" API, which you can then read in the redirected page using "conversation.cookies". Cookies are great if the result is very specific to the user, but note that bookmarks to the result URL would display something different if the cookie does not exist.
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:
- "Diligence.Forms.getForm" is a very useful function. It works by doing an internal GET on the URI to fetch the form instance. We could have also avoided setting up the instance in "resources.js" as well as routing it in "routing.js", and instead simply have created the "Diligence.Forms.Form" instance here. But this lets us use the instance both as a resource and in manual mode, as we've done here.
- The "handle" method will validate and process the form, but only if the conversation is a POST. If it's not processed, it will will return null.
- Note how we're displaying different content according to whether the processing was successful or not.
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:
- The code is not very easy to follow or debug. The same page is doing three different things: 1) displaying the form, 2) displaying errors, and 3) displaying the results of a successful post. (You could put each view in a different included fragment, but would lose the compactness.)
- This also means that caching logic for the page my be difficult if not impossible to do efficiently.
- A single URI with multiple uses can be confusing for users. If they bookmark the result "page," but try to go to it again at a later time, it would display an unfilled form, because it's the same page. This is problematic for all POSTed HTML forms: it's always a good idea to redirect the user to a book-markable URI that responds correctly to an HTTP GET.
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.