2.1. The Old-Fashioned WayTo start off, let's do Ajax with the simplest thing that could possibly work: click a link and present a response from the serverusing XMLHttpRequest directly, without Prototype or Rails' JavaScript helpers. Using XMLHttpRequest is often portrayed as being rocket science. But you'll find that, with a little practice and perhaps a couple new concepts, it's not as tricky as its reputation suggests. 2.1.1. Starting a ProjectIf you didn't create the example Rails skeleton in the last section, do so now, from your system's command line: rails ajaxonrails cd ajaxonrails script/server Browse to http://localhost:3000, and you should see Rails' welcome screen (for development purposes, script/server starts an HTTP server on port 3000). Back at the command line, let's generate a new controller called Chapter2Controller with an action called myaction. (Since you're already running the server in one terminal window, you'll want to open another.) script/generate controller chapter2 myaction
Go to http://localhost:3000/chapter2/myaction. You should see the newly generated view as in Figure 2-1. Figure 2-1. Newly generated Rails controller and viewNotice that, by default, the first part of the URL determines the controller, and the second part determines the actionthe method within the controller. Now edit the template for that action, which is in app/views/chapter2/myaction.rhtml. Add this bit of HTML to the bottom: <p><a href="#" onclick="alert('Hello !');">Inline alert( )</a></p> As you can see, we're creating a paragraph with a basic linkbut instead of the usual HRef attribute, we use onclick, where we provide a JavaScript snippet to be run. Refresh your browser, and click the link. You'll see something like Figure 2-2. Figure 2-2. Basic alert boxHaving more than one or two statements inline in an onclick attribute would quickly get cumbersome. Let's extract it to a new JavaScript function, by adding this below everything else: <p><a href="#" onclick="customAlert( );">Call custom function</a></p> <script type="text/javascript"> function customAlert( ) { alert('Hello from a custom function.'); } </script> Try it again, and see what happens. The result should be essentially the same as before. Enough warm-up, let's do some Ajax. (But keep in mind, we are still peering under the hoodby the end of the chapter, the framework will hide much of the complexity.) First, you'll need to define a new action in the controller, app/controllers/chapter2_controller.rb. There's already an action called myaction, so let's call the new one myresponse. To create it, create a new file, myresponse.rhtml, inside app/views/chapter2. For the contents of the file, enter: Hello from the server. Just to make sure everything's working, try visiting that action in your browser at http://localhost:3000/chapter2/myresponse, and you'll see something like Figure 2-3. Figure 2-3. Result of myresponse actionNow, back in myaction.rhtml, add another bit of HTML and JavaScript. <p><a href="#" onclick="serverSideAlert( );">Call server-side function</a></p> <script type="text/javascript"> function serverSideAlert( ) { var request = new XMLHttpRequest( ); request.open('get', '/chapter2/myresponse', false); request.send(null); alert(request.responseText); } </script> Point your browser back to http://localhost:3000/chapter2/myaction, and click the new link. If all goes well, you'll get a message from the server, as seen in Figure 2-4. Be warned, this example won't work in Internet Explorer browsers prior to version 7 (we'll address that problem next). Figure 2-4. Result of first Ajax callNow we're getting somewhere! Just to convince yourself, take a look at the terminal prompt, where script/server is running. Every time you click the Ajaxified link, a new hit will register: Processing Chapter2Controller#myresponse [GET] Parameters: {"action"=>"myresponse", "controller"=>"chapter2"} Completed in 0.00360 (278 reqs/sec) | Rendering: 0.00027 (7%) | 200 OK [http://localhost/chapter2/myresponse] The big problem with the current example is that it doesn't work in one of the most popular browsers, Internet Explorer 6. The reason is that Microsoft's implementation of XMLHttpRequest is an ActiveX object (actually, two of them, depending on the version of IE), which must be created differently. In order to cover all the bases, we'll need to create a little function to help sort it out. Here's the IE-safe version to add: <p><a href="#" onclick="IEAlert( );">Call server(IE-safe)</a></p> <script type="text/javascript"> function IEAlert( ) { function getRequestObject( ) { try { return new XMLHttpRequest( ) } catch (e) {} try { return new ActiveXObject("Msxml2.XMLHTTP") } catch (e) {} try { return new ActiveXObject("Microsoft.XMLHTTP") } catch (e) {} return false } var request = getRequestObject( ); request.open('get', '/chapter2/myresponse', false); request.send(null); alert(request.responseText); } </script> This iteration is the same as before, except that instead of creating an XMLHttpRequest object directly, it calls getRequestObject( ), which walks through the possible options. The function makes use of TRy, a JavaScript statement that can be used to catch exceptions and stop them from bubbling up. (This example also introduces an idea that may be new to some developers, defining a function within a function.) So far, we've been cheating a little, because the Ajax call isn't asynchronous. The third parameter of the request.open( ) method determines whether the call is asynchronous, and we have been setting it to false. Hence, request.send( ) is blockingthe JavaScript interpreter stops execution at that line and doesn't move on until the request comes back. To make the call asynchronous, we'll have to rearrange things some more. Add this block to myaction.rhtml: <p><a href="#" onclick="asyncAlert( )">Call async server-side</a></p> <script type="text/javascript"> function asyncAlert( ) { function getRequestObject( ) { try { return new XMLHttpRequest( ) } catch (e) {} try { return new ActiveXObject("Msxml2.XMLHTTP") } catch (e) {} try { return new ActiveXObject("Microsoft.XMLHTTP") } catch (e) {} return false } var request = getRequestObject( ); request.open('get', '/chapter2/myresponse'); request.onreadystatechange = function( ) { if(request.readyState==4) alert(request.responseText); } request.send( ); } </script> In all the previous examples, we called request.send( ) and then immediately accessed request.responseText( ). Now that we're sending an asynchronous request, that's not possiblethe response might not have returned by the time it's referenced. To handle this problem, the XMLHttpRequest object has a readyState attribute that changes during the life cycle of a request. It also has an attribute called onreadystatechange, where you can define a function that will be called every time readyState changes. In this example, we define a function that checks to see if readyState is 4 (which means the request is complete; readyState codes are fully described in Chapter 3), and if so, presents an alert box. Dealing with asynchronous events can take some getting used to, but it's an essential part of programming Ajax by hand. |