Now that you've got JQuery in your Camping application, one of the things you might want to do is take advantage of JQuery's AJAX capabilities. I'm going to assume you're familiar with AJAX, and JQuery's implementation of it, and just talk about how to get it going in Camping. If it's foreign to you, you might want to look at a handy AJAX and JQuery tutorial.

To save me from having to make up an actual interesting example, I'm going to write a hideously incomplete todo list application, much of it ripped off and JQuery-fied from Jan Lehnardt's CouchDB tutorial. Though not the CouchDB stuff, because that's not what I'm writing about. Though it's pretty cool, and you should check out the tutorial. Okay, back to the tuscan ham.

First we set up a Camping application and deliver JQuery. I'll use the more general method of serving static files to do this.

Camping.goes :Ajax module Ajax::Controllers class Index < R '/' def get render :index end end class Resource < R '/resources/(.+)' def get resource types = { '.css' => 'text/css', '.js' => 'text/javascript', '.jpg' => 'image/jpeg', '.jpeg' => 'image/jpeg', '.gif' => 'image/gif', '.png' => 'image/png' } full_path = File.expand_path(File.dirname(__FILE__)) + "/resources/#{resource}" if resource.include? ".." # prevent directory traversal attacks @status = "403" "403 - Forbidden" elsif not File.exists?(full_path) @status = "404" "404 - File not found" else @headers['Content-Type'] = types[resource[/\.\w+$/]]||"text/plain" @headers['X-Sendfile'] = full_path end end end end
The root of the server simply renders an index page. We've also set up the route for serving static files, which we'll use in our views to send JQuery.
module Ajax::Views Camping::Mab.set(:indent, 2) def index html do head do title 'Taking AJAX Camping' script :src => R(Resource, 'jquery.js'), :type => 'text/javascript' script :type => 'text/javascript' do " $(document).ready(function() { $('#todo').focus(); $('#add').submit(function() { var value = $('#todo').val(); $('#todo').val(''); // reset input box if (value) { // don't add empty todos $.ajax({ type: 'POST', url: '/add', data: 'todo=' + value, success: function(msg){ // Add the todo to the top of the list $('#todos').prepend('<li>' + msg + '</li>'); } }); } // focus the input field when the document loads $('#todo').focus(); return false; }); });" end end body do h1 'Taking AJAX Camping' form.add! do legend "Another todo list that doesn't work" input.todo! :type => 'text', :name => 'todo' end ul.todos! "" end end end end

A few things are going on in the view. First we make a call to Camping::Mab.set(:indent, 2) which makes the output pretty. While not required, it does make things easier if you're debugging. We then use a script element to include JQuery, which is served statically through the Resource route, and set up an AJAX call to the URL /add. Entering some text in the textbox and hitting enter triggers the AJAX call, and adds whatever was returned to the list.

Of course, we don't have a route that will respond to a request to /add, so we'd better add that now. Whack this in your controller.
class Add < R '/add' def get "#{@input.todo}: #{Time.now.to_s}" end end

The current time will now be returned in response to the AJAX call. It's not a very interesting use of AJAX. We should be saving the todo to a database, but that's not the point of this piece and would just clutter up the code. So there.

But hang on, we're not rendering a view? That's right, you don't need to render a view at all, simply return text straight from the controller and everything is hunky dory. This is AJAX option one. Of course, this isn't just an AJAX peculiarity, any controller can just print out a slab of text or HTML. This pretty much defeats the purpose of an MVC framework, as we should be separating the display logic.

So, even though it's a trivial example, let's put our presentation in a view. Change the Add route in your controller.
class Add < R '/add' def get @todo = @input.todo render :add end end
Now add the add view.
def add text "#{@todo}: #{Time.now.to_s}" end
Great, now that's separated out and we're using a view. The issue with this is that when your application gets a bit bigger, adding a couple of other views, you're going to want to use a layout. If you do, the AJAX call will now return the time, all wrapped up in the layout HTML. Probably not what you want. The way around this is to render the AJAX as a partial. So, a subtle change to the controller, calling a partial.
class Add < R '/add' def get @todo = @input.todo render :_add end end
And we have to rewrite the views to use a layout and the partial.
module Ajax::Views Camping::Mab.set(:indent, 2) def layout html do head do title 'Taking AJAX Camping' script :src => R(Resource, 'jquery.js'), :type => 'text/javascript' end body { self << yield } end end def index script :type => 'text/javascript' do " $(document).ready(function() { $('#todo').focus(); $('#add').submit(function() { var value = $('#todo').val(); $('#todo').val(''); // reset input box if (value) { // don't add empty todos $.ajax({ type: 'GET', url: '/add', data: 'todo=' + value, success: function(msg){ // Add the todo to the top of the list $('#todos').prepend('<li>' + msg + '</li>'); } }); } // focus the input field when the document loads $('#todo').focus(); return false; }); });" end h1 'Taking AJAX Camping' form.add! do legend "Another todo list that doesn't work" input.todo! :type => 'text', :name => 'todo' end ul.todos! "" end def _add text "#{@todo}: #{Time.now.to_s}" end end

Of course, you don't have to just return text. You could also return JSON or XML (setting the correct Content-type header) or any other kind of structured data you want.