Ajax Pagination

Posted by Janos on January 16, 2010

It’s often necessary to paginate in Ajax applications. For example, let’s say we have a tool for editors that lets them search for videos. The search is performed in one area and in an other area the user selects the page he wants to populate. Then he can just drag the chosen videos over the selected page. Full-page reload on pagination would be a problem, because the user would have to find the page he wants to edit again. We need to to able to paginate within the search results while leaving the rest of the page intact.

I did not want to implement pagination on the back end myself, so I took advantage of the mislaw-will_paginate gem.
The first task was to capture the pagination links. We render the links into a local variable like this:

@videos=Video.paginate :page=>params[:page]
pagination=ActionView::InlineTemplate.new("<%= will_paginate @videos %>", nil).render(@template, {})

The code to query the database would typically be more complicated, there would be chained named scopes, for example, and the call to ‘paginate’ would come last:

@videos=Video.most_popular.paginate :page=>params[:page]

We use JSON to communicate between the browser and the server. The video results are in an array generated by ActiveRecord. Let’s add the pagination string in the first position in the array:

@videos.unshift pagination # let's stick the pagination information in the first position
render({ :json => @videos.to_json(..) })

This does not disrupt the json encoding, the links stay a string while the rest of the array’s members get transformed according to the rules given in the to_json method.

On the JavaScript side, assuming that I have a div somewhere that looks like this:

<div id="pagination">
</div>

I can take resolve the pagination information and stick it to the div:

function onSearchResultsReady(results) {
  var pagination=results.shift();
  //processing here
  // ..
  $('#pagination').append(pagination);
  ajaxifyLinks();
}

The final step is to prevent the browser from reloading the page so that it instead makes an Ajax call when the user clicks on a page link. Notice the call to ajaxifyLinks. That function overrides the links’ default behavior:

function ajaxifyLinks() {
    $('#pagination a').click (function(){
        $.getJSON(this.href,
          {},
          onSearchResultsReady);
        return false; // don't follow the link
    });
  }

With minimal amount of work, we were able to make the gem do all the heavy-lifting, and even got our ajaxified pagination as well:

video_editor

Autotest

Posted by Janos on January 11, 2010

Autotest is a convenient way to keep your tests passing without having to continuously issue the rake rspec commands.
To use autotest, create a Rails project if you have not done so already:

rails huhu

cd into the new directory and enable RSpec:

./script/generate rspec

Now generate two RSpec-enabled models:

./script/generate rspec_model book title:string
./script/generate rspec_model author full_name:string

Notice how now you have two tests in spec/models.
Before running the tests, make sure your test database is ready:

rake db:migrate RAILS_ENV=test

Now from the root directory of the Rails application, execute:

./script/autospec

You should see all tests passing. Observe that the script does not exit.
Open a second shell and add a test like the following to spec/models/author_spec.rb:

it "should be stuff" do
    false.should== true
  end

Obviously, you would write a meaningful test but it illustrates the point. As soon as you save the file, you will see autotest execute in the first shell window. Here is the relevant output:

'Author should be stuff' FAILED
expected: true,
     got: false (using ==)
./spec/models/author_spec.rb:15:

Finished in 0.087639 seconds

2 examples, 1 failure

Now correct the test so that the offending line reads:

false.should==false

You’ll see how all the tests are rerun and there are no failures.
Edit the other test file, book_spec.rb, and add a passing test. You will see autotest execute only the tests in book_spec.rb.
However, if you add a failing test to book_spec.rb, then you correct it, you will see that this time autotest first executes the tests in book_spec.rb to check whether they all pass, then it goes on to execute all tests in all spec files.

It takes a little time to get used to the work style autotest offers, but it’s well worth the peace of mind to know that all your tests are passing.