July 18, 2011

Django AJAX Tutorial Part 3 - ajax pagination #2

Hello. After long time not seen I'm back to finish the topic of pagination in django with AJAX and after few posts also django topic as a whole. The reason is simple - I've switched totally to javascript and ExtJs framework. So let's get started !

Ok, so we'll be using the same 'Friend' model, so I'll skip the code here. Next go the urls :

First address is for the main page of our sample project. We won't be using it anywhere in the code. Next is the address of view which generates the friends list. The index view as well as view for changing page will be sending requests there. Finally the last link is for the change page function. For sake of simplicity we'll be using named urls here.
Next - the views, and we'll start from the index view :

per_page = 4

def index(request):
    template_name = 'pagination/index2.html'
    html,pagination = load_friends_list(request)

    return render_to_response(template_name, {"html": html, 'pagination': pagination}, context_instance=RequestContext(request))

The per_page variable remains the same as previously. In the function itself not much to say about. We assign result of calling load_friends_list function to two variables and then include them in the rendered template.
Can we proceed to the next view ? If yes - the friends list generator comes into scene.

def load_friends_list(request):
    objects = Friend.objects.all()
    template_name = 'pagination/friends2.html'
    paginator = Paginator(objects, per_page)
    p = request.session.get("pagination_page", request.REQUEST.get("page", 1))

    pages = paginator.num_pages
    try:
        page = paginator.page(p)
    except EmptyPage:
        page = 0

    friends = render_to_string(template_name, RequestContext(request, {
        "page" : page,
    }))
    
    pagination = render_to_string('pagination/pagination.html', RequestContext(request, {
        "page" : page,
        "pages": pages        
    }))

    return (friends, pagination)

Now let's go through this code above. First two lines of the function don't need explenations I guess. Then we're creating the Paginator object, calling it's constructor with two arguments - our objects list that we want to paginate and the amount of items per page.
The next line is responsible for getting the current page number. As you'll see in a minute, the change page function can set a session variable for storing the current page value. If that didn't happen we're doing a lookup through REQUEST object ( which is added to each request ) for 'page' variable. And if it's not present - we're picking the first page.
Proceeding further we're storing total number of pages using the built-in Paginator function 'num_pages'(here's the link to docs concerning this topic ). Next we're doing a try-catch check to finally get our paginated objects. By calling the 'page' function of Paginator we're receiving the objects list from the desired page. If this page does not exist we're setting value 0.
Finally we need to render all those things to be used in templates. First goes paginated friends
list object. Next we're rendering pagination sending page object as well and amount of pages (which is not really needed but simplifies the template).
We could have had returned here friends list together with pagination in one template, but I didn't wanted to change the page structure.

Finally the last view, for setting the current page.

def set_friends_page(request):    
    request.session["pagination_page"] = request.GET.get("page", 1)    
    html = load_friends_list(request)[0]
    result = simplejson.dumps({"html" : html}, cls = LazyEncoder)
    return HttpResponse(result)

