Ajax in Sinatra

Ajax has been around for a while now but that doesn’t mean it is any less fun. The nice thing about Sinatra is you are left to do as much or little JavaScript as you like and you can do it in any way that you want as well. In this ditty I hope to show that it’s easy to add some Ajax magic to a Sinatra app (with a little help from a JavaScript framework).

Set Up the App

First of all we will build a very simple app. It’s important to make sure that the app works without any Javascript in case people don’t have it installed. This basic app will do a few things:

  • Tell us the date and time
  • Show a message from the server
  • Reverse some text (just like in our first app

To start with we will require the necessary gems:

%w[rubygems sinatra haml].each{ |gem| require gem }

Note the neat way of doing this as a one-liner - you can just add as many gems as you want to the array as you require more gems.

Next up are the handlers:

get('/'){ haml :index }
get('/response'){  "Hello from the server" }
get('/time'){ "The time is " + Time.now.to_s }
post('/reverse'){ params[:word].reverse }

All one liners again. The first route is the main index page. It just uses Haml to display some html. The next page is the message from the server (a simple hello in this case, but it could be anything). The third route gets the current time and date using Ruby’s Time object. The last route takes a text input from a form (held in the params hash) and reverses it using ruby’s String::reverse method. Notice that the last route is a POST request because it is taking data posted from a form.

Last of all we need some views. These will go in the same file at the bottom:

__END__
@@layout
!!! 5
%html
  %head
    %meta(charset="utf-8")
    %title Sinatra Ajax
  %body
    %h1 Sinatra Ajax
    = yield

@@index
%form#reverse(action="/reverse" method="POST")
  %input#word(type="text" name="word")
  %input(type="submit" value="Reverse")
%ul
  %li <a id="server" href="/response">Call server-side Function</a>
  %li <a id="time" href="/time">Get the time</a>
  %li <a id="amazing" href="#">Toggle Title</a>

These views use haml, but should be fairly straighforward. The layout just sets up some very basic HTML5 and the index page shows the links to the routes we created above. This all work fine as it is, but every time you click on a link it causes a page reload. Ajax can help us avoid reloading the pages.

Enter Ajax

I’m going to use the RightJS JavaScript library for the Ajax functionality. This a very lightweight library that does everything in a really neat way (it’s like what Sinatra is to Ruby) and I think it goes very well with Sinatra. It is put together by the amazingly talented Nikolay Nemshilov who also happens to be a Ruby developer and is very responsive to any requests for help on the Google Groups page (he’s helped me far more times than I’d care to mention!)

RightJS also has a hotlink, so you can start using it straight away without downloading anything. Just add this link in your layout:

%script(src="http://rightjs.org/hotlink/right.js")

This will grab the latest version of RightJS. Now let’s get adding some Ajax…

First of all we need to add an empty div at the bottom of the page. This is so we have a target for the returned text to be put into. I’ve created an empty div with an id of msg at the bottom of the index page, which is trivial in Haml:

@@index
%form#reverse(action="/reverse" method="POST")
  %input#word(type="text" name="word")
  %input(type="submit" value="Reverse")
%ul
  %li <a id="server" href="/response">Call server-side Function</a>
  %li <a id="time" href="/time">Get the time</a>
  %li <a id="amazing" href="#">Toggle Title</a>
#msg

Now we need some custom JavaScript. Create an empty file called custom.js and save it in a folder called public in the same directory as your app file. This needs to be linked in the html, so add the following line to your layout (make sure it is underneath the link to RightJS:

%script(src="/custom.js")

Now add the following lines of JavaScript to the file:

"#time".onClick(function(event) {
  event.stop();
  $('msg').load("/time");
});

"#server".onClick(function(event) {
  event.stop();
  $('msg').load("/response");
});

This is a brilliant example of what RightJS calls UJS(Unobtrusive JavaScript). You basically write the CSS rule for the element in quotes(for example “#time” to access the link to the time route). You then specify an event handler (onClick in this case because we will be clicking on the link). Next you need to declare event.stop() this will stop the default behaviour of the link (ie it will stop it loading the page at ‘/time’). The next line is the Ajax magic - it finds the element with the id of ‘msg’ (our empty div) and replace the content of the div with whatever the server returns (in this case the current time). The exact same process occurs with the link with the id of ‘server’.

Now what about the form? The following Javascript will make the reversed text appear in the ‘msg’ div:

"#reverse".onSubmit(function(event) {
  event.stop();
  this.send({
    onSuccess: function() {
      $('msg').update(this.responseText);
    }
  });
});

This is very similar (the form has an id of ‘reverse’ making it easy to access). This time an onSubmit event handler is used as the form is submitted rather than clicked. The normal form behaviour is stopped and replaced with a callback function that inserts the response text from a successful post request into the ‘msg’ div.

Some Extra Treats

RightJS is really easy to use, but it also contains some really nice bonus features. For example, all you need to do is create a spinner (go to http://ajaxload.info/ for yours) and put it into the page (with an id of ‘spinner’). This will then automatically be used for all Ajax requests with just one line of configuration in the custom.js file:

Xhr.Options.spinner = 'spinner';

It’s also really easy to add some nice effects to your page. For example if I add a h2 heading and another link to my index view:

@@index
%h2 The Amazing Toggling Title
%img#spinner(src="spinner.gif")
%form#reverse(action="/reverse" method="POST")
  %input#word(type="text" name="word")
  %input(type="submit" value="Reverse")
%ul
  %li <a id="server" href="/response">Call server-side Function</a>
  %li <a id="time" href="/time">Get the time</a>
  %li <a id="amazing" href="#">Toggle Title</a>
#msg

And a tiny bit of JavaScript to my custom.js file:

"#amazing".onClick(function(event) {
  event.stop();
  $$('h2')[0].fade();
});

This will make the heading fade away on the first click and reappear on the next click. There are loads more effects you can use with RightJS, as well as some brilliant plugins. The UJS functionality that I have used a lot in this example is extra amazing because it will also work on dynamically created elements. So, say you had an application that allowed users to create new bits of content, they would inherit any of the functionality from any UJS statments.

The Code in Full

Here’s the fully working app code:

%w[rubygems sinatra haml].each{ |gem| require gem }

get('/'){ haml :index }
get('/response'){  "Hello from the server" }
get('/time'){ "The time is " + Time.now.to_s }
post('/reverse'){ params[:word].reverse }

__END__
@@layout
!!! 5
%html
  %head
    %meta(charset="utf-8")
    %title Sinatra Ajax
    :css
      #spinner{display:none;position:absolute;left:200px;top:32px;}
    %script(src="http://rightjs.org/hotlink/right.js")
    %script(src="/custom.js")
  %body
    %h1 Sinatra Ajax
    = yield

@@index
%h2 The Amazing Toggling Title
%img#spinner(src="spinner.gif")
%form#reverse(action="/reverse" method="POST")
  %input#word(type="text" name="word")
  %input(type="submit" value="Reverse")
%ul
  %li <a id="server" href="/response">Call server-side Function</a>
  %li <a id="time" href="/time">Get the time</a>
  %li <a id="amazing" href="#">Toggle Title</a>
#msg

And here’s the complete javascript file:

"#amazing".onClick(function(event) {
  event.stop();
  $$('h2')[0].fade();
});

"#time".onClick(function(event) {
  event.stop();
  $('msg').load("/time");
});

"#server".onClick(function(event) {
  event.stop();
  $('msg').load("/response");
});

"#reverse".onSubmit(function(event) {
  event.stop();
  this.send({
    onSuccess: function() {
      $('msg').update(this.responseText);
    }
  });
});
Xhr.Options.spinner = 'spinner';

I hope you’ve found this useful and that you have a bit of a play with RightJS and Ajax. Don’t forget though that Sinatra will work just as well with any of the other JavaScript libraries out there. Leave a message below about any experience you’ve had with Ajax and Sinatra.

blog comments powered by Disqus