views:

187

answers:

3

Using the following sample markup:

<ul>
    <li><div>Hello</div>World</li>
    <li>Hello World
        <ul>
             <li style="voice-family: dizzy">What a wonderful world</li>
        </ul>
    </li>
    <li><a href="hello.html">Hello World</a>
        <ul>
             <li>Goodbye world</li>
        </ul>
    </li>
</ul>

I'm trying to create a jquery selector that will grab the first element of each top level LI that is NOT a div, but COULD be either another DOM element OR a text string.

So, running it against the above, I'd like to return 3 elements:

  1. World
  2. Hello World
  3. <a href="hello.html">Hello World</a>

Assuming I already created a selector to grab each of the top level LIs (We'll call that $topLevelLIs) I've come up with this:

$topLevelLIs.find("*:first:not(div)")

Alas, as you can probably see, that will only return a dom element, and, as such, will only find something in option #3. The first two are just returned as empty.

Is there a way to do a "Grab the first DOM element or grab the first text string before the next DOM element" logic?

SOLUTION:

Thanks to the help of Russ and Marc I ended up using this to eventually get the type of node, and then I can act upon that:

$topLevelLIs.contents(":not(div)")
    .filter(function(index) {
        return index == 0 
    }).get(0).nodeType
+1  A: 

You can't return a text node using jQuery's selector syntax. You have to do something a bit more complex. This StackOverflow question should give you what you need. Remember that, if you need this to work in IE, you have to use the number 3 instead of Node.TEXT_NODE.

Marc W
Thanks, Marc! I'd love to see future jQuery allow for selecting of text nodes via their selectors.
DA
+1  A: 

Assuming you have a selector to identify the top <ul>'s <li> elements (I'll use the id top in the example), the you could use the $.map utility function to get what you're after. Something like

var children = $.map($('#top > li'),function(n,i) {
  var child = n.firstChild;
  while (child && child.nodeType === 1 && child.nodeName === 'DIV')
  {
    child = child.nextSibling;
  }
  return child && child.nodeType === 3? 
    child.textContent : child;
});

console.log(children); // will print out the elements you want 
                       // to the console in firebug

This will also handle an occurrence of an <li> element with no text (or child elements).

Russ Cam
Thanks, Russ. That is very helpful and thorough. Will definitely come in handy!
DA
A: 

You can't really "select" text (not in the widely-accepted sense of the term); do you mean that, in the instance of no <div>, you simply want to select the wrapping <li>?

This would select all <div> elements that are the first elements within each <li>:

$('li div:first-child');

Or, you could select only <li>s that contain a <div> element as the first child:

$('li').filter('li div:first-child');
J-P