views:

6163

answers:

8

I'm trying to show a small loading image during a slow operation with jQuery and can't get it right. It's a BIG table with thousands of rows. When I check the "mostrarArticulosDeReferencia" checkbox it removes the "hidden" class from these rows. This operation takes a couple of seconds and I want to give some feedback. "loading" is a div with a small animated gif

Here's the full code

jQuery(document).ready(function() {
jQuery("#mostrarArticulosDeReferencia").click(function(event){
    if( jQuery("#mostrarArticulosDeReferencia").attr("checked") ) {
        jQuery("#loading").show(); //not showing
        jQuery("#listadoArticulos tr.r").removeClass("hidden"); //slow operation
        jQuery("#loading").hide();
    } else {
        jQuery("#loading").show();  //not showing
        jQuery("#listadoArticulos tr.r").addClass("hidden");  //slow operation
        jQuery("#loading").hide();
    }
});
jQuery("#loading").hide();
});

It looks like jquery is "optimizing" those 3 lines

        jQuery("#loading").show(); //not showing
        jQuery("#listadoArticulos tr.r").removeClass("hidden");
        jQuery("#loading").hide();

And never shows the loading div. Any Ideas?

Bonus: There is a faster way of doing this show/hide thing? Found out that toggle is WAY slower.

UPDATE: I tried this

    jQuery("#mostrarArticulosDeReferencia").click(function(event){
    if( jQuery("#mostrarArticulosDeReferencia").attr("checked") ) {
            jQuery("#loading").show(); //not showing
            jQuery("#listadoArticulos tr.r").removeClass("hidden"); //slow operation
            setTimeout("jQuery('#loading').hide()", 1000);
    } else {
            jQuery("#loading").show();  //not showing
            jQuery("#listadoArticulos tr.r").addClass("hidden");  //slow operation
            setTimeout("jQuery('#loading').hide()", 1000);
    }
});

That's what I get

  1. click on checkbox
  2. nothing happens during 2/3 secs (processing)
  3. page gets updated
  4. loading div shows up during a split second

UPDATE 2: I've got a working solution. But WHY I have to use setTimeout to make it work is beyond me...

    jQuery("#mostrarArticulosDeReferencia").click(function(event){
    if( jQuery("#mostrarArticulosDeReferencia").attr("checked") ) {
            jQuery("#loading").show();
            setTimeout("jQuery('#listadoArticulos tr.r').removeClass('hidden');", 1);
            setTimeout("jQuery('#loading').hide()", 1);
    } else {
            jQuery("#loading").show();
            setTimeout("jQuery('#listadoArticulos tr.r').addClass('hidden');", 1);
            setTimeout("jQuery('#loading').hide()", 1);
    }
});

UPDATE 3: Just found a better solution for this particular case.

//mostrar u ocultar articulos de referencia
$("#mostrarArticulosDeReferencia").click(function(event){
    if( $("#mostrarArticulosDeReferencia").attr("checked") )
        $("tr.r").css({'display':'table-row'});
    else
        $("tr.r").css({'display':'none'});
});

Using .css({'display':'none'}) turns out to be WAY faster than hide(), so no need for the loading animation...
This article showed me the light: show/hide performance.

A: 

Have you tried using ajaxStart to run your update function?

Jason
How would you use it? can't get it to work
The Disintegrator
check this question: http://stackoverflow.com/questions/1191485/how-to-call-ajaxstart-on-specific-ajax-calls
Jason
A: 

I think it is working correctly. This operation:

jQuery("#listadoArticulos tr.r").removeClass("hidden");

isn't really that slow and it's performed in few ms tops, so you don't see the loading information because it's just there for a very short time.

