Form
From Frontal Wiki
Contents |
Introduction to the form tag
The 'form' tag is used in conjunction with the 'input' tag to add an HTML-like form to a document definition. The basic idea of a form is to group a set of user inputs together and then to send them off to a server or a local interaction for processing when the user submits the information. Here is a simple example:
<include rel="assets" blocking="true" src="http://www.frontalcode.com/assets/swfs/input_components.swf" /> <form action="login.php"> <input type="text" name="username">Username</input> <input type="password" name="password">Password</input> <input type="submit">Login</input> </form>
In this example, pressing the "Login" button would send the values entered for the username and password fields to a PHP script on the server.
(Note that for this example to work, we needed to first load a SWF named input_components.swf. This is a very simple SWF that simply has a number of the Flash input components exported in the library. We do things this way because the Flash input components are not compiled into the Frontal renderer but rather in an external library. This for efficiency and also to more easily support input components other than those provided by Adobe.)
The input elements that are shown in the example are described in more detail in the 'input' tag section, but it's worth noting here that they are actually defined by the Frontal default style sheet. In the 'input' section, we'll look at that in more detail but the noteworthy point is that they are implemented with Flash UIComponent classes. This means that the Flash UI architecture is at play when it comes to forms and so understanding that architecture can be helpful (though not necessary). We may use vocabulary in this documentation then that references that architecture. See the Flash documentation for details.
There is one exception to this though and that is that "text" tags with the style "flash-text-type" set to "input" will also act as form inputs. That is, we could rewrite the previous example like so:
<include rel="assets" blocking="true" src="http://www.frontalcode.com/assets/swfs/input_components.swf" /> <form action="login.php"> <text style="flash-text-type: input;" name="username">Username</text> <text style="flash-text-type: input; display-as-password: true;" name="password">Password</text> <input type="submit">Login</input> </form>
Implementation
The form tag is implemented with the ContainerForm class which extends the Container class.
Properties
The 'form' tag has all the properties of a 'div' tag and a few more. It add these properties to that class:
- initialFocus: This property is an 'input' element that should receive initial focus when the form is reset. By default this is the input with the lowest "tabIndex" attribute value or the first input if none have a tabIndex.
- defaultButton: Each form may have one default button. It must be an instance of the Flash Button class. When set and when the focus is not on a textarea input, then pressing enter has the same effect as if this Button were clicked. The Frontal style sheet will automatically set this property to the last input with type "submit" in the form so that pressing enter causes the form to automatically submit.
It also adds these properties though it is likely that you will never need to use them:
- activeFocusMgr: This is a static property of the ContainerForm class and so accessed as com.frontalcode.ContainerForm.activeFocusMgr. The form tag uses the Flash FocusManager to support tabbing through inputs. As there may be multiple forms in a Frontal document, there may be multiple FocusManagers. This property then refers to the currently active one.
- stageFocusMgr: This is a static property of the ContainerForm class and so accessed as com.frontalcode.ContainerForm.stageFocusMgr. When a FocusManager is used anywhere in a movie, Flash automatically applies a Stage-level FocusManager as well. This property then points at that FocusManager.
Methods
These methods are particular to the form tag:
- activate: This will deactivate any other forms' FocusManagers as well as the Stage FocusManager and activate this form's FocusManager. (The effect of activating and deactivating a FocusManager is described in the Flash documentation.) This method takes no parameters.
- deactivate: This will deactivate this form's FocusManager if it is currently activated and will activate the Stage FocusManager.
- submit: This will submit the form. See the discussion below though for how the "submit" method may be tied into a validation system in which case the submit may not actually occur.
Attributes
These attributes are particular to the form tag:
- action: This is the URL of the server script used to handle the form submission. If empty or not set then the form will not be submitted and will instead run the onSuccess interaction immediately after submission.
- method: This is either "get" or "post." If "get" then the form values will be submitted in the URL of the form submission. If "post" then they will be sent in the body of the submission. This is identical behavior to HTML forms. This sets the method property of the underlying Flash URLRequest instance used to do the submission.
- enctype: The encoding type is the MIME Type of the POST data in the submission. This sets the contentType property of the underlying Flash URLRequest instance used to do the submission.
The form tag also looks for particular attributes on its inputs, namely the "name" and "value" attributes. See the 'input' tag for more details.
Styles
These styles are particular to the form tag:
- form-auto-focus: When the form resets, it will restore focus to the input specified by the initialFocus property.
- show-progress-indicator: While not unique to this tag, its implementation is. For forms, a progress indicator does not measure download progress but rather upload progress. This can be quite helpful when a user is submitting large files through a form.
- include-frontal-form-variables: "true" or "false," this determines whether or not to send Frontals's form-grouping variables with the submission. The default is true. (See "Submitting Files" for a general discussion below.)
- resend-vars-in-multi-file-uploads: "true" or "false," this determines whether or not to re-send non-file inputs with every file submission. The default is false. (See "Submitting Files" for a general discussion below.)
Building a Complete Form
Earlier we gave a simple example of a form. It was only of limited use because it did not handle the form submission in anyway; it simply sent the information. There are various things we'd typically like to do with a form such as validate its input before submission, show that the form is processing, show errors and show a thank you message. Let's show how those things would be done.
First, it's useful to put the form into a manager tag. Then we will add siblings to the form to show a processing message and a thank you message. That is, we'll have a structure something like this:
<div> <manager> <form action="myformhandler.php" /> <text>Processing...</text> <text>Thank you!</text> </div>
To support this then we need some interactions on the form that set the current selection in the manager appropriately. Frontal provides a number of interactions to do just this. (For a complete list, see the section Adding Interactions.) Let's add them to a style sheet and integrate them with the manager.
<style><![CDATA[ form { @onAction { parent.childrenManager.jumpTo ( 'processing' ); } @onError { parent.childrenManager.jumpTo ( 'form' ); } @onSuccess { parent.childrenManager.jumpTo ( 'success' ); } } ]]></style> <include rel="assets" blocking="true" src="http://www.frontalcode.com/assets/swfs/input_components.swf" /> <div style="layout: stack;"> <manager id="form1" style="hide-unselected: true;" /> <form name="form" action="myformhandler.php"> <input type="text" name="username">Username</input> <input type="password" name="password">Password</input> <input type="submit">Login</input> </form> <text name="processing">Processing...</text> <div name="success"> <text>Thank you!</text> <text class="frMarker" mgrId="form1" elemNdx="form">Close</text> </div> </div>
Paste this into the Online Workspace and see how it behaves. After we hit the submit button (or just press the enter key), we briefly see the "Processing..." message and then see the form again. What is happening is that we are getting an error which is calling the onError interaction which is setting the manager back to the form. Let's say we'd like to display the error text on the form. First, let's add a text field to the top of the form to display an error message. Add it just above the username input:
<text name="error" style="display: none; width: 100% leftover; padding-bottom: 4px; color: red;" />
Next, let's change the onError and onSuccess interactions. If we get an error, we want to put a message in the text tag we just added and we want it to be displayed:
@onError { parent.gE ( "form" ).gE ( "error" ).text = "An error occurred - " + event.text; parent.gE ( "form" ).gE ( "error" ).sS ( "display", "block" ); parent.childrenManager.jumpTo ( 'form' ); }
And if we get a success message, we want to hide any error message:
@onSuccess { parent.gE ( "form" ).gE ( "error" ).sS ( "display", "none" ); parent.childrenManager.jumpTo ( 'success' ); }
Try these changes. Now we see an error message displayed with our form. It's not a very clear message but it's indicating an IO error; the URL we are submitting to "myformhandler.php" doesn't exist. Let's remove the "action" attribute from the "form" tag and try again. Now the form will go through all the steps of submission without actually contacting the server. You should see the "Thank you!" message and a close button.
A common thing to do with a form is to validate its entries prior to actually submitting it. We do this much as we do in JavaScript with an onSubmit interaction. We apply this to the form tag and it will be called just after the form's submit method is called but before anything is sent to the server. If this interaction returns false then the form will not be sent to the server. If it returns true then it will be.
In this example, we validate that the username and password are not the default values. Add this to our growing example and give it a try. You'll see that until the username and password fields are changed, the form will show an error. When they are both changed, the submission will go through.
<style><![CDATA[ form { @onSubmit { if ( gE ( 'username' ).value == 'Username' || gE ( 'password' ).value == 'Password' ) { gE ( 'error' ).text = "Please enter a username and password."; gE ( 'error' ).sS ( 'display', 'normal' ); return false; } else { gE ( 'error' ).sS ( 'display', 'none' ); return true; } } } ]]></style>
Frontal provides another interaction after the onSubmit call and just before the actual submission. What happens in the interim is that Frontal builds the Flash FileReferenceList, FileReference, URLRequest and URLLoader instances needed to perform the network operations for the submission. When the onAction interaction is called, these objects are available for review, editing and perhaps further validation. Like onSubmit, if onAction returns false then the submission will not be done. Note that in our style sheet, we use the onAction interaction not for any more validation or fancy manipulation of the submission objects but rather to simply change the selection in our manager to the "Processing..." page. For details of the onAction interaction, see Adding Interactions.
If a submission passes through the onSubmit and onAction interactions then the submission is actually performed. How this is actually done depends on what sort of data is being submitted. For now, we'll assume that it does not include files uploads. (In "Submitting Files" below we will discuss this case.) Then as the submission progresses, certain interactions will be called on the form tag. If an error occurs (either an IOError or a SecurityError) then the onError interaction will be called. And if the submission completes successfully then the onSuccess interaction will be called. We saw these earlier as we used them to display error messages and change the selection of our manager.
Details about the onError and onSuccess interactions are listed in Adding Interactions but just for clarity, these interactions each have a pre-defined variable named "event" available to them. This event is dispatched from the Flash instance used to submit the form and so it is through this event that we can get more details about the submission that generated the interaction. For example, after we successfully submit a form, we often get data back from the server. We can access that data through the event. Here is an example of how we might handle a JSON-formatted response from a form submission:
@onSuccess { var results = JSON.decode ( event.target.data ); }
We'll discuss different marshalling techniques like JSON below.
One last note about form submissions, while it is out of scope to discuss how to write the server-side scripts to handle form submissions, it should be noted that scripts written to handle HTML submissions work without modification with Frontal forms. In fact, we have integrated with many interfaces designed for HTML and JavaScript and that are unaware that the form submission is coming from Frontal.
Modifying Input Values Before Submission
In some cases, the values entered into the form fields might need to be modified before submission. One such case is when the input fields contain labels that should not be submitted unless they are replaced with user-entered values. In the following example, we demonstrate the use of the getSubmissionValue interaction. This interaction provides a final opportunity to alter input values just before the form whisks them away to their destination. The value returned in this example is blank if the input contents are equivalent to the label, or whatever the actual contents are otherwise.
<style><![CDATA[ form { @onAction { parent.childrenManager.jumpTo ( 'processing' ); } @onError { parent.childrenManager.jumpTo ( 'form' ); } @onSuccess { parent.childrenManager.jumpTo ( 'success' ); } } input[type=text], input[type=password] { @getSubmissionValue { return input.text == node.text ( ).toString ( ) ? "" : input.text; } } ]]></style> <include rel="assets" blocking="true" src="http://www.frontalcode.com/assets/swfs/input_components.swf" /> <div style="layout: stack;"> <manager id="form1" style="hide-unselected: true;" /> <form name="form" action="myformhandler.php"> <input type="text" name="username">Username</input> <input type="password" name="password">Password</input> <input type="submit">Login</input> </form> <text name="processing">Processing...</text> <div name="success"> <text>Thank you!</text> <text id="receipt">You submitted:</text> <text class="frMarker" mgrId="form1" elemNdx="form">Close</text> </div> </div>
Submitting Files
File submissions with Frontal forms is a little different than with HTML if there is more than one file being uploaded in the form. The issue is that Flash only allows one file upload per submission and so we have to do things a little differently when submitting more than one.
In the section on the 'input' tag, we will see how to add file inputs to our form. We can either add inputs that allow the user to select a single file or to select multiple files. (The latter is something we cannot do with HTML forms.) The issue then is what happens when a form with none, one or both of these input types is submitted.
During a form submission, Frontal first looks to see if no file inputs are set. If that is the case then a single submission using the Flash URLLoader will be performed. If that is not the case, then server submissions will be done for each file entered via the Flash FileReference class.
This approach of doing multiple server submissions for a single form submission is different than how HTML forms work and raises some issues like how does a server-side script know that two separate server submissions are related? Or even if it knows they are related, how does it know how many to expect? Being a client-side technology, Frontal can't solve these problems but it can help. With every Frontal server submission (even when no files are being uploaded), a few parameters are added to help manage these scenarios. These parameters behave just as if they were hidden inputs in the form (see the 'input' tag for details) and are as follows:
- __frontalSubmissionID: An ID unique to the form submission. (This is 10, random, Base64-encoded bytes and the likelihood that any two submissions in some given time frame would have the same ID should be very small.)
- __frontalSubmissionCount: The number of server submissions to expect for this one form submission.
- __frontalSubmissionIndex: The index of this particular server submission.
With these parameters then, a server-side script has the information to group a number of server submissions together, to tell how many there will be and to know which one was last received. (The server submissions should come in order which provides some degree of fault detection.)
If these extra parameters are not desired then set the style "include-frontal-form-variables" to "false" on the form tag.
Another issue with having multiple server submissions is to which should non-file data be added? That is, a form may have text and other inputs along with the file data that need to be sent to the server but with multiple submissions, it's unclear to which should they be applied. Frontal will always send this data with the first submission. Then, if the style "resend-vars-in-multi-file-uploads" is set to "true" on the form, the data will also be applied to every submission that follows as well.
To accommodate the special behaviors of the multiple submission process, there is support for a few more interactions on the form tag. The full details of these can be found in Adding Interactions.
- onPartialSuccess: When a form submission invokes multiple server submissions, this interaction is called after each one save for the last. This interaction is basically signaling the success of the last server submission.
- onFormData: It is a quirk of the Flash FileReference class that data returned by a server-side script when a file upload is involved is not included with the completion event but rather with a separate event. Another oddity is that this event will not be dispatched if no data is returned from the server. In any case, this interaction is called when the server submission involves a file and when the server-side script returns data.
Finally, when submitting files, the time needed to upload the files to the server can be non-trivial. It is therefore a useful thing to indicate the progress of the upload to the user. With Frontal this is as easy as adding the style "show-progress-indicator: normal;" to the form tag. Frontal can uses the same framework for monitoring form uploads as it does for downloading images and so the details in the advanced topic on progress indicators are directly applicable to forms.
Submitting via XML RPC, JSON, SOAP, Etc.
There are many web services available that have differing requirements for how inputs and outputs should and will be formatted. Frontal makes it fairly convenient to integrate with these disparate web services.
In this example we're showing how to lookup a user ID from a user name on Flickr. Flickr supports various formats for its APIs and in this case we are using the XML RPC library written for Frontal.
Our first step is to define a form with a text input for a username. We also set the action attribute to be the location of the Flickr web service.
<form action="http://api.flickr.com/services/xmlrpc/" method="post"> <text name="error" style="display: none; width: 100%; padding-bottom: 4px;" /> <input tabIndex="1" type="text" name="username">User Name</input> <input tabIndex="2" type="submit">Lookup</input> </form>
Next, we need to include the XML RPC library which we will use to format our request and parse the response from Flickr.
<include rel="inline" src="http://frontalcode.com/assets/xml/xmlrpc.xml" />
Next, we need to manipulate the request generated by the form when the user hits submit. Rather than use the standard format for HTML form parameters, we want to use the XML RPC format that Flickr requires. Recall that the onAction interaction is called just prior to submitting data to the server so its the perfect place to do this transformation. In our onAction interaction, we get the username, add it to a structure defined by Flickr, marshal the result into the XML RPC format and then set the request.data property to the result. This works because "request" refers to the URLRequest that is being used for the submission and so whatever data we apply to is will be sent in that submission. In other words, while we have formatted our request, we will still let Frontal do all the heavy lifting of actually sending the request and monitoring it for errors and success.
@onAction { var username = gE ( "username" ).value; var rpc = new xmlRPC ( ); rpc.addParameter ( { api_key: "fa0ccb3c5f57dc174a484baa4d0f4ba8", username: username } ); request.data = rpc.marshalRequest ( 'flickr.people.findByUsername' ); }
Then we need to display the result of our XML RPC call. We do that in an onSuccess interaction. Below is the snippet in which we parse the XML RPC response, parse the XML result and write the user ID to the Frontal definition.
var rpc = new xmlRPC ( ); rpc.parseResponse ( event.target.data ); var response = new XML ( rpc.response ); document.write ( "User ID is " + response.attribute ( "nsid" ).toString ( ) );
Putting it all together then along with some error handling, we get this:
<include rel="assets" blocking="true" src="http://www.frontalcode.com/assets/swfs/input_components.swf" /> <include rel="inline" src="http://frontalcode.com/assets/xml/xmlrpc.xml" /> <style><![CDATA[ form { @onAction { var username = gE ( "username" ).value; var rpc = new xmlRPC ( ); rpc.addParameter ( { api_key: "fa0ccb3c5f57dc174a484baa4d0f4ba8", username: username } ); request.data = rpc.marshalRequest ( 'flickr.people.findByUsername' ); } @onError { gE ( "error" ).text = event.text; gE ( "error" ).sS ( "display", "normal" ); } @onSuccess { var rpc = new xmlRPC ( ); rpc.parseResponse ( event.target.data ); if ( rpc.isFault ) { gE ( "error" ).text = rpc.response.faultString; gE ( "error" ).sS ( "display", "normal" ); } else { gE ( "error" ).sS ( "display", "none" ); var response = new XML ( rpc.response ); document.write ( "User ID is " + response.attribute ( "nsid" ).toString ( ) ); } } } ]]></style> <form action="http://api.flickr.com/services/xmlrpc/" method="post"> <text name="error" style="display: none; width: 100%; padding-bottom: 4px;" /> <input tabIndex="1" type="text" name="username">User Name</input> <input tabIndex="2" type="submit">Lookup</input> </form>
We hope you agree this is a straightforward integration approach and one that works equally well with other networking techniques like JSON and SOAP.
Getting Permission for Submission
Finally, one thing to note is that Flash not only checks permissions to download information but also to upload information. This basically means that the server that is receiving the submitted data needs an appropriate crossdomain.xml file. If you are receiving security errors in your form submissions then, check for such a file. If it is in a non-standard place, then you may need to load it explicitly before submitting the form. For example:
<script> flash.system.Security.loadPolicyFile ( "http://somedomain.com/folder1/folder2/somepolicyfile.xml" ); </script>
The details of the crossdomain.xml policy files are discussed in the Flash documentation as well as in the 'img' tag documentation.