Saturday, 2 April 2011

Implement Ajax actions using page slices [Part 2]

Last time I discussed how to implement Ajax browsing using page slices] generated
at server side. If you haven't read that yet, I strongly encourage you to do so, cause there's little chance the rest of this post will make sense to you.

Read Part 1- Implement Ajax browsing using page slices.

This time we're going to apply the same technique, but we'll implement some actions. By actions I mean simple interactions for instance 'vote a comment up', or 'follow a member', 'delete an item' etc.. Any kind of interaction that does not require more than a click from the user in an Ajax interface.

For instance, on libsquare.net, if you are logged in, you can vote up or down any review that appear on the jQuery page:

So how do we implement that?

The pure HTML flow

First let's remember that page slicing is designed to allow us to implement things without JavaScript, so let's just do that. Here's the work flow of the vote up feature without any JavaScript:

The user clicks on vote up, he arrives on a page where the vote up is pre-selected, clicks submit and is redirected to the page review with the vote count updated.

Let's Ajax this!

First let's give a class 'do_vote_up' to our vote up link:

<a href=".../review/111/vote_up" class="do_vote_up">Vote up</a>

Then we need to give an ID and slice the container that contains the voting unit:

<div class="slice_container" id="review_vote_111">
<& /slice.mas , id => 'review_vote_111' &>
 ... Your vote display html code ...
<a href=".../review/111/vote_up" class="do_vote_up">Vote up</a>
 ...
</&>
</div>

Let's assume /review/111 is a page that always display the review with it's voting part.

Now we're only a small bit of Javascript away from the full Ajaxed feature:

$('a.do_vote_up').live('click', function(event){
  event.preventDefault();
  var a = $(event.target);
  var container = a.closest('.slice_container');
  var target_url = a.attr('href');
  var result_url = target_url; result_url.replace('\/vote_up','');
  // Post a vote up query to the voting URL.
  $.ajax({
    type: 'POST',
    url: target_url,
    data: { vote: 1 , vote_submit : 1 },
    success: function(){
       // On success, reload the voting slice that now contains the updated vote.
       container.load(result_url,{ page_slice: container.attr('id') });
    }
  });
});

Et voila, the vote up action is now Ajaxed. Implementing the same thing for the vote down is left as an exercise for the reader :P

Next time I'll be speaking about submitting forms and dealing with redirections in Ajax. Stay tuned and thanks for reading!

Monday, 21 February 2011

Slice your HTML to Ajax anything. [Part 1]

There's roughly two ways of addressing Ajax on your website:
  • Designing your pages with Ajax in mind. This usually means you'll have a  growing number of features that require JavaScript to work. The W3C recommendation specifies that you should provide JavaScript-less versions of your features. So it means you will have to develop your features twice to maintain accessibility. Sounds a painful thing to do.
  • Designing your pages without Ajax in mind. This means you design only good old pure html/params & forms techniques to animate your features. This is good for accessibility, this is good for testability, this is good for search engines because all your features are accessible via plain links. The only problem is... well there's no Ajax coolness.
But we're going to fix that, and it's not going to be painful at all providing you:
  • have got zero business logic in your rendering layer. Meaning each part of it is idempotent. This is hopefully the case when you use a MVC model for your application.
  • use a rendering layer that's capable of manipulating its buffer, and renders the whole page in this buffer before sending it.
For this post, I'll be using Catalyst as the MVC and HTML::Mason as the View layer. We'll focus on implementing Ajax results paging. (Other ajax aspects will be dealt with in later posts).


Principle of ajax page slices.

The idea of page slices came to me when I was looking at jQuery's load function. It's got a very convenient trick called 'load page fragments' that allows you to do stuff like:

$('#container').load('page_url?offset=2 #result_id');

It will look for an element having the id 'result_id' in the returned document and replace the content of #container with it. Here's a full example of html/javascript that implements this:

<div class="container">
 <div class="result" id="result_id">
   <p>This is page 1</p>
   <p>item 1</p>
   <p>item 2</p>
   <a class="page" href="page_url?page=2">Go to page 2</a>
 </div>
