Select tags that starts with "x-" in jQuery
Asked Answered
C

9

12

How can I select nodes that begin with a "x-" tag name, here is an hierarchy DOM tree example:

<div>
  <x-tab>
    <div></div>
    <div>
      <x-map></x-map>
    </div>
  </x-tab>
</div>
<x-footer></x-footer>

jQuery does not allow me to query $('x-*'), is there any way that I could achieve this?

Champaigne answered 17/6, 2013 at 5:12 Comment(9)
Is this valid at first place? <x-map></x-map>Arcanum
document.createElement('x-map') I think it is valid.Pralltriller
actually is a new way to declare custom elements, just like x-tags.org or polymer-project.org projectChampaigne
@Scott Selby: :contains() is for text, not tag names.Lipocaic
I'd lean towards no simple out-of-the-box solution, since the jQuery element selector ends up calling getElementsByTagName(), which doesn't do wildcards.Duwalt
@Lipocaic - yup, that's why I didn't put mark as an answer on a guessIntermixture
This is not valid HTML and check the x-tags.org itself is not valid. validator.w3.org/…Rabkin
it soon will dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/… ... anyway, browsers will not block you by doing this today alsoChampaigne
@Champaigne can you tell me a little bit more about your use-case? Why exactly do you want to get all custom tag names that begin with 'x-' vs querying for specific custom elements by their individual tag names? (I wrote X-Tag, so I'm curious about what is driving your need for such functionality)Heaven
V
7

There is no native way to do this, it has worst performance, so, just do it yourself.

Example:

var results = $("div").find("*").filter(function(){
    return /^x\-/i.test(this.nodeName);
});

Full example:

http://jsfiddle.net/6b8YY/3/

Notes: (Updated, see comments)

If you are wondering why I use this way for checking tag name, see:
JavaScript: case-insensitive search
and see comments as well.

Also, if you are wondering about the find method instead of adding to selector, since selectors are matched from right not from left, it may be better to separate the selector. I could also do this:
$("*", $("div")). Preferably though instead of just div add an ID or something to it so that parent match is quick.

In the comments you'll find a proof that it's not faster. This applies to very simple documents though I believe, where the cost of creating a jQuery object is higher than the cost of searching all DOM elements. In realistic page sizes though this will not be the case.

Update:

I also really like Teifi's answer. You can do it in one place and then reuse it everywhere. For example, let me mix my way with his:

// In some shared libraries location:
$.extend($.expr[':'], {
    x : function(e) {
            return /^x\-/i.test(this.nodeName);
    }
});

// Then you can use it like:
$(function(){
    // One way
    var results = $("div").find(":x");

    // But even nicer, you can mix with other selectors
    //    Say you want to get <a> tags directly inside x-* tags inside <section>
    var anchors = $("section :x > a");

    // Another example to show the power, say using a class name with it:
    var highlightedResults = $(":x.highlight");
    // Note I made the CSS class right most to be matched first for speed
});

It's the same performance hit, but more convenient API.

Vestry answered 17/6, 2013 at 5:59 Comment(7)
Very nice, look good. You mentioned about performance; out of curiosity how're you measuring the performance? profiling using chrome developer tool or jsperf?Snailfish
As per pretty much all jQuery performance articles (proven by many JSPerf tests), native browser selectors are the fastest. For something like pseudo element (the updated version) and pretty much any selector not supported by browser natively (non CSS), JS (jQuery) has to get all elements in scope (that's why limiting the scope parent) and test each and every element instead of asking the browser to return them directly.Vestry
Some good references and tests on the topic: seesparkbox.com/foundry/jquery_selector_performance_testing , also encosia.com/… , and eng.wealthfront.com/2010/10/jquery-right-way.htmlVestry
There is also some good information on how it works here wordsbyf.at/2011/11/23/selectors-selectoring and of course the official jQuery performance learning page learn.jquery.com/performance/optimize-selectorsVestry
Wow!! I used to learn and write coding, most of the time I used to struggle to find an optimized solution. More things to learn and long way to travel. Thanks for taking time to guide me :)Snailfish
It seems my solution is faster than yours. Though I haven't considered about performance. I might be wrong please check this jsperf test.Pralltriller
The regex part is definitely faster. jsperf.com/regexstrmanipulation/2 Thanks a lot for this! I still think in realistic page size the find method will be faster, although for small DOM like this sample it's a bit slower. I updated the answer to reflect this.Vestry
P
8

The below is just working fine. Though I am not sure about performance as I am using regex.

$('body *').filter(function(){
    return /^x-/i.test(this.nodeName);
}).each(function(){
    console.log(this.nodeName);
});

Working fiddle

PS: In above sample, I am considering body tag as parent element.

UPDATE :

After checking Mohamed Meligy's post, It seems regex is faster than string manipulation in this condition. and It could become more faster (or same) if we use find. Something like this:

$('body').find('*').filter(function(){
    return /^x-/i.test(this.nodeName);
}).each(function(){
    console.log(this.nodeName);
});

jsperf test

UPDATE 2:

If you want to search in document then you can do the below which is fastest:

$(Array.prototype.slice.call(document.all)).filter(function () {
    return /^x-/i.test(this.nodeName);
}).each(function(){
    console.log(this.nodeName);
});

jsperf test

Pralltriller answered 17/6, 2013 at 5:49 Comment(4)
I like it, there may be a better way to improve this but for now may help me out..Champaigne
Good update. I have added comments to my answer about this. Thanks for bringing it up. I have upvoted your answer.Vestry
Be careful with these tests though, as they are not good examples. The ratio of other elements to the ones you want is unlikely to be the same in a real full page. Interesting seeing document.all though. I thought it was an old thing.Vestry
@MohamedMeligy Thanks, I will be careful with the tests though. :)Pralltriller
V
7

