ilias
ilias

Reputation: 63

is it possible to combine elements for the same attributes with querySelectorAll?

Out of curiosity, would it be possible to refactor this:

document.querySelectorAll('input[id^=' + tagId + '],select[id^=' + tagId + '],textarea[id^=' + tagId + ']');

to something smaller, like this?:

document.querySelectorAll('(input|select|textarea)[id^=' + tagId + ']');

Of course this doesn't work, that's why I'm asking. Is it possible?

Upvotes: 1

Views: 1508

Answers (4)

guest271314
guest271314

Reputation: 1

An approach extending document.querySelcectorAll() to internally add vendor prefix at :any without including vendor prefix in selector string by using feature detection at document.body.style . Could probably be improved in several ways; additional css selectors that currently require vendor prefixes could be added , multiple if statements could be more thorough or shortened; and other improvements

originalQuerySelectorAll = document.querySelectorAll;
console.log(originalQuerySelectorAll);

document.querySelectorAll = function() {
  var selector = arguments[0];
  if (/\:any/.test(selector)) {
      if ("webkitAnimation" in document.body.style) {
        selector = selector.replace(/\:(any)/g, ":-webkit-$1");
      }
      if ("MozAnimation" in document.body.style) {
        selector = selector.replace(/\:(any)/g, ":-moz-$1");
      }
  }
  return originalQuerySelectorAll.call(document, selector)
}

var tagId = "abc";
var elems = document.querySelectorAll("[id^=" + tagId + "]:any(input, select, textarea)");
console.log(elems)
<input id="abc-1"> 
<select id="abc-2"></select>
<textarea id="abc-3"></textarea>
<div id="abc-4"></div>
<p id="abc-5"></p>

jsfiddle https://jsfiddle.net/0xpu1bvw/

Upvotes: 0

user663031
user663031

Reputation:

Use :any pseudo-class;

document.querySelectorAll('[id^=' + tagId + ']:-webkit-any(input, textarea, select)');

Right now, you will need to prefix this with -webkit- for Chrome and Safari, or -moz for Firefox; no IE support. This is likely to be standardized eventually under the name :matches. See MDN documentation here.

However, managing DOM elements via their IDs, as if they were named variables in some giant global namespace, is an anti-pattern. It is highly likely that there are better ways to find and keep track of elements than to assign a bunch of IDs over here in your code, then over there do getElementById or equivalent every time you turn around in order to find it again, much less doing the equivalent of a wildcard-like search.

Upvotes: 2

CupawnTae
CupawnTae

Reputation: 14610

Until the :any selector is standardized, you could use a helper function, similar to using a CSS preprocessor (borrowing @guest271314's example):

function makeSelector(tagId, elements) {
  return elements.map(function(el) {
    return el + '[id^=' + tagId + ']';
  }).join(',');
}

var selector = makeSelector('abc', ['input','select','textarea']);

console.log(document.querySelectorAll(selector));
<input id="abc-1"> 
<select id="abc-2"></select>
<textarea id="abc-3"></textarea>
<div id="abc-4"></div>
<p id="abc-5"></p>

You could then swap the helper implementation at a later date (e.g. for one using :any) without affecting functionality.

Upvotes: 0

guest271314
guest271314

Reputation: 1

You could use "[id^=" + tagId + "]" with :not(/* element */):not(/* element */)

var tagId = "abc";
var elems = document.querySelectorAll("[id^=" + tagId + "]:not(div):not(p)");
console.log(elems)
<input id="abc-1"> 
<select id="abc-2"></select>
<textarea id="abc-3"></textarea>
<div id="abc-4"></div>
<p id="abc-5"></p>

alternatively, you could add a className or a data-* attribute to input, select, textarea elements; the data-* attribute does not require a name or value; used only for selection filtering ; you could then utilize document.querySelectorAll("[id^=" + tagId + "][data-_]"); , or briefer document.querySelectorAll("[data-_]")

var tagId = "abc";
var elems = document.querySelectorAll("[id^=" + tagId + "][data-_]");
console.log(elems, elems[0].dataset)
<input id="abc-1" data-_> 
<select id="abc-2" data-_></select>
<textarea id="abc-3" data-_></textarea>
<div id="abc-4"></div>
<p id="abc-5"></p>

An approach using Array.prototype.filter(), though not as brief as using unique className or data-* attribute, similar to pattern described at OP

var tagId = "abc";
var elems = [].filter.call(document.querySelectorAll("[id^=" + tagId + "]")
            , function(el) {
                return /input|select|textarea/i.test(el.tagName)
            });
console.log(elems)
<input id="abc-1"> 
<select id="abc-2"></select>
<textarea id="abc-3"></textarea>
<div id="abc-4"></div>
<p id="abc-5"></p>

Upvotes: 0

Related Questions