EDIT: The question morphed into how to optimize a massive CSS change to 700 or more divs. I'm leaving the old question below to describe my original approach.
I have the following jQuery but it is not behaving as I expect. There are around 700 divs with class gr so hiding them takes a noticeable time. I am trying to do:
"Working" goes back to "Show/hide Pronunciation"
$(document).ready(function () {
$('#togglePron').click(function() {
$('#togglePron').html("Working...");
$('div.gr').toggle();
$('#togglePron').html("Show/hide Pronunciation");
});
});
...
...
<div class="pronlink" id="togglePron">Show/hide P</div>
<div class="gr">hai</div><div class="zi">A</div>
<div class="gr">nao</div><div class="zi">B</div>
etc.
Thanks to Mike Lentini there's a jsfiddle for this question.
This is the full page I'm working on
The behavior I observe is that "Show/hide P" takes a noticeable time to change, then it changes briefly to "Working", and it goes back to "Show/hide". So is jQuery bunching together both the html() and .toggle(), instead of running html() first?
This seems to be browser specific because in Opera it does what I want. In IE 7 and Chrome 18, the behavior is as I described. Is there a way to make the behavior I want in Chrome happen? Or a better way to do what I am describing?
div
s "#toggleFormBar" and "#togglePron" to a
s instead. Shouldn't affect your code at all, but generally best practice is for clickables to be anchors - DACrosby 2012-04-12 05:39
One way would be to dynamically modify your css styles directly (not per element) using javascript.
In your page, all of the text divs in each <td>
have a class='gr'
in them WITH an inline style="display:block;"
. First I'd move the display:block;
into the style sheet (preferably into the first position).
Using this SO answer (and this page, also this SO answer), I'd go through each of the stylesheets until you got the one your interested in (adding a title to the <css />
will allow you to easily do this). Remove the display block (which is a simple stylesheet.deleteRule(<index>)
index will be zero because you put it first in .css). Add a new display:none;
.
This will essentially change the whole css for the page vs changing them individually PER element as you're forced to do with JQuery.
Maybe not the most popular solution but still allows you to keep your current design (not duplicating the information) and uses css for what its suppose to do (tho 'dynamic').
Follow up: Nice find with the "Totally Pwn CSS with Javascript" . Does exactly what I would do in your situation (wanted to add it here for posterity :) )
On a side note, someone should make a feature request to JQuery to be able to do something like this with ease. :)
Follow Up 2: just realized someone else posted solution this before me
Another way you can do this is to remove the 'gr' class and add another 'grHide' class. Check out this fiddle to see what I mean.
Do as I said above, move the inline display:block;
into the css stylesheet under the .gr class. Add a separate class that does everything .gr does but do a display:none;
instead.
This should allow you to use jquery AND still have it be very quick. The fiddle is quick anyways.
EDIT :
Rather than using the toggle function to bind an event, why not just swap in a class on all the elements? This should be much faster:
$(document).ready(function(){
$('#toggleP').click(function(){
$('.gr').toggleClass('hidden');
});
});
Then in your CSS, add this style:
.hidden { display: none !important; }
See this fiddle for an example.
ADDITIONAL EDIT :
As noted by kingjeffrey, class-swapping the parent element instead of the child elements is even more efficient:
JS:
$('#togglePron').click(function(){
$('table').toggleClass('hide-pron');
});
CSS:
table.hide-pron .gr { display: none !important; }
For a (relatively) small number of children, the difference between these approaches is negligible – but as the child count increases, swapping the parent class can be noticeably faster.
ORIGINAL ANSWER :
Your element isn't going to update until the click()
function returns. Here's one way (probably not the best way) to do it:
$(document).ready(function () {
$('#toggleP').click(function() {
$('#toggleP').html("Working...");
setTimeout(
"$('div.g').toggle(10,function(){$('#toggleP').html('Show/Hide P');});",
10)
});
});
The toggle()
is now asynchronous due to being "scheduled" for 10ms in the future via setTimeout()
. This means the click()
function can return almost immediately, without waiting on the toggle()
to complete.
As others have noted, the best option is probably to wrap all of your div
s in a parent (if this is feasible for your actual code) and then toggle that parent div
.
JSFiddle link: http://jsfiddle.net/5E45Q/
If you can group all your div.g
's in a parent element, you will be able to hide that single parent element without modifying the DOM 700 times. See http://jsfiddle.net/SVf5k/6/
But if that is unavoidable, you can add a short delay before you toggle the div.g
's. This will give the js engine an opportunity to change the html on #toggleP
. See http://jsfiddle.net/SVf5k/5/
Edit: My original answer was aimed to address your confusion about why you only saw "Working..." for a brief moment and did not see the effects of toggle()
until after some time. Upon actually looking at the website you're building, I can see that your real concern is simply making this UI update faster, in which case I highly recommend Justin's answer (though I'd also be interested in an explanation of why there is such a huge performance difference there).
Short answer: I think what you want is an asynchronous iterator, which I've seen hand-rolled on other projects but strangely don't think I've seen in an open source library (doesn't mean it doesn't exist--I just haven't seen it).
And you can see here that it behaves the way (I think) you expect.
Now, for explanation:
So is jQuery bunching together both the html() and .toggle(), instead of running html() first?
I don't think it's jQuery; I believe it's browser-dependent behavior that you're seeing, with respect to how the browser's rendering engine processes UI updates from the event loop. I know that a lot of UI implementations do not update UI elements instantaneously but only in between every event that is processed.
With browsers my general observation (and I'm not really an authority on this; you'd have to talk with an actual browser contributor) is that some UI updates happen immediately, and some don't have a visible effect except on every full pass through the event loop. So it would appear to me that whatever jQuery's doing under the hood for toggle()
falls under the latter umbrella.
toggleClass()
is so much faster is that it boils down to a few simple comparisons, an array scan and a string manipulation -- about 25LOC including one "complex" nested call: either addClass()
or removeClass()
. Using the toggle()
effects function results in effects and easing calculations, binding/unbinding handlers, event registration, and other goodies -- I stopped counting after about 3 nested function calls (I was at ~200LOC). The effects function isn't necessarily ill-suited for this type of thing, it just doesn't scale to hundreds of elements very well - Justin ᚅᚔᚈᚄᚒᚔ 2012-04-13 14:51
If you look at the jQuery docs, you see that toggle()
has a callback -- though you also have to specify a duration if you want a callback. So your code might look something like:
$(document).ready(function () {
$('#toggleP').click(function() {
$('#toggleP').html("Working...");
$('div.g').toggle(500, function() {
$('#toggleP').html("Show/hide P");
});
});
});
...
...
<div class="plink" id="toggleP">Show/hide P</div>
If half a second is a good duration for you. See more here: http://api.jquery.com/toggle/
EDIT: Quick jsfiddle: http://jsfiddle.net/SVf5k/
use $.ajax()
here fiddle : http://jsfiddle.net/SVf5k/10/
$(function () {
$('#toggleP').click(function() {
$(this).html("...Working...");
$.ajax({
success: function(){
$('div.g').toggle(500, function() {
$('#toggleP').html("Show/hide P");
});
}
})
});
});