views:

76

answers:

5

Hi,

I've created the following process. Basically it loops through a gigantic unordered list with multiple level nested lists and creates a 2 level nested unordered list. It works well but is very slow in IE7. FireFox and Safari have no big problems with it. Not very good at jQuery I was wondering if I could speed up this by using better/faster/smarter functions. Due to the CMS I am using I can not change the output nor jQuery version I'm using which is 1.2.6.

Here's what I've got;

$('ul#mainnav li ul li').each(function() {
    // add level class
    $(this).addClass('depth-'+$(this).parents('ul').length);
    // make uls siblings in stead of children
    $($(this).children('ul')).insertAfter($(this));
});
// take all lis out of their uls
$('ul#mainnav li ul').each(function() {
    $(this).replaceWith($(this).children());                
});
// wrap chidlren lis with div
$('ul#mainnav li').each(function() {
    $(this).children('li:first').before('<ul class="megamenu" />');     
    $(this).children('.megamenu').append($(this).children('li'));
});
// apply plugin easyLisSplitter
$('.megamenu').easyListSplitter({ 
    colNumber: 5
});

Any ideas? If more info is needed let me know!

Cheers

EDIT:

Thanx for all the replies. Haven't tried everyting yet. Using var $this = $(this); sped up things by 20 milliseconds. Well I'll take those. All help is still welcome! Here's the before markup:

<ul id="mainnav">
<li><a href="#">A-1</a>
    <ul>
        <li><a href="#">A-1-1</a>
            <ul>
                <li><a href="#">A-1-1-1</a></li>
                <li><a href="#">A-1-1-2</a></li>
                <li><a href="#">A-1-1-3</a></li>
            </ul>
        </li>
        <li><a href="#">A-1-2</a></li>
        <li><a href="#">A-1-3</a>
            <ul>
                <li><a href="#">A-1-3-1</a></li>
                <li><a href="#">A-1-3-2</a></li>
                <li><a href="#">A-1-3-3</a></li>
            </ul>
        </li>
        <li><a href="#">A-1-4</a>
            <ul>
                <li><a href="#">A-1-4-1</a></li>
                <li><a href="#">A-1-4-2</a></li>
                <li><a href="#">A-1-4-3</a></li>
                <li><a href="#">A-1-4-4</a></li>
                <li><a href="#">A-1-4-5</a></li>
            </ul>
        </li>
        <li><a href="#">A-1-5</a>
            <ul>
                <li><a href="#">A-1-5-1</a></li>
                <li><a href="#">A-1-5-2</a></li>
                <li><a href="#">A-1-5-3</a></li>
                <li><a href="#">A-1-5-4</a></li>
                <li><a href="#">A-1-5-5</a></li>
            </ul>
        </li>
    </ul>
</li>
<li><a href="#">B-1</a>
    <ul>
        <li><a href="#">B-1-1</a>
            <ul>
                <li><a href="#">B-1-1-1</a></li>
                <li><a href="#">B-1-1-2</a></li>
                <li><a href="#">B-1-1-3</a></li>
            </ul>
        </li>
        <li><a href="#">B-1-2</a></li>
        <li><a href="#">B-1-3</a>
            <ul>
                <li><a href="#">B-1-3-1</a></li>
                <li><a href="#">B-1-3-2</a></li>
                <li><a href="#">B-1-3-3</a></li>
            </ul>
        </li>
        <li><a href="#">B-1-4</a>
            <ul>
                <li><a href="#">B-1-4-1</a></li>
                <li><a href="#">B-1-4-2</a></li>
                <li><a href="#">B-1-4-3</a></li>
                <li><a href="#">B-1-4-4</a></li>
                <li><a href="#">B-1-4-5</a></li>
            </ul>
        </li>
        <li><a href="#">B-1-5</a>
            <ul>
                <li><a href="#">B-1-5-1</a></li>
                <li><a href="#">B-1-5-2</a></li>
                <li><a href="#">B-1-5-3</a></li>
                <li><a href="#">B-1-5-4</a></li>
                <li><a href="#">B-1-5-5</a></li>
            </ul>
        </li>
    </ul>