RaYell
I have THOUSAND of rows, it takes 2 or 3 second. And it's very annoying
The Disintegrator
What if you comment out hide and removeclass instructions, do you see loading div then?
RaYell
Something like that you say? jQuery("#mostrarArticulosDeReferencia").click(function(event){ if( jQuery("#mostrarArticulosDeReferencia").attr("checked") ) { jQuery("#loading").show(); //not showing //jQuery("#listadoArticulos tr.r").removeClass("hidden"); //slow operation //jQuery("#loading").hide(); } else { jQuery("#loading").show(); //not showing //jQuery("#listadoArticulos tr.r").addClass("hidden"); //slow operation //jQuery("#loading").hide(); } });yes, I see the loading div, but I nerd to hide it afterwards...
The Disintegrator
Mental note: don't try to paste code in comments.
The Disintegrator
A: 

I think you want to be doing

jQuery("#listadoArticulos tr.r").removeClass("hidden");

inside a callback function for show.

Look at the method documentation here: http://docs.jquery.com/Effects/show

You can achieve the same "instant showing" effect by calling show(0);

But you want to do the callback? No problem, do this:

show(0, function() { jQuery("#listadoArticulos tr.r").removeClass("hidden"); });

Simple! :)

Will Morgan
Doing this I got the same behavior as before 1. click on checkbox 2. nothing happens during 2/3 secs (processing) 3. page gets updated 4. loading div shows up during a split secondAND I have to use settimeout to hide the div. If I dont use it, the div never shows...
The Disintegrator
Do you have a live example I could play with? I'm thinking instead of changing each individual row, if you've got an entire set, you could change the tbody tag instead.
Will Morgan
A: 

I have seen this behavior as well and it depends on the browser used. IE is the one most affected. The reason is not that jQuery is slow, although the JavaScript runs slower in IE, but more due to the rendering of the new HTML. The reason setTimeout() helps is because it does the first action and only when the browser is done rendering the new HTML the second part is done. The whole operation is actually slower but the feedback makes it nicer.

One way to demonstrate this is by hiding the long table and running the same code. It will do the same but as the table is invisible and doesn't render the whole action is much faster.

There are a couple of ways to get around this.

  1. Try to avoid tables and go for a DIV layout instead. Tables render slow and depending on the browser the table might only become visible then completely rendered.
  2. If you must use table consider using multiple tables of say 100 rows stuck together. That way the page starts rendering as soon as the first table has been reformatted.
  3. If one large table is the only way to go and you know the user can see only the top rows you can do the action twice, the first time on the top rows and the second time on all the rows. This is the slowest and least optimal solution but can be very easy to implement by adding a jQuery filter to the action.
Maurice
Why are you recommending divs for tabular data? Why are you recommending breaking up the data? Do you care about semantics?
Will Morgan
OR accessibility for that matter?
Will Morgan
Mainly because of the rendering speed of a table in the browser. Using divs and css is a good alternative. Of course there is the option to use a fixed table-layout which will help as well. See: http://stackoverflow.com/questions/1046035/why-is-ie7-so-slow-compared-to-safari.
Maurice
This isn't for layout. This is a table for tabular data.Can't use fixed width because I try to use all the available space, and if I split the table, the columns will get different widths.Using Firefox
The Disintegrator
+2  A: 

I tried everything and I have to conclude than this is the product of a browser brainfart.

  1. jQuery tells the browser to show/hide the loading div
  2. jQuery tells the browser to add/remove the hidden class from the rows
  3. jQuery tells the browser to show/hide the loading div
  4. The browser does all the above steps in the wrong order (2 in first place)

'setTimeout' in my last update is forcing the browser tho execute all the steps in the correct order. It sucks, isn't elegant, but at least it works...

The Disintegrator
A: 

This doesn't answer the original question, but is it any faster to use a CSS rule for a class on the table itself:

#listadoArticulos.hideCertainRows tr.r { display: none }

and toggle the display like this:

jQuery("#listadoArticulos").addClass("hideCertainRows");

?

Michael B
A: 

Apparently you're firing an asynchronous request (yes, asynchronous, in other words, it doesn't run in sync with the code!) during the "long running process". You need to call $('#loading').hide() at the end of the callback function of the asynchronous request.

Here's an excellent example:

$('#loading').show();
$.getJSON('someURL', function(data) {
    // Do slow processing.
});
$('#loading').hide();

This is obviously not going to work. It will show, fire an asynchronous request and then immediately hide. You need to rewrite the code as:

$('#loading').show();
$.getJSON('someURL', function(data) {
    // Do slow processing.
    $('#loading').hide();
});
BalusC
A: 

Just a note - I've developed a plugin for creating an overlay div with a loading graphic in it:

http://www.contextllc.com/blog/2010/06/jquery-showloading-plugin-show-loading-graphic-over-specific-element/

Jim