1

What's the best way to select elements where any attribute contains a specific string?

I know the following works if I know the attribute name:

// Select any element where data-test attribute contains rbl-value
var items = $("*[data-test*='rbl-value']");

But I could have rbl-value inside any attribute and I want to be able to process those elements/attributes.

<div class="rbl-value:itemClass">
   <p>Don't process me...</p>
   <p data-inputname="rbl-value:inputName">data-inputname should be processed.</p>
</div>

Assuming there is clever solution, how performant is it?

Terry
  • 2,148
  • 2
  • 32
  • 53
  • 1
    There is no wildcard selector for partial attribute names . See [How to select value by attribute name starts with](https://stackoverflow.com/questions/26657398/jquery-how-to-select-value-by-attribute-name-starts-with). It won't be great performance if you can't narrow down a container , class or tagname also otherwise you would need to go through the whole DOM and checking every element's attributes – charlietfl May 01 '21 at 22:35
  • I was afraid of that. – Terry May 01 '21 at 23:03
  • There's no *"any attribute's value"* selector. So you're out of luck. If you don't know you selectors - seems like there were no systematization while building that app. Or it's an *[XY problem](https://en.wikipedia.org/wiki/XY_problem)*. – Roko C. Buljan May 01 '21 at 23:37
  • Also, what means *"process those elements/attributes"* ? – Roko C. Buljan May 01 '21 at 23:44
  • Think of something like angular/react/vue where they can sprinkle something like {{expression}} inside attributes. I assume those are a fixed list of element/attribute combinations? – Terry May 01 '21 at 23:47
  • The difference with those framewoks is they know the attribute names and the DOM selector engine recognizes full attribute names. What is your higher level uses case? And how much of this querying do you need to do? – charlietfl May 01 '21 at 23:54

2 Answers2

1
  • Loop (expensively) all your elements using the "*" selector or rather "body *" (as suggested by @cars10m) to skip the head children.
  • Use Element.attributes and its .name and .value properties:

const ELS_all = document.querySelectorAll("*");

ELS_all.forEach(EL => {
  [...EL.attributes].forEach((a) => {
    if (/rbl-value:/.test(a.value)) {
      console.log(a.name, a.value);
    }
  })
});
<div class="rbl-value:itemClass">
   <p>Don't process me...</p>
   <p data-inputname="rbl-value:inputName">data-inputname should be processed.</p>
</div>

Then to extract the specific rbl-value:<THIS STUFF HERE> you could use this small Regular Expression:

/(?<=rbl-value:)\w+\b/g

const a = { // Just to imitate the returned El.attributes entry object 
  value: "asd123 ghjkasd rbl-value:inputName hjlqwezuo rbl-value:something asd"
};

const rblValues = a.value.match(/(?<=rbl-value:)\w+\b/g);
console.log(rblValues)
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • 1
    Very good!! If you are not interested in the header, style and script sections at the top of the document you could change the first selector to `"body *"`. – Carsten Massmann May 02 '21 at 07:31
0

OK, since "all attributes" includes not only all attributes starting with "data-" but all other "possible" attributes a DOM-element might have, the solution is not that simple.

What about concentrating on "the usual suspects"? I. e. what about looking at a certain list of attributes for each element?

Maybe the following could be a starting point?

$("body *").not("script").each(function(){
  console.log(JSON.stringify(Object.entries({tag:this.tagName,dataset:this.dataset,id:this.id,name:this.name,href:this.href,class:this.className})));
});
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<div class="rbl-value:itemClass">
   <p>Don't process me...</p>
   <p data-inputname="rbl-value:inputName">data-inputname should be processed.</p>
</div>

Obviously, this needs further work and is not a "complete answer". But maybe this is a step in the right direction?

Carsten Massmann
  • 26,510
  • 2
  • 22
  • 43