</li>

And here's the after;

<ul id="mainnav">
<li><a href="#">A-1</a>
    <div class="listContainer1">
        <ul class="megamenu listCol1">
            <li class="depth-2"><a href="#">A-1-1</a></li>
            <li class="depth-3"><a href="#">A-1-1-1</a></li>
            <li class="depth-3"><a href="#">A-1-1-2</a></li>
            <li class="depth-3"><a href="#">A-1-1-3</a></li>
            <li class="depth-2"><a href="#">A-1-2</a></li>
        </ul>
        <ul class="listCol2 megamenu">
            <li class="depth-2"><a href="#">A-1-3</a></li>
            <li class="depth-3"><a href="#">A-1-3-1</a></li>
            <li class="depth-3"><a href="#">A-1-3-2</a></li>
            <li class="depth-3"><a href="#">A-1-3-3</a></li>
            <li class="depth-2"><a href="#">A-1-4</a></li>
        </ul>
        <ul class="listCol3 megamenu">
            <li class="depth-3"><a href="#">A-1-4-1</a></li>
            <li class="depth-3"><a href="#">A-1-4-2</a></li>
            <li class="depth-3"><a href="#">A-1-4-3</a></li>
            <li class="depth-3"><a href="#">A-1-4-4</a></li>
            <li class="depth-3"><a href="#">A-1-4-5</a></li>
        </ul>
        <ul class="listCol4 megamenu">
            <li class="depth-2"><a href="#">A-1-5</a></li>
            <li class="depth-3"><a href="#">A-1-5-1</a></li>
            <li class="depth-3"><a href="#">A-1-5-2</a></li>
            <li class="depth-3"><a href="#">A-1-5-3</a></li>
            <li class="depth-3"><a href="#">A-1-5-4</a></li>
        </ul>
        <ul class="listCol5 megamenu last">
            <li class="depth-3"><a href="#">A-1-5-5</a></li>
        </ul>
    </div>
</li>
<li><a href="#">B-1</a>
    <div class="listContainer2">
        <ul class="megamenu listCol1">
            <li class="depth-2"><a href="#">B-1-1</a></li>
            <li class="depth-3"><a href="#">B-1-1-1</a></li>
            <li class="depth-3"><a href="#">B-1-1-2</a></li>
            <li class="depth-3"><a href="#">B-1-1-3</a></li>
            <li class="depth-2"><a href="#">B-1-2</a></li>
        </ul>
        <ul class="listCol2 megamenu">
            <li class="depth-2"><a href="#">B-1-3</a></li>
            <li class="depth-3"><a href="#">B-1-3-1</a></li>
            <li class="depth-3"><a href="#">B-1-3-2</a></li>
            <li class="depth-3"><a href="#">B-1-3-3</a></li>
            <li class="depth-2"><a href="#">B-1-4</a></li>
        </ul>
        <ul class="listCol3 megamenu">
            <li class="depth-3"><a href="#">B-1-4-1</a></li>
            <li class="depth-3"><a href="#">B-1-4-2</a></li>
            <li class="depth-3"><a href="#">B-1-4-3</a></li>
            <li class="depth-3"><a href="#">B-1-4-4</a></li>
            <li class="depth-3"><a href="#">B-1-4-5</a></li>
        </ul>
        <ul class="listCol4 megamenu">
            <li class="depth-2"><a href="#">B-1-5</a></li>
            <li class="depth-3"><a href="#">B-1-5-1</a></li>
            <li class="depth-3"><a href="#">B-1-5-2</a></li>
            <li class="depth-3"><a href="#">B-1-5-3</a></li>
            <li class="depth-3"><a href="#">B-1-5-4</a></li>
        </ul>
        <ul class="listCol5 megamenu last">
            <li class="depth-3"><a href="#">B-1-5-5</a></li>
        </ul>
    </div>
</li>

A: 

Did you try hiding the list while applying the changes in order to save the browser from having to reflow the layout after each change?

