Sunday, May 31, 2009

Ramaze, jQuery, and Ajax

This post tries to show how to use Ajax and jQuery with Ramaze. Since, as usual, I didn't really know anything about JavaScript, jQuery, or Ajax, this was quite a learning experience. Right up front, I need to thank the Ramaze email list is general and Gavin in particular for patiently answering my questions. The exchange that started this is documented here.

Also, this jQuery tutorial, provided me a convenient jumping off point and much of the code for the jQuery is taken directly from here or is based on this code and slightly modified.

What we're going to do in this example is some very simple Ajax which put up five numbers, let's you select one, and then prints the number you selected below along with the time you selected it at. You probably wouldn't normally do this with Ajax, but it does show how to use it.

Here's our start.rb file:


# start.rb

require 'rubygems'
require 'ramaze'

# You can access it now with http://localhost:7000/
class MainController < Ramaze::Controller

# Layout using page but not if it comes from an Ajax request.
layout(:page){ !request.xhr? }

def index
@title = "Ramaze / Ajax Example"
end

# This should only get called from an ajax request.
def choose
if request.xhr? # came from ajax request
# Create a JSON "object" of the choice that can in through the post
# request and the current time. For the time, we need to enclose it
# in double quotes so that it will be interpreted as a string.
"{ choice: #{request['choice']}, time: \"#{Time.now}\" }"
else
request['choice'].to_s
end
end
end

Ramaze.start



It's pretty straightforward but does have a couple of new items in it. First there's this:

layout(:page){ !request.xhr? }

This tells Ramaze to use page for layout accept in the case of an Ajax call. When you make an Ajax call, you want just the data returned (in our case some JSON) and not the entire formatted page. This took the help of the Ramaze email group (thanks Gavin!) to figure out. If you're searching you may find deny_layout. This has been removed in the latest Ramaze, so avoid it.

Our index method is very straightforward so we'll move on to the choice method. Here, we simply take in the data from the post in this case something that will look like {choice: 2} and create another JSON object that also contains the time from a Time.now call. Here, we need to enclose the time in double quotes, so that it will be interpreted as a string. Without the quotes, there is no way to interpret it correctly.

Our index.xhtml file is very simple


<!-- view/index.xhtml -->

<h2>#@title</h2>

<p>This example demonstrates the use of Ramaze and Ajax along with some JQuery and JSON.</p>

<!-- An empty container where we'll put the choices and the return values
values from the ajax/post call.
-->

<div id="ajaxContainer"></div>


The only interesting piece is the empty div element. We're going to put our ajax code into it via page.xhtml.

Finally, here's the page.xhtml


<!-- view/page.xhtml -->

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>#@title</title>
<link rel="stylesheet" type="text/css" href="/page.css"/>

<!-- Our jQuery is in the public/js directory -->
<script src="js/jquery-1.3.2.js" type="text/javascript"></script>

<script type="text/javascript">

// For jQuery put everything inside of the document ready code.
$(document).ready(function() {

// Create an array of text and links. We'll add this to the
// ajaxContainer using join()
var choiceMarkup = ["Please choose a number: "];
for(var i=1; i <= 5; i++) {
choiceMarkup[choiceMarkup.length] = "<a href='#'>" + i + "</a> ";
}
choiceMarkup[choiceMarkup.length] = (" Your choice will be printed below.");

// Grab the ajaxContainer so we can add the choiceMarkup to it.
var container = $("#ajaxContainer");

// Add markup to container
container.html(choiceMarkup.join(''));

// Add click handlers. Here we're going to get all of the anchor elements <a>
// and add the function to them.
container.find("a").click(function(e) {

// Don
't do the normal thing you'd do when clicking a link.
e.preventDefault();

// Send a post request to the choose method when a link is
// clicked. Pass in the JSON "choice: item" to the function.
// The callback routine gets a resultObject(JSON) and a status
// (not used and not actually returned). The choose() method
// will return JSON, the fourth parameter, to post.
$.post(
"/choose",
{choice: $(this).html()},
function(resultObject, resultStatus) {

// Take the resultObject and grab the choice and time from it and format it.
var result = [
"<br>Thanks for choosing. You chose: ", resultObject.choice, " at ", resultObject.time
];
// Add the result to the ajaxContainer at the bottom. This will create a new
// item each time one of the numbers is clicked.
$("#ajaxContainer").append(result.join(
''));
},
'json' );
});
});
</script>
</head>
<body>
<div id="header">
<h1>SteamCode</h1>
</div>
#@content
<!-- Set the footer in the center of the screen. -->
<div id="footer" style="text-align: center;">
<h5> Powered by Ramaze </h5>
</div>
</body>
</html>



The first new thing we see here is the inclusion of the jQuery library. I've put it into the public/js directory. If you use a different version other than 1.3.2 you will need to change this line. Next is our actual script. All jQuery code is put inside the document ready block. After that, we create some text, links for the five choices, and a bit more text and shove these into an array, choiceMarkup. Then we grab the ajaxContainer defined in our view/index.xhtml file and put the choiceMarkup into it.

Next up, we find all of the anchor, a, links and add a function to them when they're clicked. The first thing we do in this function is disable the normal response to a click and then add our own code. Here we do a post function, calling the choose method in our controller and pass JSON that looks like {choice: 3} (we talked about this above). When the function returns, we have our data in the result object and as also discussed, this contains the choice and the time. We format this and then add it to our ajaxContainer. This will add a new line each time something is clicked and continue until we do a refresh. The final parameter to the post is the 'json' which tells jQuery that the data returned will be (obviously) JSON.

Everything else we've seen before and I won't go over it again.

There's quite a few new things to see in here and there aren't too many examples on the web showing the use of Ajax, jQuery, and Ramaze. Here's a post, but I'm not sure that it will work with the new Ramaze. It may take some modifications to get it working. Here's a nice jQuery link that helped a bit. Finally, for JSON, try here. JSON is not complex, but you do have to follow the rules (I didn't originally and it took a while and some help to figure out).

Let me know of any questions or comments.

2 comments:

  1. You say "If you're searching you may find deny_layout. This does not work in the latest Ramaze so avoid it." Just to be clear - it's not so much that it doesn't work, but that it no longer exists. Just in case anyone read that sentence and thought that Ramaze contained dodgy non-working features.

    I notice that you create your JSON string manually in your controller. Alternatively you could have used the json gem and simply called .to_json on a hash. But in this case it's certainly plenty easy to just do it maunally, and it takes some of the mysticism out of the JSON too.

    ReplyDelete
  2. Thanks Sam. I've corrected the comment on deny_layout. You're quite right about JSON too. I'll give the gem a shot if I have to do anything significant with JSON.

    ReplyDelete