views:

164

answers:

3

In jQuery I'm trying to select only mount nodes where a and b's text values are 64 and "test" accordingly. I'd also like to fallback to 32 if no 64 and "test" exist. What I'm seeing with the code below though, is that the 32 mount is being returned instead of the 64.

The XML:


<thingses>
    <thing>
        <a>32</a> <-- note, a here is 32 and not 64 -->
        <other>...</other>
        <mount>sample 1</mount>
        <b>test</b>
    </thing>
    <thing>
        <a>64</a>
        <other>...</other>
        <mount>sample 2</mount>
        <b>test</b>
    </thing>
    <thing>
        <a>64</a>
        <other>...</other>
        <mount>sample 3</mount>
        <b>unrelated</b>
    </thing>
    <thing>
        <a>128</a>
        <other>...</other>
        <mount>sample 4</mount>
        <b>unrelated</b>
    </thing>
</thingses>

And unfortunately I don't have control over the XML as it comes from somewhere else.

What I'm doing now is:


var ret_val = '';

$data.find('thingses thing').each(function(i, node) {
    var $node = $(node), found_node = $node.find('b:first:is(test), a:first:is(64)').end().find('mount:first').text();
    if(found_node) {
        ret_val = found_node;
        return;
    }

    found_node = $node.find('b:first:is(test), a:first:is(32)').end().find('mount:first').text();
    if(found_node) {
        ret_val = found_node;
        return;
    }

    ret_val = 'not found';
});

// expected result is "sample 2", but if sample 2's parent "thing" was missing, the result would be "sample 1"
alert(ret_val);

For my ":is" selector I'm using:


    if(jQuery){
        jQuery.expr[":"].is = function(obj, index, meta, stack){
            return (obj.textContent || obj.innerText || $(obj).text() || "").toLowerCase() == meta[3].toLowerCase();
        };
    }

There has to be a better way than how I'm doing it. I wish I could replace the "," with "AND" or something. :)

Any help would be much appreciated. thanks!

A: 

Why do you call end() after find()? I may not understand your intention correctly, but end() takes you back to $node, so that the subsequent find("mount:first") just returns you the first mount child of that.

Am I wrong here?

Edit

Besides, why do you do .each on all things? This way, your ret_value will actually get the mount value of the last thing that conforms to the 64-if-not-then-32 condition.

If you want to select the first 64/test thing, and if there no such thing, then the first 32/test, then why don't you just write this explicitly?

var ret_value = $data.find( "thingses > thing:has( a:is(64) ):has( b:is(test) ) > mount" ).text();
if ( !ret_value )
    ret_value = $data.find( "thingses > thing:has( a:is(32) ):has( b:is(test) ) > mount" ).text();

And you could encapsulate that long query to make the code more readable:

function findByAandB( data, a, b ) {
    return data.find( "thingses > thing:has( a:is(" + a + ") ):has( b:is(" + b + ") ) > mount" ).text();
}

var ret_value = findByAandB( 64, "test" ) || findByAandB( 32, "test" );

Do I misunderstand what you need? Because I'm a bit confused :-)

Fyodor Soikin
hi, yep i use end() to return to $node so I can get the mount text value for that "thing." i think it's equivalent to using .siblings('mount').text().
taber
No, it's not. `end()` actually CANCELS your last query transformation. So that `$("a").find("b").end()` is equivalent to `$("a")`
Fyodor Soikin
Hi, thanks, I didn't realize I could chain :has like that. I will give that a shot!
taber
`:has` is essentially a filter, just like your `:is` is. And you can apply as many filters as you want, one after another.
Fyodor Soikin
that did the trick! nice idea reusing it like that, too. thanks a bunch.
taber
A: 

This seems to work for me:

var el = $('thingses thing')
    .has('b:contains("test")')
    .has('a:contains(64)');
if(!el.length) 
    el = $('thingses thing')
        .has('b:contains("test")')
        .has('a:contains(32)');
alert(el.find('mount:first').text());
Brother Erryn
Hey, what the...? How is this different from my answer?
Fyodor Soikin
It's not really...there were no responses when I started typing, and yours was there when I finished (I cleaned up the formatting a couple of times after that). It never hurts to see another coding style, and I expect you'll get the green checkbox.
Brother Erryn
A: 

Various ideas:

  • Using a sibling selector (Hierarchy selectors in jQuery)
    b could precede or follow a, otherwise you can keep half of the selector

    find('b:first:is(test) ~ a:first:is(64), a:first:is(64) ~ b:first:is(test)')

  • find one then go back and find the other one. I guess it may be ugly though

    find('b:first:is(test)).parent().find('a:first:is(64)')

  • custom jQuery selectors can take parameters so you can write your :allAtOnce selector :)

Felipe Alsacreations