</div>
<script>
 $(document).ready(function(){
    $('a.page').live('click',function(e){
        var a = $(e.target);
        var result = a.closest('.result');
        var container = result.closest('.container');
        container.load(a.attr('href') + ' #' + result.attr('id'));
    });
 });
</script>

This does the trick on the client size but it doesn't save much time on the server side.
Plus it requires DOM parsing of the whole document to find the result_id element. Altogether, chances are that your ajax paging will be slower that a simple whole page reload. To fix that, we're going make the server output only the required slice for us.

Let the server do the work.

First let's have a look first at the intended client side code:

<div class="container" id="result_id">
<!-- This is the slice 'result_id' -->
  <p>This is page 1</p>
  <p>item 1</p>
  <p>item 2</p>
  <a class="page" href="page_url?page=2">Go to page 2</a>
<!-- End of slice -->
</div>
<script>
 $(document).ready(function(){
    $('a.page').live('click',function(e){
        var a = $(e.target);
        var container = result.closest('.container');
        container.load(a.attr('href'), { page_slice: container.attr('id') });
    });
 });
</script>

On the server side, we need a technique to output only the required slice, discarding any previously generated HTML, and aborting the rendering so no more subsequent HMTL is rendered. In Mason, this is easily done by implementing a content wrapping component.
Here's how it looks in under Catalyst (the $c bit):

Component page_slice.mas:
<%args>
 $slice_id 
</%args>
<%init>
  my $requested_slice = $c->req->param('page_slice');
  if( ( $requested_slice // '' ) eq $slice_id ){
    # Discard any previously generated content,
    # output the content and finish rendering
    $m->clear_buffer();
    $m->out_method->($m->content);
    $m->abort();
 }else{
    # This component is transparent
    $m->out_method->($m->content);
 }
</%init>

Here's how to use this component in your page.mas:

% deal with param('page') to compute results and next page.
<div class="container" id="result_id">
 <&| /page_slice.mas , slice_id => 'result_id' &>
  <!-- This is the slice 'result_id' -->
% # Output the right results
% ...
  <a class="page" href="page_url?page=<% $next_page %>">Go to page <% $next_page %></a>
  <!-- End of slice -->
 </&>
</div>
<script>
 $(document).ready(function(){
    $('a.page').live('click',function(e){
        var a = $(e.target);
        var container = result.closest('.container');
        // Use the container ID as the slice ID.
        container.load(a.attr('href'), { page_slice: container.attr('id') });
    });
 });
</script>


When clicking a '.page' link, a request will be sent with the param 'page_slice'. Thanks to this, the component page_slice.mas will output only the requested page slice, and this will populate the container.

Your paging will obviously works without JavaScript. There's no Ajax specific code at server side. This is:
  • Good for accessibility. Try using your paging with lynx. It just works.
  • Good for SEO. Our beloved search engines will crawl these links, even if they don't do Ajax.
  • Good for testing. Because Ajax is not needed to use your application, you can easily unit test your features from your web test suite.
  • Good for reliability. If for some reason your JavaScript is broken, the feature will still be available.
Of course this technique can be applied to any kind of browsing features: sorting, faceting etc..
Next time we'll discuss performing an action in Ajax, and managing Ajax redirections.

I hope you enjoyed this post, thanks for reading!

Avoiding encoding headache

David Wheeler recently posted about encoding headaches. I'm not going to copy/paste my reply here, but the crux of it is:

Encoding is for i/o; in the Perl space, text must be Perl character strings.

I repeat:

Encoding is for i/o; in the Perl space, text must be Perl character strings.

Write that 100 times, display it next to your monitor(s), record it and play it whilst sleeping on your ipod for one week, have it tattooed on your fingers, and in no time you'll be known as the encoding guru in your company.

Monday, 7 February 2011

Some say MySQL does not have any sequences

Not true, it's got only one, and it's per session:

select last_insert_id(last_insert_id() + 1);


To be reset like that:

select last_insert_id(0);