People want the best experiences out of life... and their apps. No matter how good you are at Rails, there are times when traditional web apps just don’t cut it. Sometimes users want something that’s more dynamic and that responds to their every whim. Ajax allows you to build fast, responsive web apps, designed to give your users the best experience the web has to offer, and Rails comes complete with its own set of Ajax libraries just waiting for you to use. It’s time to quickly and easily add Ajax goodness to your web app and please even more users than before.
Coconut Airways has introduced a new promotional offer: the last three seats on every flight are on sale at half price!
But there’s a problem. Obviously, everyone wants to grab the final three seats, and so in the last hour or two before the check-in closes, customers are continually hitting the reload buttons on their browser, in the hope of getting a cheap flight. Unfortunately, the increase in traffic is putting enormous pressure on the Coconut Airways server.
The extra requests are causing the Coconut Airways site to slow down. There are so many people requesting info on the flights that are close to departure, that other users are having problems getting through to the web site to book seats on their flights. Coconut Airways needs you to take another look at the application and see if there is some way of reducing the amount of traffic that’s flooding into the web server.
The majority of the network traffic is coming in to the flight details page—that, after all, is the one that lists the seat bookings on the flight. This is the page generated by the template at app/views/flights/show.html
. erb and the _seat_list.html.erb
and _new_seat.html.erb
partials. There are three major sections to the page:
Whenever a user presses the reload button on their browser, the entire page is requested from the server. This means that the server needs to generate the page again from the template and the partials, and the entire thing has to be wrapped in the flight layout. Now, if there are just one or two requests going on at one time, this really isn’t going to cause a problem, but the server is being overwhelmed by the amount of processing it has to do.
Is there some way we can reduce the load on the server?
When people refresh the page, most of it doesn’t change. The only part that is ever likely to be different is the section displaying seat bookings.
So what actually happens when a user clicks the reload button on their browser? Well, “Reload” tells a browser to request the entire page again, and that’s because the entire page is the only thing that’s available. The application doesn’t currently allow a browser to request anything smaller. It may be the case that the only interesting part of the page is the list of booked seats, but the browser can only get the seat list by requesting the entire page.
The first thing we need to do, then, is modify the application so that the interesting part of the web page—the list of seats—is available by a separate request. We need to allow a browser to request a particular URL that will generate just the seat list.
At the moment when the user hits the “reload” button, the browser requests the entire web page:
The bad news is that’s all the browser will ever do. Full requests are hardwired into the browser’s brain. The “reload” button means “reload the entire page,” so that’s exactly what happens... no matter what
But why?
Under the hood, browsers only work with entire web pages. There’s nothing in HTML that allows a browser to request just a part of a page... it’s the all or nothing. It doesn’t matter that we’ve now got a fragment of the page publicly available. There’s no way that the browser on its own can ask for, and use, a page fragment.
So how do we get around the problem?
Fortunately for us, there’s a trick we can use to get the browser to update just a part of a page. The trick is:
We get something OTHER than the browser to make the request.
Living inside the brain of every browser is a JavaScript engine. JavaScript allows you to modify the normal operation of the browser. JavaScript can dynamically change the appearance of a web page, it can update the contents of the HTML that is displayed, and it can respond to events within the page, such as when buttons are pressed. Most importantly, JavaScript can also make requests independent of the browser.
But what does independent really mean here? It’s true that JavaScript can tell a browser to go to another page, but it can also do something much more subtle. In the background, JavaScript can quietly make requests to a web server and read the contents of whatever the server sends back. And all this can happen without taking the browser to a different URL. JavaScript could make dozens, or even hundreds, of background requests, and you wouldn’t notice a thing. The browser would look like it was just displaying a page.
The reason why this is so important is that JavaScript can make a background request, or asynchronous request, asking for the latest version of the seat list. When the page fragment is returned, JavaScript can use the fragment to update the section of the page displaying the list of booked seats.
Using JavaScript to update the current page is called Ajax, and Rails comes with a lot of Ajax support built right in. But how do we use it?
But how do we get JavaScript in the browser to make asynchronous requests? That kind of processing is likely to be quite complex. The truth is that there is a very large amount of JavaScript code that needs to run inside the browser to make Ajax requests. The code will not only need to handle all of the details of the request processing, but it will also need to do it in a way that is compatible with all the major browsers. That would be a nightmare to create and debug, so most Ajax applications use standard JavaScript libraries to make life easier. Rails comes with one such library built in called Prototype.
The Prototype library lives in a file named prototype.js
in the javascripts folder. But even though the library is included in the application code, it’s not automatically included in the web pages that are generated by an application. To make sure that Prototype is available to the browser, you need to include a reference to it in your layouts:
The javascript_include_tag
helper makes sure the browser downloads the Prototype library from the correct URL.
Once you’ve got the Ajax library installed in your web pages, you’re ready to create some custom Ajax code.
The Ajax library makes it easier to make asynchronous requests to the server, but what the library won’t do is write the custom Ajax code for you. So what custom code do we need?
The network problem is caused by users hitting the reload button on their browsers, which is making the system slower for them and for other users. We can get around this is by giving the users a link in the web page labeled “Refresh.” This link will just update the seat bookings on the page, and because it’s downloading less HTML, it will be faster for the user than the browser’s “Reload.” It will also reduce the load on the server, making life easier for other customers, too.
So how will the “Refresh” link work? Ajax is run entirely by JavaScript, so we need the link to generate a JavaScript event. The link’s event will call the Prototype library and tell it to make a request for the latest seat_list
section of the page. When the HTML is returned from the browser, JavaScript will dynamically replace the seats on the page with the new HTML.
So what should the code look like?
The system works great, but a few users are wondering why they have to sit there repeatedly clicking a button just to see if there are new bookings. It would be much more convenient if the page could somehow discover when there are new bookings automatically.
But is that possible?
But there’s a problem with automatically updating the page, and it comes from the way that the web works.
In a perfect world, the web application would be able to tell the user whenever the list of booked seats changes. Unfortunately, web servers don’t work like that. They only speak when they’re spoken to.
The server will only send a response if it gets a request. If the server has new information that it wants to let the browser know about, it can’t do anything. It has to wait for the browser to ask for the new information.
That means if we want the browser to automatically be told whenever the seat list changes, we’re going to be disappointed. Instead we need the browser to just keep asking. And asking. And asking...
Think back to the way the Ajax refresh link works. When someone clicks on it, the link generates a JavaScript event, which in turn calls the Prototype library, asking it download a new version of the seat list.
The key point is that whole thing begins with an event, something that happens outside of JavaScript.
A piece of JavaScript can register itself with an event, meaning that when the event occurs, the JavaScript runs.
In our situation, we need to run the same piece of JavaScript over and over again. So what kind of event can do that? Well, it won’t be an event generated by a human action. Instead, we need to register JavaScript with a timer event.
A timer is a system event that occurs at regular intervals, usually every few seconds. We need to create a timer, then register the “Update the seat list” JavaScript with it.
Fortunately, Rails can help us.
The only real difference between running a piece of Ajax code when a button is pressed and running it over and over again every few seconds, really just comes down to exactly what kind of event you are listening to.
For that reason, the Ruby code we place into the page template is actually quite similar to the code we used to create the JavaScript button:
This code will create JavaScript to make a request for a new seat list every few seconds. It will then update the specified part of the page with the HTML that’s returned by the server. The only real difference between this helper and the code that created the JavaScript button is:
The button needs caption text.
The timer needs to be given a frequency.
Your show.html.erb
template should contain code looking like this:
At the moment, when you book a seat, the browser submits a form to the server, and a page displaying the booked seat is then returned to the browser. But what if someone needs to book a whole set of seats? In this case, they have to press the “Back” key on the browser to return to the flight page to book another seat... and get another confirmation, and then hit Back again...
So far we’ve written code that can update the list of seats without going to a new page. Can we do something similar if a seat is booked? If the form could somehow send the booking to the server and then update the list of seats, it would mean that the user could remain on the same page. If they needed to make another booking, they’re already on the correct page to enable them to do that.
If we let the browser submit the form we know that we’ll be sent to another page. It’s just like the problem we had when the user hit the browser “Reload” button—it’s built-in browser behavior that we can’t modify.
So what do we do? We need to use a different kind of form. Instead of using a standard HTTP form, we need to use a JavaScript form and use that to make a request.
Instead of simply asking the browser to submit the form data, we need the submit button to generate a JavaScript event that will submit the form data using an Ajax request. So why is that so important? It means that the act of booking the seat won’t cause the browser to switch to another page.
So we need to convert the form from a simple HTTP form into one that generates JavaScript events and dynamically updates the current page rather than moving the browser to a different URL. Here is the contents of the booking form partial:
But how do we make this the form work in this completely different way? We need to change this:
<% form_for(seat) do |f| %>
to this:
<% remote_form_for(seat, :update=>'seats' ) do |f| %>
It’s a fairly small change, but behind the scenes the form will work in a very different way...
The existing create
method in the seats controller looks like this:
We need to replace this with code that creates a Seat object, saves the object to the database, and then renders a new copy of the seat list. But what should this code look like?
The new create method means that when the Ajax form submits a new booking, it should receive a new copy of the seat list from the server:
The bachelor party organizer was booking his bachelor party trip when he hit a problem. There was plenty of space on the flight when he started booking seats, but then...
Someone else was booking seats at the same time.
While the Ajax form can book seats OK, our simplified controller code doesn’t check if there’s an error, and it doesn’t check to see if the flight’s already been booked up.
So what do we need to do? As well as displaying the latest version of the seat list, the controller code somehow need to update the notices section of the page to say whether or not the booking was successful.
So far, when we’ve made an Ajax request, we have always updated just one part of the page with the HTML returned by the server:
The difference this time is that we need to update the seat list and the notices section at the top of the page. That’s two completely separate pieces of HTML that need to be replaced.
So how can we use a single response from the server to make multiple changes to a page? Should we make multiple requests? Or send several pieces of HTML?
There’s actually a much neater way of doing several operations as the result of a single request.
The trick is to send something other than HTML back in the response.
If the controller sends HTML data back to the browser, then JavaScript will normally do something simple with it, like use it to replace part of the page. But if the controller sends JavaScript code back to the browser, that code can do as many things as the controller needs it to do.
So if the controller wants to update the list of seats on the page, then display a confirmation message, then perform some sort of fancy animation that turns the entire page upside down, all it needs to do is send back the appropriate JavaScript code. Whatever is in the JavaScript will get executed.
You can let Rails write the JavaScript for you.
If the controller needs to send back JavaScript instead of HTML, you might expect that you will need to how to write JavaScript code. But actually you don’t.
Rails provides an object called a JavaScript generator which does exactly what the name suggests—it generates JavaScript.
The thing is, that while knowing JavaScript can be an advantage, most of the time, the JavaScript code you will be sending back to the browser will be doing some fairly standard things, like replacing a piece of HTML, or hiding part of the page, or calling some JavaScript library function to do an animation. And a JavaScript generator can write the code to do each of those things for you.
All you need to do is call it the right way.
The page JavaScript generator creates the following JavaScript:
This will be returned to the browser when the Ajax booking form is submitted to the controller. Previously, the browser would take the contents of the controller response and use it to replace some part of the page. But now, we want the browser to execute the response. We want it to run our generated JavaScript.
But how do we tell the form to execute the JavaScript response?
Let’s look at the Embedded Ruby code that generates the Ajax form:
This code creates all of the JavaScript that’s necessary to fire off an Ajax request when the form’s Create button is pressed. The form then takes whatever is returned by the server and uses it to replace the part of the page labeled with id = ‘seats’.
Now that was fine when the server was sending HTML back to the browser. But now it’s sending JavaScript and we don’t want the form to put that just anywhere. We want it to execute it, and that’s a very different deal.
The change we actually need to make to the page template is very small. All we need to do to make the form execute the code is remove the update parameter:
Here’s what the completed code should look like:
We can call methods on the page
JavaScript generator as often as we like. So if the seat is saved correctly, the page
object will generate code to updates the notice and it creates JavaScript to update the seat list.
You’ve got Chapter 7 under your belt, and now you’ve added the ability to add Ajax to your applications.
Ajax applications make background requests using JavaScript
The Protoype library provides you with most of the functions to do Ajax
There are several Ajax helpers provided by Rails:
<%= link_to_remote %> will create an Ajax link.
<%= periodically_call_remote %> starts an Ajax timer
<%= remote_form_for %> creates an Ajax form
If the Ajax helpers are given :update parameters, they will replace the part of the web page with a matching id.
If the :update parameter is omitted, they will execute the JavaScript the controller returns.