In: Categories » Computers and technology » AJAX » Do not Submit Your Forms Ajax Them
This article focuses both on solving the problem of sending data using HTTP POST, and on the definition of a state that is associated with an HTML page. This article covers the following topics:
• Explanation of the problems associated with HTTP POSTs
• How to fix the problems related to HTTP POSTs
• Illustration of a workflow architecture
• Association of state with a handler
Problem You want to use Ajax to solve some of the problems associated with submitting forms the traditional way with POST.
Theory When implementing a workflow such as buying an airplane ticket, you use HTML forms. An HTML form has a number of UI elements, such as a text box, a list box, or a combo box, that users can use to enter information. When they’re content with the information entered, they press a button to submit the data to the server. The server receives the data and generates some kind of result. In previous articles, an HTTP POST was used when data was sent to the server. None of the examples used the HTTP POST in the context of an HTML form. When an HTTP POST is used in the context of an HTML form, the server processes the form and generates some data that then becomes the next HTML page. From a content-processing perspective, the difference in processing between an HTTP GET and POST is dramatic. The client could call the URL multiple times, and the content would be generated each time. An HTTP POST is different in that the client has to send state. Based on that state, the server does some processing and generates some content. The state that the client has to send is not optional. A browser helps the client by caching the state sent previously, and when requested, sends the state again with an appropriate dialog box. The problem with sending the state again is that you might execute the process to buy a plane ticket again, thus buying two. Imagine a situation in which you’re buying a plane ticket and you want to experiment with different times and airports. Yet you would probably reply that in fact, this is an incorrect assertion, as the dialog box rarely appears. The reason why the dialog box rarely appears is because you’re guided and told which buttons you can and cannot press. To go back a step in the workflow process, you would not press the Browser Back button. Next time, look closely at a typical HTML-based workflow, and you’ll notice how information is repeated on multiple screens and how the workflow guides the user. It is very clever, and users are typically not aware of being manipulated into thinking in certain directions.
Solution Web application frameworks have gone to great extents to solve the HTTP POST problem. You need to look no further than the ASP.NET framework to see the complexity involved in making sure that the Browser Back or Refresh buttons don’t cause a mountain of problems with respect to the server-side state. ASP.NET is not to be blamed, because its creators were trying to fit a dynamic architecture into an old static Web infrastructure. The following points sum up the advantage to using this approach:
• The data is sent to the server only once, and only if the user presses the Form Submit button.
• The server side stores an application state that you can use HTTP GET to call whenever a particular HTML page is called. Therefore, you can use the Browser Back and Browser Forth buttons to move between pages without corrupting the server-side state or the client-side HTML page.
Converting the POST to Use the XMLHttpRequest Object
The solution to the HTTP POST problem is simple, but it requires the ability to use the XMLHttpRequest object. In this section, you’ll learn how to convert the original application to use the new architecture. The original and new HTML pages will be illustrated at a code level. The idea of this section is to illustrate the conversion.
The Original HTML Form
The original HTML form used an HTTP POST, and is illustrated as follows:
<html> <head> <title>Title</title> </head> <body> <form action="/ajaxrest articles/architecture/forms/Posted.ashx" method="POST"> <input type="text" name="example" /> <input type="submit" value="Submit" /> </form> </body> </html>
The bold form attributes show that when the Submit button is pressed, the URL /ajaxrest articles/architecture/forms/Posted.ashx is called using a POST. From a Representational State Transfer (REST) perspective, the URL only accepts a single verb, POST. This is the heart of the problem in that you cannot retrieve the page using a GET, which is the default HTTP verb used by a browser.
The Converted HTML Form
The converted HTML form uses the same HTML constructs, but it delegates the POST to an XMLHttpRequest and retrieves the next page using a GET. The following code shows the converted HTML form:
<html>
<head>
<title>Title</title>
</head>
<script language="JavaScript" src="/scripts/jaxson/common.js"></script>
<script language="JavaScript" src="/scripts/jaxson/communications.js"></script>
<script language="JavaScript" src="/scripts/jaxson/commonmorphing.js"></script>
<script language="JavaScript" type="text/javascript">
var representations = { };
function OnSubmit() {
var obj = RepresentationManager.iterateHtml.get( representations,
document.getElementById( "form"));
var stringToSend = Ops.serializeCGI( obj);
var asynchronous = FactoryHttp.getAsynchronous();
asynchronous.settings = {
onComplete : function(xmlhttp) {
location.href = document.getElementById( "form").action;
}
}
var obj = new Object();
obj.data = stringToSend;
obj.length = obj.data.length;
obj.mimetype = "application/x-www-form-urlencoded";
asynchronous.post( document.getElementById( "form").action, obj);
}
</script>
<body>
<form id="form"
action="/ajaxrest articles/architecture/forms/AjaxPosted.ashx"
method="POST">
<input type="text" name="example" />
<input type="button" value="Submit" onclick="OnSubmit()"/>
</form>
<div id="output"></div>
<div id="error"></div>
</body>
</html>
The important modified parts of the HTML page are shown in bold. The overall change has been to convert the input type of submit to button, and have the button implement the click event. The click event calls OnSubmit and is responsible for doing a POST and GET. When OnSubmit is called, the function RepresentationManager.iterateHTML.get is called, which is used to extract the state in the HTML form elements. Normally, when using the previous HTML form-submit technique, the browser manages the extraction of the state from the HTML form. It is more complicated to perform a custom extraction, but it does give the added benefit of being able to extract the state from other HTML elements, such as div or span elements. You need to think of a GET method call as a general state extraction that is assigned to a JavaScript object.
When the JavaScript object that contains the state has been created, you need to convert the object into a computer graphics interface (CGI)-encoded query string. The function Ops.serializeCGI carries out the conversion. Again, you need to perform a custom serialization, but you have the added flexibility of serializing to a CGI-encoded query string, a persisted JavaScript Object Notation (JSON), or even an XML string. Then when you have the CGI query string, you use the remaining code in OnSubmit to POST the data to the server. Notice that the POST URL used is from the HTML form. Once the POST has been completed in the implementation of the onComplete method, the location.href is assigned the URL that was POSTed to.
Here is where you might get confused. Why first POST and then GET the same URL? It might seem more efficient to perform a single POST or GET. The reason for executing the two verbs is due to browser history. The POST is executed by the XMLHttpRequest object and thus not part of the browser history. The POST is used to create a state on the server, and then the browser executes the GET, so that the page is recorded in the browser history. As a result, the browser has two GETs in the history, instead of a GET and a POST, as was the case in the original HTML form example. When you have two GETs in the history, you don’t need to send state to the server, as the state is retrieved. The client still calls the same URL, but the functionality of the server URL has changed. In the modified HTML form example, the server has to react to a POST and a GET. However, the server must associate a state with the request, which was not necessary in the case of the original HTML form. In the original HTML form, the state was generated with every POST. Associating a state with the request is not that difficult and only requires the use of the Web application-provided session mechanism. You have to change the server-side code so that the information generated by the POST is stored in the session and retrieved when the GET is called. The following code shows an extremely simple implementation of the original serverside code:
public void ProcessRequest(HttpContext ctx) {
ctx.Response.ContentType = "text/html";
ctx.Response.Write("<HTML><BODY>You wrote <b>" +
ctx.Request["example"] + "</b></BODY></HTML>");
}
Here’s the modified server code:
public void ProcessRequest(HttpContext ctx) {
ctx.Response.ContentType = "text/html";
if( ctx.Request.HttpMethod == "POST") {
ctx.Session.Add( "example", ctx.Request[ "example"]);
ctx.Response.Write("<HTML><BODY>You wrote <b>" +
ctx.Request["example"] + "</b></BODY></HTML>");
}
else if( ctx.Request.HttpMethod == "GET") {
ctx.Response.Write("<HTML><BODY>You wrote <b>" +
ctx.Session["example"] + "</b></BODY></HTML>");
}
}
The example is coded using ASP.NET, but even if you’re not an ASP.NET programmer, you should be able to follow the explanation. In the original implementation of ProcessRequest, it was expected that you call the method using a POST. To generate the content, you extract the example variable using the method ctx.Request. As the original implementation of ProcessRequest is defined, there is no memory of having been called previously. The generated output is dependent on the parameters sent in the POST. The original code is considered unsafe, because a POST is assumed. If a GET is executed, then problems will occur, because an inconsistent state will be defined. In the modified source code, you first test the HTTP verb that is being called (ctx.Request. HttpMethod). If an HTTP POST is called, then the generated content is like the original HTML form example, and the state is saved to the session (ctx.Session). If an HTTP GET is called, then the generated content is similar to the POST, except that the state is retrieved from the session.
POSTing Forms and REST
In the modified example, the URL uses /ajaxrest articles/architecture/forms/AjaxPosted.ashx and uses a session. The problem with this approach is that HTTP cookies are used to determine what content is generated. The solution is used to illustrate that you can relatively easily modify an existing application that uses a POST into one that uses Ajax-combined POST and GET. Next, you modify the POST and GET combination to use no session variables. Or, to put it more succinctly, it’s OK to use session variables as long as they don’t use cookies. For example, in the ASP.NET architecture, it is possible to have the session variables modify the URL to include an identifier that cross-references to a session variable. Having the infrastructure modify the URL makes it possible to articlemark the URL and reference it at some later point in time. However, you’ll run into a problem if you use session variables that time out. A timed-out session variable, even if it doesn’t use cookies, is problematic, because users might have articlemarked the URL and found out later that they cannot reference it. It is possible to just set the session variable to time out a very long time from now, but this doesn’t solve the problem of the state eventually disappearing. In the case of this article, the last thing you want to happen is the disappearance of state.
Therefore, you need to re-architect the server to use a cache instead of session variables. The advantage of the cache is that it gives you the ability to control when a piece of state remains and is deleted. The next problem is a bit more complicated. In the modified example of the HTML form, the page PostAjax.ashx responded to either a GET or a POST. When the GET was called, the state of the HTML page and the HTML page itself were combined in one step. From Article 5, you know that combining the HTML page with its state is wrong. You want to be able to load the state as aWeb service call. This means two URLs are necessary. The “Supporting HTML Pages with Relative URLs” section in Article 5 shows you how to manage the two URLs.
• /workflow/page1/1234: This URL is used to download the HTML page that is displayed in the browser. The URL supports only the GET verb, as it is used to download the HTML content from the server.
• /services/workflow/page1/1234: This URL supports both the POST and GET verbs and is the state associated with the HTML page URL. The state URL is created using the techniques explained in the section “Supporting HTML Pages with Relative URLs” in Article 5. Both example URLs are appended with the number 1234, which represents the unique cached data identifier used when loading the state. In the state and HTML page solution, the state created by one HTML page is delegated to another page. Look back at the code of the modified HTML form example. The state of the HTML page is associated with the PostAjax.ashx page. With the state and HTML page solution, the state needs to be associated with the page, and thus the implementation of the modified HTML form example changes slightly to the following:
<html>
<head>
<title>Title</title>
</head>
<script language="JavaScript" src="/scripts/jaxson/common.js"></script>
<script language="JavaScript" src="/scripts/jaxson/communications.js"></script>
<script language="JavaScript" src="/scripts/jaxson/commonmorphing.js"></script>
<script language="JavaScript" type="text/javascript">
var representations = { };
function OnSubmit() {
var obj = RepresentationManager.iterateHtml.get( representations,
document.getElementById( "form"));
var stringToSend = Ops.serializeCGI( obj);
var asynchronous = FactoryHttp.getAsynchronous();
asynchronous.settings = {
onComplete : function(xmlhttp) {
location.href = document.getElementById( "form").action;
}
}
var obj = new Object();
obj.data = stringToSend;
obj.length = obj.data.length;
obj.mimetype = "application/x-www-form-urlencoded";
asynchronous.post(URLEngine.serviceURL(), obj);
}
URLEngine.serviceURL = function() {
return "/services/" + this.requestChunks.slice( 0,
this.requestChunks.length - 1).join( "/");
}
function Initialize() {
var asynchronous = FactoryHttp.getAsynchronous();
asynchronous.settings = {
onComplete : function(xmlhttp) {
var obj = Ops.serializeFromCGI( xmlhttp.responseText);
RepresentationManager.iterateHTML.set( representations,
document.getElementById( "form"), obj);
}
}
asynchronous.get(URLEngine.serviceURL());
}
</script>
<body onload="Initialize()">
<form id="form"
action="/ajaxrest articles/architecture/forms/AjaxPosted.ashx"
method="POST">
<input type="text" name="example" />
<input type="button" value="Submit" onclick="OnSubmit()"/>
</form>
<div id="output"></div>
<div id="error"></div>
</body>
</html>
The bold code shows the additional pieces necessary to implement the state and HTML page solution. As explained in Article 5, when the page has been loaded completely, the body.onload event is triggered and calls the function Initialize. Calling Initialize results in the state being retrieved using the URL dynamically computed using the function URLEngine. servicesURL. When the state has been retrieved, it is deserialized and assigned to the HTML page using the methods serializeFromCGI and iterateHTML.set. The implementation of OnSubmit stays the same, with aminor modification being the URL where the state is being POSTed. The POST URL must be the same as the one used to retrieve the data in the Initialize function. The next page that loads after the state stays the same; you can modify it to your liking.
The URL that determines the next page warrants a little discussion. In the case of the HTML code, the loaded URL is /ajaxrest articles/architecture/forms/AjaxPosted.ashx. The URL is not appended with a cache identifier, meaning that the URL is not associated with any state. In the modified HTML form example, a cookie defines the state that is associated with the URL. Since you’re not using cookies, the URL has no state. If this is your desired effect, then you can leave the code as is. However, this probably is not the desired effect, so you need to associate the cache identifier with the URL. Therefore, the URL must be /ajax articles/architecture/forms/1234/ AjaxPosted.ashx or something along those lines. The important bit is that the unique cache identifier is included in the URL. However, the URL is hard-coded, so you need to modify it dynamically, much like the approach illustrated in Article 5. In a nutshell, you need to define groupings of URLs. For example, one workflow might have the URLs /workflow/app-name/page1, /workflow/app-name/page2, and so on. Each of the URLs would be associated with a cache identifier. Thus, whenever you navigate one of the URLs, you’ll be navigating the cached data associated with the workflow application.
Remember the following points:
• Using the XMLHttpRequest POST and GET combination avoids the dreaded HTTP “Post data again” dialog box. You can rest easy that your clients won’t be purchasing the same item twice.
• By having POST and GET as separate steps, you can optimize the efficiency of the Web application, because the data associated with the GET can be cached.
• You don’t need to make huge changes to your Web application to take advantage of the separate POST and GET, as illustrated in the modified HTML form example.
• In a complete state navigation implementation, use a two-URL approach, where one URL defines the HTML page, and the second URL represents the state Web service called by the HTML page.
• In a complete state navigation implementation, the URLs for the state and the next page need to be algorithmically definable.
• You can combine this solution with the data-validation articles illustrated in Article 3.
• To associate cached data with a URL, you should not use cookies, but instead use unique cached identifiers in the URL. This allows you to articlemark an HTML page and its associated state for later reference.
legal notice
Our website is not responsible for the information contained by this article. Web-articles is a free articles resource.
Suggestion: If you need fresh, daily updated content for your website, feel free to use our service. Click here for more information.
Useful tools and features
If you like this article (tutorial), please link to it from your web page using the information above.
related articles
The Easiest Way to Get Started with Ajax and REST Problem You want to know the best way to get started with writing Ajax and REST. Solution When developing an Ajax and REST application, you must decide on the tools and frameworks you’ll use. The choice is simple: Use whatever you’re using today, and write some Ajax applications. You don’t need to change the tools you’re using today. Whether you’re using ASP.NET, JavaServer Pages (JSP), PHP, Ruby, or Python, you...
2. Testing a Dynamic Contract with Ajax
Coding the Contract Using Test-Driven Development Techniques Coding the contract using agile and test-driven development techniques requires writing a number of tests and implementing aMock URL layer. Problem You want to code the contract using these development techniques. Solution To demonstrate, let’s define a use case, implement the use case as a contract, write a test case(s) to implement the contract, implement the contract in the Mock URL, and finally...
3. Testing the Client Side Logic
Problem You want to effectively test your application’s client-side logic. Theory Testing GUI code tends not to be a productive task because of the complications that arise. The main complication is how to test the correctness of a user interface. Imagine a situation where clicking a button causes a table to be filled with data. Now imagine that when a check box is checked and the button is clicked again, a different table is filled with content. The fact that clicking the same button results in two ...
4. Understanding JavaScript and Types
Understanding JavaScript and Types Problem You want to work around the fact that JavaScript does not have types declared for its variables. Theory JavaScript code does not have any variables with a declared type. The lack of typed variables is apparent when you declare functions. That said, not having typed variable declarations does not mean JavaScript has no types or no type safety. Let’s start out with the simple declaration of a function, as illustrated by the following ex...
Coding Using Conventions and Not Configurations Problem You want to make your JavaScript constructs more efficient by applying the Rails “convention over configuration” principle to them. Theory You may already be familiar with the programming platform Ruby on Rails, which is used to build Web applications. The focus of this recipe is not Ruby on Rails, but one aspect of Ruby on Rails namely, convention over configuration (see http://en.wikipedia.org/wiki/ Ruby_on_Rails for m...
6. Advantage of parameterless functions in JavaScript
Using Parameterless Functions Problem You want to take advantage of parameterless functions in JavaScript. Theory JavaScript functions for the most part have parameters. You may think that the previous sentence states the obvious after all, without parameters, what data could be passed to a function? JavaScript has the ability to declare functions that have no parameters, even though the caller of the function has passed parameters to the function. For example, let’s look at...
7. JavaScripot Functions
Treating Functions Like Objects Problem You want to take advantage of the fact that functions are objects (remember, everything is an object in JavaScript). Theory Many people think that a function is some keyword used in JavaScript. A function is also an object that can be manipulated. Knowing that a function is an object makes it very interesting from the perspective of writing JavaScript code, because the code can treat the function like another other object. This mean...