Mottie/tablesorter

block ajax loading on page init

christhomas opened this issue · 21 comments

I would like to use ajax for pagination, but I normally render the table with the first table of results I want to show by default and then if the user clicks a button, fetch new results based on the data sent to the server in the parameters.

but the problem I have is a double load, where the server renders the table complete with page 0 (or 1, if you prefer) and then the ajax load fires and it loads the same data again.

I would like to know a way to prevent this ajax load from executing on load, cause I think maybe this is a common situation and something there is an option for, but I cannot find a way to do it apart from hacking on the code to prevent it.

Hi @christhomas!

Can you start with an empty table?

Edit: Doing this will prevent loading the same set of pages more than once. As the pager has built-in functions to prevent this from happening... at least in the latest versions.

Hi Mottie, great job on the plugin btw.

No not really, it's a bad user experience because the user expects when the page is loaded, to see the table, to see an empty table and then after a few seconds, remembering that this depends on the speed of your internet, it can easily be 5 seconds of emptyness, load the rest of the table.

On super fast data sets I can see it doesnt matter, but in our system, the data takes a few seconds to load, so it's pretty boring looking at an empty table whilst it loads.

Perhaps it's a potential enhancement, you could add a boolean which will block the ajax load on the page load, or tablesorter init.

If it possible that I could hook the tablesorter init event to set the ajaxUrl after the table has loaded, therefore it won't load from ajax before calling the event, then I'll set the url afterwards and it'll only apply afterwards when I start clicking on pager buttons?

Try this then (untested)...

