views:

667

answers:

6

I'm working on a dropdown menu and need to mutate the existing html markup with JQuery to make it fit the design.

Here is a simplified example: Wrap all "ul's" that contain more than one "li" in a div (in the same div, NOT one div for each UL).

<ul>
    <li>foo</li>
</ul>
<ul>
    <li>foo</li>
    <li>foo</li>
</ul>
<ul>
    <li>foo</li>
    <li>foo</li>
</ul>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"&gt;&lt;/script&gt;

<script>
my_selection = [];
i = 0;
// find all ul's that have more than one li
$("ul").each(function(){
    if($(this).find("li").length > 1){
     // add this to my_selection
     my_selection[i] = $(this);
     i++;
    } // if
}); // each

// wrap my_selection in div tags
$(my_selection).wrapAll(document.createElement("div"));
</script>

The above code gives this firebug error:

"Node cannot be inserted at the specified point in the hierarchy" code: "3"

How can I make it work?

A: 

You have a bug in your code, attempting to wrap your LI's by DIV.

<script>
my_selection = [];
i = 0;
// find all ul's that have more than one li
$("ul").each(function() {
    var $ul = $(this);
    if ($ul.find("li").length > 1){
        // add this to my_selection
        my_selection[i] = $ul;
        i++;
    } // if
});

// wrap my_selection in div tags
$(my_selection).wrap(document.createElement("div"));
</script>
cballou
Cballou, I tried your example, but I'm still getting FireBug error: Node cannot be inserted at the specified point in the hierarchy" code: "3
A: 

I'm not sure if jQuery can take arrays as selectors. In $(my_selection), my_selection is not a jQuery object but an array of objects. You can try generating jQuery objects in the first place, using var my_selection = $(''); ... my_selection.add($(this));.

Also, with .wrapAll() method, you can just use: .wrapAll('<div></div>');.

Tatu Ulmanen
http://docs.jquery.com/Core/jQuery#elements
William
Thanks to you I'm a bit wiser now than yesterday.
Tatu Ulmanen
A: 

I was a bit busy when I answered this question, sorry for all the problems. This following solves the problem correctly.

<script type="text/javascript">
jQuery( function() {
    jQuery( 'ul' ).find( 'li:eq(1)' ).parent().wrapAll( '<div></div>' );
} );
</script>

Edit: I changed li:gt(0) to li:eq(1), they both work, but one makes more sense.

It's simple, it goes through all the ul's and their children, it tries to grab the 2nd child. If the 2nd child is found it then goes back to it's parent to add it to the wrapAll method.

Edit: It looks like if you edit your post more than 8 times times it becomes a Community Wiki, interesting.

William
@William - You are supposed to be finding the total number of list items. This code will fail as **ul.length == 1** in all cases. It is assumed that **.length** is attempting to find the length of an array and in your case you are merely checking against a single UL element, not an array of LI elements.
cballou
William, I tried your example, but it doesn't seem to work. I see no added "div" element.
@William - That code does not support your case and it does in fact reference the array of jQuery objects, as stated. Test your code for yourself.
cballou
Afraid this one won't work as it will only get all the ul elements that aren't the first one found.
Sonny Boy
Good catch, I fixed it now. Either way this works, but Sonny Boy deserves the credit. Plus he found a solution first also.
William
+3  A: 

This would be a much cleaner approach:

$('ul:has(li)').wrap('<div></div>');

First, you don't need to create the node, just provide the wrapping string to jQuery. Second, use the :has selector in this instance to cut down on the each functionality in your example. Finally, (this will depend on your intended functionality) you may want to use wrap instead of wrapAll.

EDIT: Another option would be to approach it from the other way around. Get the <li> tags then grab the <ul> parent:

$('li:not(:only-child)').parent().filter('ul').wrap('<div></div>');
Sonny Boy
I like the clean approach, just needs a quick adjust to ensure more than one LI exists (minimum of 1) +1
cballou
It needs to have more than one li, I've tried turning it into a selector, I wasn't able to find anything to do what he wants.
William
@cballou Thanks, good catch. My edit should have the correct code.
Sonny Boy
@Sonny Boy - Apparently I didn't write it the way I should. gt() can still be used, see my above post, yes this time it works. :P
William
@Sonny. Thanks for this. Your version is cleaner and works, but it won't do what I need it to. I need to be able to pick one or more consecutive uls and add wrap them in a div based on a variety of factors like number of li's in previous ul. I should have been more specific...
ed.talmadge give us an example of what you're trying to do. You can still probably use something similar to what we did (one liners) with that. Post more details
William
@ed Sounds like this is something you should consider handling server-side if it's going to start getting that comlicated. I'll give it my best shot in jQuery though if you can post what you're actually after.
Sonny Boy
+1  A: 

This is a bit of a hack.

<script>
jQuery( function() {
    my_selection = [];

    i = 0;
    n = 0;
    jQuery( 'ul' ).each( function() {
        var ul = jQuery( this );
        if ( ul.children().length > 1 ) {
            my_selection[n++] = 'ul:eq(' + i + ')';
        }
        ++i;
    } );

    jQuery( my_selection.join( ',' ) ).wrapAll( document.createElement( 'div' ) );
} );
</script>
William
A: 

These are all great answers. But I notice that your script is not inside a $(document).ready(function(){...}) and you seemed to verify this error still exists with other code that wasn't wrapped in the ready function.

I tried and found that William's code worked (+1 to him), but because of my OCD I cleaned it up a bit

$(document).ready(function(){
 my_selection = [];
 $('ul').each(function(i){
  if ($(this).find('li').length > 1 ){
   my_selection.push(['ul:eq(' + i + ')']);
  }
 });
 $( my_selection.join(',')).wrapAll('<div class="wrapper"></div>');
})
fudgey