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.

The Disintegrator
  • 4,147
  • 9
  • 35
  • 43

10 Answers10

3

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...

Update: the setTimeout is necessary because jquery "simplifies" the chain of commands. If I tell it to hide one element and the show it again, it will simply ignore the command because the final result will be the same as do nothing...

The Disintegrator
  • 4,147
  • 9
  • 35
  • 43
  • I think I added a comment saying that this answer was misleading the correct answer, and that there is not brainfart from part of the browser, is from jquery library and its absense of calback for the removeClass callback (which it seems natural). I don't know why it was removed. – Jorge E. Cardona Feb 04 '13 at 07:50
3

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
  • 1,082,665
  • 372
  • 3,610
  • 3,555
1

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
  • 4,470
  • 5
  • 29
  • 42
  • 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 second AND I have to use settimeout to hide the div. If I dont use it, the div never shows... – The Disintegrator Aug 10 '09 at 08:57
  • 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 Aug 10 '09 at 09:48
1

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

Code Lღver
  • 15,573
  • 16
  • 56
  • 75
Jim
  • 11
  • 1
1

Put a div on your page somewhere like so:

<div id="spinner" class="spinner" style="display:none;">
    <img id="img-spinner" src="@Url.Content("~/images/loading.gif")" alt="Loading"/>
</div>

Then hook the show and hide to ajaxStart, ajaxStop, and ajaxError events like so:

$("#spinner").bind("ajaxSend", function() { $(this).show(); })
$("#spinner").bind("ajaxStop", function() { $(this).hide(); })
$("#spinner").bind("ajaxError", function() { $(this).hide(); });
Brian
  • 11
  • 4
0

Have you tried using ajaxStart to run your update function?

Jason
  • 51,583
  • 38
  • 133
  • 185
  • check this question: http://stackoverflow.com/questions/1191485/how-to-call-ajaxstart-on-specific-ajax-calls – Jason Aug 10 '09 at 07:28
0

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
  • 69,610
  • 20
  • 126
  • 152
  • I have THOUSAND of rows, it takes 2 or 3 second. And it's very annoying – The Disintegrator Aug 10 '09 at 06:50
  • What if you comment out hide and removeclass instructions, do you see loading div then? – RaYell Aug 10 '09 at 07:21
  • 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 Aug 10 '09 at 07:29
0

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
  • 27,582
  • 5
  • 49
  • 62
  • 1
    Why are you recommending divs for tabular data? Why are you recommending breaking up the data? Do you care about semantics? – Will Morgan Aug 10 '09 at 08:25
  • 1
    OR accessibility for that matter? – Will Morgan Aug 10 '09 at 08:26
  • 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 Aug 10 '09 at 08:35
  • 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 Aug 10 '09 at 09:02
0

Try to use the callback "complete" of the show method, js is mostly based in async events, and this three lines:

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

are going to be executed without waiting, so, your showing, adding class and hiding immediately the div, that's why you can't see the div, you're hiding it without wait to be shown and it appears and disappear really fast, or even it may never show up (don't know about any optimization of that type).

If you add the two last inside the complete callbacks it will work, more http://api.jquery.com/show/:

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

In any case, also, the correct way to do this for ajax request (I imagine is going to be useful for someone) is using ajaxStart and ajaxStop like this:

$(document).ajaxStart(function() {
   $( "#loading" ).show();
}).ajaxStop(function() {
  $( "#loading" ).hide();
});

Addition for removeClass absence of callback:

Maybe your problem lay on the fact that there is no callback for removeClass, but you can do the next:

jQuery("#loading").show(400, function (){
    var els = jQuery("#listadoArticulos tr.r");
    els.each(function(i, el) {
        el.removeClass("hidden");
        if (els.length == i +1) jQuery("#loading").hide();
    });
});

try this too:

var els = jQuery("#listadoArticulos tr.r");
els.each(function(i, el) {
  if (i == 0) jQuery("#loading").show();
  el.removeClass("hidden");
  if (els.length == i +1) jQuery("#loading").hide();
});

You may add the problem that if you have images or any extra code that activates once you remove the class, there is no native way to wait for them to hide the loading div, so maybe even if you remove the class hidden there is no easy way to wait for the objects to be visible.

I just notice that this was 2 years ago, maybe you don't need this anymore.

Jorge E. Cardona
  • 92,161
  • 3
  • 37
  • 44
0

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
  • 106
  • 4