This view starts with getting the 'page' number from request and storing it in a session variable. If 'page' is not present in the request, we're setting it to the first page. Then we're using previously described function load_friends_list to generate list of paginated objects. As you can see we pick only the first argument from the returned value which is the friends list itself. Finally we can build the result response ( we're using the LazyEncoder described in the first tutorial concerning Ajax) and retunrn it via HttpResponse.

If we already have that we can get to the last part - the templates. First the templates for friends and pagination, as not much is going on there :

{% if page.objects_list %}
 <ul class="friends-list">
  {% for f in page.object_list  %}
   <li>
    <div class="friend-avatar">
     <img src="http://a2.twimg.com/profile_images/1116899230/mb_normal.png" />
    </div>
    <div class="friend-name">
     <p>{{ f.name }}</p>
    </div>
   </li>
  {% endfor %}
 </ul>
{% else %}
 <p>You have no friends.</p>
{% endif %}

'friends' template have basically the same structure as in the previous tutorial. We've just changed the source of paginated objects to objects_list parameter of the page objects (which was generated by Paginator as you already know) and moved here the test for friends existence

Now the pagination template :

<ul id="pagination">
    {% if pages %}
        {% ifequal pages 1 %}
            <li id="{{pages}}">{{pages}}</li>
        {% else %}
            {% for i in page.paginator.page_range %}
                <li id="{{i}}">{{i}}</li>
            {% endfor %}                        
        {% endifequal %}
    {% endif %}
</ul>

First we're checking if pagination will be present at all (if there were no objects to paginate, the amount of pages would be 0 and so there would be no pagination. We could have used page.paginator.num_pages here but it needs much more chars to describe as well as requires a lookup in the page object. So why bother ? :) )
If the amount of pages is one, we just insert one list element. If the amount is greater than one we'll iterate through page.paginator.page_range parameter which has the form of an array (ie. [1,2,3]). And that's all for the pagination. Finally we can get to the index tpl.

<div style=\"margin-top:15px; border-top:1px solid #E1E6EC\">
        <div class=\"friendsList\">
            <div id=\"loading\">
            </div>
            <h2 class=\"div-title\">
                Friends
            </h2>
            <div class=\"div-content\">
                <div id=\"friends-data\">                     
                    {{ html|safe }}
                </div>           
            </div>
            {{ pagination|safe }}
        </div>
    </div>   
<script type=\"text/javascript\">
</script>

First of all I've cut the script out as I want to describe it separately. As for the template itself basically nothing to write here about. Sctructure is mainly the same as previously. We have just inserted our two generated variables : the friends list and pagination. We're using 'safe' filter on them so that html tags won't be escaped. The last code belongs to the javascript required for handling ajax requests.

$(function(){  
 function showLoader(){
     $("#loading").fadeIn(500,0);
  $("#loading").html("");
 }

 function hideLoader(){
  $("#loading").fadeOut('slow');
 };
 
 $("#pagination li:first").addClass('selected');

 $("#pagination li").click(function(){   
  showLoader(); 
  $("#pagination li").removeClass();
  $(this).addClass('selected');
  var pageNum = this.id;

        $.ajax({
            type: "GET",
            url: "{% url set_friends_page %}",
            data: "page="+pageNum,
            async: false,
            dataType: 'json',
            success: function(data){
                el = $('#friends-data');
                $("#friends-data").html(data.html);
                hideLoader();
            }
        });
        return false;        
 });
});

As previously we're using jQuery for the sake of simplicity. Also first two functions are exactly the same - they either hide or show the loader div by changing it's display type. Next line sets the class of first pagination element to 'selected'. Finally the most important function - click event handler for pagination
list elements. So each time user clicks <li> element we call this function. First we show the loader, and remove class from all pagination list <li> elements. Next we add class 'selected' to the currently clicked element by using this variable. Before performing ajax request there's just one last thing to do which is storing the id of clicked element to pageNum variable.
Now the fun part - we will do a proper ajax request using jQuery's built in 'ajax' function. In a nutshell it all just comes down to doing a request at our change page view url and then handle the returned data.
Before doing a request we must set some initial parameters which are: the type of request ('GET', 'POST' etc.) url of request (which in this example is our set_friends_page view url), data that we will send (which is the 'page' variable that we're processing later in our views), asynchronousity (if the request blocks the UI or not. Here we do not need the request to be asynchronous as there's not much for the user to do during page load), type of returned data and callback function called when ajax calls ends with success.
The last parameter is the one responsible for performing the html update operation. As a result of our ajax request we're receiving json (of the following form {"html": "<ul>....</ul>"} ) object containing one parameter : html. This data.html contains the rendered list of friends for proper page number. So we select friends-data div from the DOM and in the next step insert the returned html inside this div with jquery's 'html' function. After that we hide the loader div once again.

So basically that's all for this episode of 'Ajax with Django' tutorial. Hope you liked it and if something is not clear or if you have some other questions, just leave a comment or find me on twitter. I'm aware of the fact, that this code has some bugs like the fact that stored session variable is not being reseted each time we're refreshing the example page which may lead that we have different than first page loaded but first page selected in the navigation but really I've tried to keep it simple.
I've created a demo so you can check how it works :
http://samples.fromzerotocodehero.x14.eu/pagination2/start
, as well as prepared the code for download:
http://ofcjs.x14.eu/pagination2.rar
I will try to put next tutorials in the next few days to finish the series. So stay tooned.

3 comments:

  1. very good tutorial and can hopefully help me in building json in the application that I created for this lecture. thank you

    ReplyDelete
  2. Good Document helped lot :)

    ReplyDelete

stat4u