There is no native way to do this, it has worst performance, so, just do it yourself.

Example:

var results = $("div").find("*").filter(function(){
    return /^x\-/i.test(this.nodeName);
});

Full example:

http://jsfiddle.net/6b8YY/3/

Notes: (Updated, see comments)

If you are wondering why I use this way for checking tag name, see:
JavaScript: case-insensitive search
and see comments as well.

Also, if you are wondering about the find method instead of adding to selector, since selectors are matched from right not from left, it may be better to separate the selector. I could also do this:
$("*", $("div")). Preferably though instead of just div add an ID or something to it so that parent match is quick.

In the comments you'll find a proof that it's not faster. This applies to very simple documents though I believe, where the cost of creating a jQuery object is higher than the cost of searching all DOM elements. In realistic page sizes though this will not be the case.

Update:

I also really like Teifi's answer. You can do it in one place and then reuse it everywhere. For example, let me mix my way with his:

// In some shared libraries location:
$.extend($.expr[':'], {
    x : function(e) {
            return /^x\-/i.test(this.nodeName);
    }
});

// Then you can use it like:
$(function(){
    // One way
    var results = $("div").find(":x");

    // But even nicer, you can mix with other selectors
    //    Say you want to get <a> tags directly inside x-* tags inside <section>
    var anchors = $("section :x > a");

    // Another example to show the power, say using a class name with it:
    var highlightedResults = $(":x.highlight");
    // Note I made the CSS class right most to be matched first for speed
});

It's the same performance hit, but more convenient API.

Vestry answered 17/6, 2013 at 5:59 Comment(7)
Very nice, look good. You mentioned about performance; out of curiosity how're you measuring the performance? profiling using chrome developer tool or jsperf?Snailfish
As per pretty much all jQuery performance articles (proven by many JSPerf tests), native browser selectors are the fastest. For something like pseudo element (the updated version) and pretty much any selector not supported by browser natively (non CSS), JS (jQuery) has to get all elements in scope (that's why limiting the scope parent) and test each and every element instead of asking the browser to return them directly.Vestry
Some good references and tests on the topic: seesparkbox.com/foundry/jquery_selector_performance_testing , also encosia.com/… , and eng.wealthfront.com/2010/10/jquery-right-way.htmlVestry
There is also some good information on how it works here wordsbyf.at/2011/11/23/selectors-selectoring and of course the official jQuery performance learning page learn.jquery.com/performance/optimize-selectorsVestry
Wow!! I used to learn and write coding, most of the time I used to struggle to find an optimized solution. More things to learn and long way to travel. Thanks for taking time to guide me :)Snailfish
It seems my solution is faster than yours. Though I haven't considered about performance. I might be wrong please check this jsperf test.Pralltriller
The regex part is definitely faster. jsperf.com/regexstrmanipulation/2 Thanks a lot for this! I still think in realistic page size the find method will be faster, although for small DOM like this sample it's a bit slower. I updated the answer to reflect this.Vestry
G
3

