12

What would be the best way to get all divs that have any class that starts with input? In other words, a and b should be returned from what's below, but not c.

<div id="a" class="input1 foo"></div>
<div id="b" class="foo input2"></div>
<div id="c" class="xinput3 foo"></div>

The ostensible way, which surprisingly was accepted here, is to do $("div[class^='input']"); but of course that misses b. And of course $("div[class*='input']"); will give a false positive on c.

The best I could come up with was this monstrosity

function getAllInputDivs() {
    return $("div").filter(function (i, currentDiv) {
        return $.grep($(currentDiv).attr("class").split(" "), function (val) {
            return val.substr(0, "input".length) === "input";
        }).length > 0;
    });
}

Is there a cleaner way? Here's a working fiddle of the above

Community
  • 1
  • 1
Adam Rackis
  • 82,527
  • 56
  • 270
  • 393
  • I don't think classnames are the best option when having things like input1 and input2. If the input is unique enough to be enumerated in that way, you can use ID's or names's which can be selected with `$("[id^=input]")` – Kevin B Dec 20 '11 at 17:14
  • @KevinB - you're probably right. I'm not sure if there's a good use case for this or not. Right now it's just an theoretical question to see if it can be done. – Adam Rackis Dec 20 '11 at 17:19
  • Similar question: [jQuery selector to target any class name (of multiple present) starting with a prefix?](http://stackoverflow.com/questions/4524412/jquery-selector-to-target-any-css-name-of-multiple-present-starting-with-a-pre) but I haven't made custom selector expressions before, so I quite like the answers to this question :) – BoltClock Dec 20 '11 at 17:41
  • 1
    @BoltClock: Custom selectors break `querySelectorAll`. They're not worth it. –  Dec 20 '11 at 18:31
  • 1
    @ЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖ: True that. – BoltClock Dec 20 '11 at 20:29
  • @ЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖ - I think I finally understand your point. You're saying that when you *don't* use custom selectors jQuery is able to use the native (and likely fast) querySelectAll? – Adam Rackis Dec 20 '11 at 20:35
  • @AdamRackis: Hey, sorry. Just saw this comment. Yes, jQuery (or Sizzle actually) uses the native `querySelectorAll` when possible. It attempts the `qSA` inside a `try/catch` because invalid selectors will throw an error. So in the catch block, it shifts over to Sizzle's JavaScript based selector engine which is typically slower. –  Dec 22 '11 at 02:59
  • ...keep in mind also that several built-in Sizzle selectors are custom. I tend to avoid these. –  Dec 22 '11 at 03:01
  • @ЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖ - good stuff - thanks! – Adam Rackis Dec 22 '11 at 03:02

3 Answers3

22

You can create your own expression in jQuery

$.expr[':'].hasClassStartingWithInput = function(obj){
  return (/\binput/).test(obj.className);
};

and you can retrieve those div with

$('div:hasClassStartingWithInput');

a JsFiddle Example: http://jsfiddle.net/7zFD6/


Edit: you could also use a parameter (without hardcoding the class name inside the function identifier) in this way

$.expr[':'].hasClassStartingWith = function(el, i, selector) {
  var re = new RegExp("\\b" + selector[3]);
  return re.test(el.className);
}

new example on http://jsfiddle.net/pMepk/1/

Fabrizio Calderan
  • 120,726
  • 26
  • 164
  • 177
  • 1
    Shouldn't that `\b` be a `^` instead? – Blazemonger Dec 20 '11 at 17:09
  • no it doesn't. the `^` would check only if a class starting with input is also the first class listed into the class attribute. As he specified inside the question he need to have a and b cases – Fabrizio Calderan Dec 20 '11 at 17:12
  • 1
    I think `\b` could fail in some (narrow) situations since a class may contain characters that would be interpreted as a word boundary. Like `my.input`, which is a valid class. –  Dec 20 '11 at 17:13
  • @FabrizioCalderan +1. That's really, really slick. – Adam Rackis Dec 20 '11 at 17:16
  • 1
    @ЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖΞЖ - I'd be able to live with those narrow exceptions. I can't imagine ever, ever ever wanting to create a class name of `my.input` – Adam Rackis Dec 20 '11 at 17:17
  • @AdamRackis Yeah if you only work with your own HTML, then I'm sure it will be fine. –  Dec 20 '11 at 17:20
  • you're right @Šime Vidas, just placed to enclose the whole expression – Fabrizio Calderan Dec 20 '11 at 17:31
  • 1
    @FabrizioCalderan Paren-sequences like `((` or even `(((` degrade the code-readability (in my opinion) - I try to get rid of them where ever I can... – Šime Vidas Dec 20 '11 at 17:34
  • @Fabrizio Calderan: That is not necessary at all. `return ((/\binput/).test(obj.className));` evaluates in the same order as `return /\binput/.test(obj.className);` – BoltClock Dec 20 '11 at 17:38
  • @BoltClock: already answered to Šime Vidas =) Just useless parens to highlight the whole expression. – Fabrizio Calderan Dec 20 '11 at 17:56
  • @FabrizioCalderan The example #2 returns an object, where I want Boolean. Could you also please explain how the parameters `el, i, selector` work? Thank you.. – 5ervant - techintel.github.io Sep 02 '15 at 03:21
  • @FabrizioCalderan I think I already found how to use `$("div:hasClassStartingWith('test')")[0]`. Now my following question, how can I use that expression with a jQuery object instead of an HTML element? – 5ervant - techintel.github.io Sep 02 '15 at 03:40
  • @FabrizioCalderan **Solved it!** ...by using: `.is(":hasClassStartingWith('test')")` – 5ervant - techintel.github.io Sep 02 '15 at 03:55
6

Here's one way...

function getAllInputDivs() {
    return $("div").filter(function () {
        return /(?:^|\s)input/.test(this.className);
    });
}

Or make it more versatile...

function classStartsWith( tag, s ) {
    var re = new RegExp('(?:^|\\s)' + s);
    return $(tag || '*').filter(function () {
        return re.test(this.className);
    });
}

Or take the indexOf approach if you don't like regex...

function classStartsWith( tag, s ) {
    return $(tag || '*').filter(function () {
        return this.className.indexOf(s)===0 || this.className.indexOf(' ' + s)>-1;
    });
}

Though you should be aware that it does't test for tab characters, only space characters, so it could fail if a tab was used instead of a space.


Going back to the regex versions, you can improve efficiency by adding the searched string to the selector.

Then it is only testing a subset of divs.

function getAllInputDivs() {
    return $("div[class*='input']").filter(function () {
        return /(?:^|\s)input/.test(this.className);
    });
}

With the .filter() applied to only those divs that you know have input somewhere in the class, the performance will improve.

Or the versatile version would look like this:

function classStartsWith( tag, s ) {
    var re = new RegExp('(?:^|\\s)' + s);
    return $((tag || '*') + '[class*="' + s + '"]').filter(function () {
        return re.test(this.className);
    });
}
2

This is my solution for the problem:

(function($) {
    $.fn.hasClassStartsWith = function(klass) {
    var _return = [];
    for(var i = 0; i < this.length; i++){
        if((' ' + $(this[i]).attr('class')).indexOf(klass) != -1)
            _return.push(this[i]);
    }
    return _return;
    }
})(jQuery);

Use it as follows:

var divs = $('div').hasClassStartsWith("my_class_prefix");

It works also for the case someone creates a class with a dot in the middle.

igasparetto
  • 1,086
  • 1
  • 12
  • 28