Chapter 7. Ajax: Avoiding the traffic

image with no caption

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.

There’s a new offer at Coconut Airways

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.

image with no caption

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.

Which parts of a page change most?

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:

image with no caption

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?

We need a way to update just the seat list

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.

image with no caption

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.

Do this!

Add the two pieces of code above to routes.rb and seats_controller.rb.

Doesn’t the browser always update the entire page?

At the moment when the user hits the “reload” button, the browser requests the entire web page:

image with no caption

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.

So what ELSE can make a 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.

image with no caption
image with no caption

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?

First we need to include the Ajax libraries...

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.

image with no caption

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:

image with no caption

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.

...then we need to add an Ajax “Refresh” link

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?

image with no caption

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?

The browser needs to ask for an update

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.

image with no caption

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...

image with no caption

But SHOULD we make the browser ask over and over again?

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.

image with no caption

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.

You listen to a timer like you listen to a button

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:

image with no caption

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:

  1. The button needs caption text.

  2. The timer needs to be given a frequency.

Your show.html.erb template should contain code looking like this:

image with no caption

Someone’s having trouble with their bachelor party

image with no caption

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.

image with no caption

The form needs to make an Ajax request

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.

image with no caption

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.

The form needs to be under the CONTROL of JavaScript

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:

image with no caption

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...

We need to replace the create method

The existing create method in the seats controller looks like this:

image with no caption

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?

So what effect does this code have?

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:

image with no caption

There’s a problem with the flight bookings

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...

image with no caption

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.

image with no caption

Brain Power

This sounds straightforward, but what problem can you see with this?

We only know how to update one part of the page at a time

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:

image with no caption

So what’s different this time?

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.

The controller needs to return JavaScript instead of HTML

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.

image with no caption

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.

Note

[Note from the Good Taste police: You really don’t want to do this]

image with no caption

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.

image with no caption

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.

So what does Rails generate?

The page JavaScript generator creates the following JavaScript:

image with no caption

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?

If you don’t say where to put the response, it will be executed

Let’s look at the Embedded Ruby code that generates the Ajax form:

image with no caption

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:

image with no caption

The completed code can will now do several things

Here’s what the completed code should look like:

def create
  @seat = Seat.new(params[:seat])
  render :update do |page|
    if @seat.save
      page.replace_html 'notice', 'Seat was successfully booked'
    else
      page.replace_html 'notice', 'Sorry - the seat could not be booked'
    end
    page.replace_html 'seats', :partial => 'flights/seat_list',
      :locals => {:seats => @seat.flight.seats }
 end
end

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.

Tools for your Rails Toolbox

You’ve got Chapter 7 under your belt, and now you’ve added the ability to add Ajax to your applications.

Rails Tools

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.236.142.143