Jonas H
I tried $('ul#mainnav').hide() but it slowed things down :( Thanx tho!
A: 

One thing you could do is provide a context for your .megamenu selector. Selecting by class name takes a bit longer than selecting by id because there's no native function to do it in Javascript. Setting a scope on the selector helps jQuery narrow the search for elements with that class. It would look like this:

$('.megamenu', $('#someelement'))

But it may not be possible in your setup.

Another thing would be to construct a string of html that you append to your megamenu all at once instead of running append() for each LI in your list. jQuery's append method works great with large HTML strings if you want to cut down on the time it takes to insert DOM nodes. That seems to be the biggest issue here...you're running a lot of DOM manipulation methods inside loops. You want to cut down on as many of these as you can wherever you can. It may not be possible to do much in your script, however, aside from constructing a string of html to append all at once to megamenu (which should, possibly, be an ID, not a class).

treeface
Setting a scope like your example slowed it down by 5 milliseconds :( Could you give an example as to how to create a string of html to work with?
@user280313: because jQuery implements `$('.megamenu',$('#someelement'))` by calling `.find()`, you might find it marginally faster to use that directly, giving you: `$('#someelement').find('.megamenu')` Though I imagine it'll only be a few milliseconds.
David Thomas
+1  A: 

Another thing that should help is to eliminate redundant jquery calls by storing the resultant object in a variable. For example, change this:

$('ul#mainnav li ul li').each(function() {
    // add level class
    $(this).addClass('depth-'+$(this).parents('ul').length);
    // make uls siblings in stead of children
    $($(this).children('ul')).insertAfter($(this));
});

to this:

$('ul#mainnav li ul li').each(function() {
    var $this = $(this);
    // add level class
    $this.addClass('depth-'+$this.parents('ul').length);
    // make uls siblings in stead of children
    $($this.children('ul')).insertAfter($this);
});

Note the convention of starting a var name with $ when it refers to a jquery object.

Also, I'm not sure, but I think there's a more direct way to append to a list of children which might simplify the last line of the above code. Maybe something like $this.children('ul').append?

As the other answers say, only alter the live dom once. Another way to do this is to clone the whole structure that you're working on, manipulate the clone, and then replace the original with the clone. However you do it, the key is to avoid the browser trying to render each change as it's made.

Sid_M
Using var $this = $(this); sped up things by 20 milliseconds. Well I'll take those. Thanx!
Not exactly a big improvement. Oh well. It's a good practice nonetheless. Thanks for the feedback. BTW if you haven't tried it, I definitely encourage you to try doing your manipulations on a clone so the browser isn't trying to render each change as it's made. I haven't tested it, but many people stress the value of this approach.
Sid_M
A: 

Hi guys,

Well I'm learning loads about jQuery tonight... I have tried a completely different approach. The reason I wanted all list items of 2nd level and deeper as siblings is because I wanted to use the easylistsplitter plugin. Because this ate a lot of the resources I created the following code. I don't really need list items, Google doesn't run jQuery so it will see list items, my users will see a neat little menu with menu items split into columns. Hope this code helps others, it does the job in half the time of the first code.

If anyone has any pointers on making this code even faster please let me know.

    $('ul#mainnav).each(function() {
    $(this).children('li').append('<div class="megamenu" />');
});
$('ul#mainnav').each(function() {
    var $this = $(this);
    var $length = $this.parents('ul').length;
    $this.addClass('depth-'+$length);
    if($length > 1) {
        $this.appendTo($this.parents('li').find('div.megamenu'));
    }
});
$('ul#mainnav ul').remove();
$('ul#mainnav div.megamenu').each(function() {
    var links = $(this).find('a');
    var columns = 5;
    var perCol = Math.ceil(links.length/columns);
    for(var i = 0; i < links.length; i+=perCol) { 
      links.slice(i, i+perCol).wrapAll('<div>'); 
    }
});
A: 

Have you tried using the .detach() -alike workarounds for previous versions of jQuery (since detach is 1.4 specific)?

Specifically this example might work (not sure, I don't use 1.2.6 anymore and the requester asked for a 1.3.2 workaround).

elem.clone(true);
elem.remove(); 
r00fus
Will try! Thanx...