$('table')
  .tablesorter({
    // make sure sortList is set up if a sort is applied
    sortList : [ [0,0 ]
  })
  .on('pagerBeforeInitialized', function(event, pager){
    var table = this,
        $table = $(this);

    pager.page = 0;            // set current page here
    pager.size = 10;           // set current size here
    pager.totalPages = 100;    // set total pages here
    pager.currentFilters = []; // set initial filters here

    $table.data('pagerLastPage', pager.page);
    $table.data('pagerLastSize', pager.size);
    pager.last = {
      page : pager.page,
      size : pager.size,
      sortList : (table.config.sortList || []).join(','),
      totalPages : pager.totalPages,
      currentFilters : pager.currentFilters
    }
  })
  .pager({
    // pager init options here
  });

Edit: updated 11/21/2013 with pager parameter

thanks for your reply, this is what I need to do in order to stop the ajax from triggering an update? or it's dong something more than that?

is there a specific part of this code which is responsible for preventing it, or it's all required? I'm just wondering why I need to do so many things.

Basically that code sets all the parameters that are checked before allowing an ajax call... it's only 5 things being set:

  • current page number
  • current page size
  • total number of pages
  • current sort order
  • current filters.

The two .data() settings ensure that the pager settings you have set aren't overridden.

You don't have to be using a default sort, or even the filter widget, it they need to be set in the pager.last variable so it looks exactly the same as the current set up... like I said, if everything matches, the ajax call won't go through.

I'm trying to look through the code for the place where it's comparing the pager.last variables in order to decide whether to call another ajax request, but I can't seem to find it, you got a line number? cause the code you specified above I think it's not working in my setup and I want to debug why

ah, no worries, I spoke too soon, I found it on line 469 "don't allow rendering multiple times on the same page/size/totalpages/filters/sorts"

what about this as a "better" solution, since it probably will react per table instead of only on the first, also, it'll copy the existing options you've already set in the pager instead of creating new values. I realise the one you did was for an example, I think I just perhaps cleaned it up a bit, what do you think?

var preventAjaxAutoload = function(event,options)
{
    var target = event.currentTarget,
        config = target.config,
        pager = config.pager;

    options.page            = pager.page;           // current page
    options.size            = pager.size;           // current size
    options.totalPages      = pager.totalPages;     // total pages
    options.currentFilters  = pager.currentFilters; // any filters

    $(target).data('pagerLastPage', options.page);
    $(target).data('pagerLastSize', options.size);

    options.last = {
        page:           options.page,
        size:           options.size,
        sortList:       (config.sortList || []).join(','),
        totalPages:     options.totalPages,
        currentFilters: options.currentFilters
    };
}

$("table).on('pagerBeforeInitialized', preventAjaxAutoload);

Well, I was just posting that code to see if it would work for you... I was actually thinking about adding an option to skip the initial ajax call.

I don't think the code you shared would work since it has this in there instead of event.currentTarget like you used for the config variable (but maybe I'm wrong, I didn't test it). Also, you should avoid adding quotes around functions as it needs to use eval() (which wastes cycles) to get the function name, so just remove the quotes around preventAjaxAutoload.

But more importantly, did it work?

oh sure, I wasn't trying to say that, sorry if I gave that impression.

oops on the this, it wasn't supposed to be there and I left it in because of an omission copying from my code into something that was generic, I've edited the comment to fix up the issues that you mentioned, the reason I had "preventAjaxLoad" instead of just dropping the name of the method directly is again because I had this code as part of a class, I was using $.proxy(this,"preventAjaxLoad") and I just deleted the code I didn't need anymore.

But also, yes, I suppose I Could use "this" too, but I didnt know what the "this" referred to at the time of writing, so I preferred to not rely on it, does it mean the jquery dom node? So I guess we could simplify it even further.

Obviously it's better if you just add an option to skip the initial ajax call, I think it would be a great option to add with a lot of reason to exist, it's something I wanted in datatables when I used that library instead, the double load is annoying.

More importantly, yes, it did work, thanks for your help!

something I realised is that when I use the above code, the filter stops working, I'm trying to find out what the reason might be, but when I disable that callback, it works again, so something in here is stopping the table from filtering

ok, the problem occurs when I set the ajaxUrl, even though your method given above blocks the initial call, it prevents the filters from executing

when I went through commenting and uncommenting the code, I found that this single line was enough to stop the filters from working.

options.pager.ajaxUrl = ajaxURL;

everything else is uncommented, I am trying to find out why this would happen, but it isn't easy since I'm not used to your codebase :)

ok, I've realised the problem, it's because setting an ajaxUrl also sets filter_serversideFiltering = true

so it's not doing any filtering in the client anymore, which is great! I was wondering how I could do that step, unfortunately, it doesn't execute any ajax call either, maybe because of the previous step to set the last step equal to the current step, so it has is dropping out and not performing the call because it thinks nothing has changed.

I'm going to look deeper into that now that I've figured this part out.

ok, I realised the problem exists when the currentFilters in the above block of code is not configured in a particular way

var preventAjaxAutoload = function(event,options)
{
    var target = event.currentTarget,
        config = target.config,
        pager = config.pager;

    options.page            = pager.page;           // current page
    options.size            = pager.size;           // current size
    options.totalPages      = pager.totalPages;     // total pages
    options.currentFilters  = pager.currentFilters; // any filters

    $(target).data('pagerLastPage', options.page);
    $(target).data('pagerLastSize', options.size);

    options.last = {
        page:           options.page,
        size:           options.size,
        sortList:       (config.sortList || []).join(','),
        totalPages:     options.totalPages,
        currentFilters: [] // <==== THIS FIXED IT, NOW THE FILTERS EXECUTE AJAX CALLS
    };
}

$("table).on('pagerBeforeInitialized', preventAjaxAutoload);

However, I'm not sure why, I am just editing your code you suggested until it works, I Don't really know the reason why

Hey, good! It sounds like you have a solution now.

Sorry I wasn't clear, yeah, I meant to say in the code above that you should set the filters on that line to match your initial table

pager.currentFilters = []; // any filters

I think probably you should update your example above, since this bug is marked as a howto, to reflect the "this" and "options" parameters that the method can accept as opposed to using external parameters like $table, the reason being is that it's self-referencing as opposed to using parameters another person in another situation might not have.

you can see what I did with mine, I did that because I had to find a way to do it without having any $table parameter and I think it worked, although when you compare mine and yours, they look quite different, I don't actually know whether I'm doing it right in my version, I just know it works :P perhaps one day I can go back over it and find out.

Ok, I've updated my original answer... just so you know, pager and options in your code are exactly the same thing.

I needed to do that earlier and here is how I did it by adding 1 line and modifying another one in jquery.tablesorter.pager.js :

 14       // target the pager markup
 15       container: null,
 16 
+ 17       // set this to false if you want to block ajax loading on init
+ 18       processAjaxOnInit: true,
...
295             // add rows to first tbody
-  296             c.$tbodies.eq(0).html( tds );
+ 296             p.processAjaxOnInit ? c.$tbodies.eq(0).html( tds ) : p.processAjaxOnInit = true;

Then you just have to add this in the pager options if you want to block ajax on init.

processAjaxOnInit: false

This will in fact process the first AJAX call in order to retrieve total number of rows (to update the display accordingly) but will prevent Tablesorter to actually update the rows, which could also help you with issue #426 (if I read that write).

So it's up to you to set the sortorder/filters of the first ajax call if this needs to be done :)

Lyn.

Edit : As always, my english isn't perfect, feel free to ask anything that could be misunderstood.

Thanks for sharing the code Lynesth! I'll add it in the next update.

But, that solution will still make that extra call to the server... I guess it would be up to you to on what to return initially. Maybe the ajaxUrl can be modified by using the pagerInitialized event?

$(function(){
  $("table")
    .tablesorter()
    .tablesorterPager({
      ajaxUrl: "http://mydatabase.com?init=true", // just return total pages
      ajaxProcessing: function(ajax){
        // do something with the ajax
        return [ formatted_data, total_rows ];
      }
    })
    .on('pagerInitialized', function(event, opt){
      // opt = config (pager widget)
      // opt = config.pager (pager addon)
      opt.ajaxUrl: "http://mydatabase.com?page={page}&size={size}&{sortList:col}&{filterList:fcol}";
    });
});

P.S. @Lynesth... just to correct a minor English mistake, it's "if I read that right" ;)

Hey @Mottie !

Yeah, this would still make a call to the server. But this is needed in my opinion.

If you want to return just the total numbers of rows (and avoid returning a lot more data on the first call) you can first set a custom ajaxUrl that will do it and then change it on pagerInitialized event just like you proposed. I think that would be the way to go.

Oh and that was just a typing mistake... "write" and "right" kind of have the same pronounciation, so my brain mixed that up :p

ah, sorry guys, I'm late to this party, but that solution does look pretty good so far.