It might not be efficient, but consider it as a last option if you do not get any answer.
Try adding a custom attribute to these tags. What i mean is when you add a tag for eg. <x-tag>, add a custom attribute with it and assign it the same value as the tag, so the html looks like <x-tag CustAttr="x-tag">.
Now to get tags starting with x-, you can use the following jQuery code:

$("[CustAttr^=x-]")

and you will get all the tags that start with x-

Girhiny answered 17/6, 2013 at 5:25 Comment(2)
I thought about this solution also.. but the problem is that you need to add an attribute everytime, the idea behind the tag name beginning with "x-" was to help querying instead of allowing all type of custom elements without any patternChampaigne
@mateusmaso: Off topic, but I wonder such an idea was even proposed. We've had a markup language that looks like HTML but provides namespacing out of the box for over a decade now; it's called XML, and it manifests in HTML as XHTML. Then again, jQuery doesn't support XML namespaces anyway, making that point quite moot.Lipocaic
O
3

custom jquery selector

jQuery(function($) {
    $.extend($.expr[':'], {
        X : function(e) {
            return /^x-/i.test(e.tagName);
        }
    });
});

than, use $(":X") or $("*:X") to select your nodes.

Oddson answered 17/6, 2013 at 6:35 Comment(1)
Good call for expressions :)Vestry
D
2

Although this does not answer the question directly it could provide a solution, by "defining" the tags in the selector you can get all of that type?

$('x-tab, x-map, x-footer')
Dissogeny answered 17/6, 2013 at 5:42 Comment(1)
Yeah, short of specifying each individual element name there really isn't any other way.Lipocaic
F
1

Workaround: if you want this thing more than once, it might be a lot more efficient to add a class based on the tag - which you only do once at the beginning, and then you filter for the tag the trivial way.

What I mean is,

function addTagMarks() {
    // call when the document is ready, or when you have new tags

    var prefix = "tag--"; // choose a prefix that avoids collision
    var newbies = $("*").not("[class^='"+prefix+"']"); // skip what's done already

    newbies.each(function() {
        var tagName = $(this).prop("tagName").toLowerCase();
        $(this).addClass(prefix + tagName);
    });

}

After this, you can do a $("[class^='tag--x-']") or the same thing with querySelectorAll and it will be reasonably fast.

Fermin answered 31/10, 2017 at 9:53 Comment(0)
M
0

See if this works!

function getXNodes() {
  var regex = /x-/, i = 0, totalnodes = [];
  while (i !== document.all.length) {
    if (regex.test(document.all[i].nodeName)) {
      totalnodes.push(document.all[i]);
    }
    i++;
  }
  return totalnodes;
}
Mongoloid answered 17/6, 2013 at 5:30 Comment(0)
B
0

Demo Fiddle

var i=0;
for(i=0; i< document.all.length; i++){
    if(document.all[i].nodeName.toLowerCase().indexOf('x-') !== -1){
        $(document.all[i].nodeName.toLowerCase()).addClass('test');
    }
}
Brookite answered 17/6, 2013 at 5:57 Comment(0)
C
-4

Try this

var test = $('[x-]');
if(test)
    alert('eureka!');

Basically jQuery selector works like CSS selector. Read jQuery selector API here.

Cucullate answered 17/6, 2013 at 5:20 Comment(1)
If you had read it yourself you would have realized that this won't actually work.Lipocaic

© 2022 - 2024 — McMap. All rights reserved.