How do you replace CSS/toggle a large amount of elements?

Go To StackoverFlow.com

4

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:

  1. When "Show/hide Pronunciation" is clicked, immediately change that text to "Working"
  2. Wait until all 'div.gr' are hidden/shown
  3. "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?

2012-04-04 23:28
by Heitor Chang
Is it possible for you to put all divs with class "g" into some kind of container div? Doing a toggle on a single parent element seems to perform muchbetter than on the hundreds of individual div - DMac the Destroyer 2012-04-09 21:03
I updated my question with the full page, http://freezoo.alwaysdata.net/readhash8.py and I considered how to restructure it but came short. It would be nice to have the "gr" text all in one line. No, I don't know how to put them together - Heitor Chang 2012-04-09 23:04
Off topic from your question, but you may want to change the divs "#toggleFormBar" and "#togglePron" to as instead. Shouldn't affect your code at all, but generally best practice is for clickables to be anchors - DACrosby 2012-04-12 05:39
@DouglasA.Crosby Thanks for the tip. First, I am not too familiar with CSS. I want the whole width of the bar to be clickable and don't see a clean, obvious way to do that with anchors - Heitor Chang 2012-04-12 13:56


1

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.

2012-04-10 11:40
by g19fanatic
I like this answer and it is much cleaner than the other ones suggested. I am just having trouble getting the css changes to work in IE. I tried two methods so far, and will keep looking. They work on Chrome and Firefox. This one was easy to follow http://www.alistapart.com/articles/alternate and the same for this 26 vote answer: http://stackoverflow.com/a/622193/1238040 does not work in IE - Heitor Chang 2012-04-10 14:10
Alright, I found this "Totally Pwn CSS with JS" that totally pwnd the other solutions I tried : - Heitor Chang 2012-04-10 14:21
http://bugs.jquery.com/ticket/11579 added feature request :) hopefully it will eventually make it in - g19fanatic 2012-04-10 14:52
Well, the jquery folks' reply was that there are plugins that accomplish this. Worth a try, at least - Heitor Chang 2012-04-10 17:43
I tried to look around to see what plugin's would allow you to do this with jQuery but didn't find any... Not surprised to say the least... Good luck - g19fanatic 2012-04-10 18:04
Looks like the jQuery.Rule plugin does the above. Checkout http://flesler.blogspot.com/2007/11/jqueryrule.htm - g19fanatic 2012-04-10 18:13
Thanks for looking, but I'm not convinced to use it. In the demo page http://flesler.webs.com/jQuery.Rule/ the second to last example returns an error. Maybe it's intentional but I doubt it. I'm thinking the free hosting ads broke his examples. Anyway, I have to admit I'm being selfish and milking the bounty period, hoping for an answer that's even more pwnage. Perhaps it'll also encourage people to come read the question and upvote - Heitor Chang 2012-04-10 18:27


6

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 divs in a parent (if this is feasible for your actual code) and then toggle that parent div.

JSFiddle link: http://jsfiddle.net/5E45Q/

2012-04-09 21:07
by Justin ᚅᚔᚈᚄᚒᚔ
I should've shown the full page, now I updated the question. http://freezoo.alwaysdata.net/readhash8.py I thought about how to join the "gr" divs and came short. The closest would be putting all the "gr"s together in a line but I couldn't think of how to line it up with the Chinese - Heitor Chang 2012-04-09 23:25
@HeitorChang: See updated answer that should help improve your use case - Justin ᚅᚔᚈᚄᚒᚔ 2012-04-10 18:17
Yes, this is simple and fast, I wouldn't have thought it would run so quickly. Thank you for the update - Heitor Chang 2012-04-10 18:36
The class swap is a winner! (assuming no transition effect is required - kingjeffrey 2012-04-10 23:51
For a slightly faster class based solution (and the original on this answer is so good, this improvement may not be worth the change), you could change the class of the parent table rather than the 700 child elements. This will cause the browser's rendering engine to reflow the page just once. See http://jsfiddle.net/5E45Q/5 - kingjeffrey 2012-04-11 00:03
@kingjeffrey: Good call, since as the number of child elements increases the difference between class-swap approaches becomes more pronounced. I'll edit the answer to include this method - Justin ᚅᚔᚈᚄᚒᚔ 2012-04-11 15:21
This is definitely the cleanest, thanks again - Heitor Chang 2012-04-12 19:04


3

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/

2012-04-09 21:17
by kingjeffrey
You're right, it would be faster and easier if I grouped them. The way I laid out the information, I just don't see a way how. I added a link to the full page above, http://freezoo.alwaysdata.net/readhash8.p - Heitor Chang 2012-04-09 23:23
As a hack, you could toggle between two divs, one div contains the characters and their pronunciation and the other contains just the characters. This would require each character reside in two locations, only one of which will be shown at any given time - kingjeffrey 2012-04-09 23:27
Wouldn't I still need the 700 divs for each character? I don't get how toggling divs would run faster, I mean, wouldn't I still need to hide one or the other when the Show/Hide is clicked - Heitor Chang 2012-04-09 23:31
No, hide #group-1, show #group-2:
character 1pronunciation 1
character 2pronunciation 2
character 1
character 2
kingjeffrey 2012-04-09 23:40
>group-1 would contain all 700 characters and their pronunciations. #group-2 would contain just the 700 characters. So you wouldn't be toggling between 1400 groups, just 2. - kingjeffrey 2012-04-09 23:43
I got it, I'm trying it out right now : - Heitor Chang 2012-04-09 23:53
Wonderful, I would never have thought of repeating the same info, it just doesn't feel right superficially, it feels like repeating code. Anyway, the speed difference from 10 seconds to almost nothing makes it worthwhile - Heitor Chang 2012-04-10 00:03
Like I said. It is a hack. But it works - kingjeffrey 2012-04-10 00:14
Oh, I didn't notice you calling it a hack. It sure looks like one. Well, 20 more hours till I can deliver the bounty. Will someone top your hack? : - Heitor Chang 2012-04-10 00:20


2

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

Something like this.

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.

2012-04-10 14:54
by Dan Tao
Dan, the reason that 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


0

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/

2012-04-04 23:31
by Mike Lentini
Good idea with the jsfiddle. I really do have a bunch of divs, on average 600-700. So with that many divs, I'm not getting nice results anymore. This update has around 700 divs in its body: http://jsfiddle.net/SVf5k/2/ Maybe jQuery isn't the place for changes, I need to restructure my html - Heitor Chang 2012-04-04 23:42
Hm, yeah I can see that it gets a bit slow. I'm not sure if there is a faster way to achieve it - Mike Lentini 2012-04-04 23:53
It's ok for the toggle to be slow, but I'm looking for immediate feedback on the screen that the toggling started and isn't complete yet - Heitor Chang 2012-04-04 23:56


0

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");
              });
          }
      })
  });
});
​
2012-04-10 14:59
by Yorgo
Ads