Using only pure JavaScript, what is the most efficient way to select all DOM elements that have a certain data- attribute (let's say data-foo).
The elements may be different, for example:
<p data-foo="0"></p><br/><h6 data-foo="1"></h6>
You can use querySelectorAll:
document.querySelectorAll('[data-foo]');
document.querySelectorAll("[data-foo]")
will get you all elements with that attribute.
document.querySelectorAll("[data-foo='1']")
will only get you ones with a value of 1.
document.querySelectorAll('[data-foo]')
to get list of all elements having attribute data-foo
If you want to get element with data attribute which is having some specific value e.g
<div data-foo="1"></div>
<div data-foo="2"></div>
and I want to get div with data-foo set to "2"
document.querySelector('[data-foo="2"]')
But here comes the twist ... what if I want to match the data attirubte value with some variable's value? For example, if I want to get the elements where data-foo attribute is set to i
var i=2;
so you can dynamically select the element having specific data element using template literals
document.querySelector(`[data-foo="${i}"]`)
Note even if you don't write value in string it gets converted to string like if I write
<div data-foo=1></div>
and then inspect the element in Chrome developer tool the element will be shown as below
<div data-foo="1"></div>
You can also cross verify by writing below code in console
console.log(typeof document.querySelector(`[data-foo="${i}"]`).dataset('dataFoo'))
why I have written 'dataFoo' though the attribute is data-foo reason dataset properties are converted to camelCase properties
I have referred below links:
MDN: data-*
MDN: Using data attributes
Try it → here
<!DOCTYPE html>
<html>
<head></head>
<body>
<p data-foo="0"></p>
<h6 data-foo="1"></h6>
<script>
var a = document.querySelectorAll('[data-foo]');
for (var i in a) if (a.hasOwnProperty(i)) {
alert(a[i].getAttribute('data-foo'));
}
</script>
</body>
</html>
Here is an interesting solution: it uses the browsers CSS engine to to add a dummy property to elements matching the selector and then evaluates the computed style to find matched elements:
It does dynamically create a style rule [...] It then scans the whole document (using the
much decried and IE-specific but very fast document.all) and gets the
computed style for each of the elements. We then look for the foo
property on the resulting object and check whether it evaluates as
“bar”. For each element that matches, we add to an array.
Native JavaScript's querySelector and querySelectorAll methods can be used to target the element(s). Use a template string if your dataset value is a variable.
var str = "term";
var term = document.querySelectorAll(`[data-type=${str}]`);
console.log(term[0].textContent);
var details = document.querySelector('[data-type="details"]');
console.log(details.textContent);
<dl>
<dt data-type="term">Thing</dt>
<dd data-type="details">The most generic type.</dd>
</dl>
var matches = new Array();
var allDom = document.getElementsByTagName("*");
for(var i =0; i < allDom.length; i++){
var d = allDom[i];
if(d["data-foo"] !== undefined) {
matches.push(d);
}
}
Not sure who dinged me with a -1, but here's the proof.
http://jsfiddle.net/D798K/2/
While not as pretty as querySelectorAll (which has a litany of issues), here's a very flexible function that recurses the DOM and should work in most browsers (old and new). As long as the browser supports your condition (ie: data attributes), you should be able to retrieve the element.
To the curious: Don't bother testing this vs. QSA on jsPerf. Browsers like Opera 11 will cache the query and skew the results.
Code:
function recurseDOM(start, whitelist)
{
/*
* #start: Node - Specifies point of entry for recursion
* #whitelist: Object - Specifies permitted nodeTypes to collect
*/
var i = 0,
startIsNode = !!start && !!start.nodeType,
startHasChildNodes = !!start.childNodes && !!start.childNodes.length,
nodes, node, nodeHasChildNodes;
if(startIsNode && startHasChildNodes)
{
nodes = start.childNodes;
for(i;i<nodes.length;i++)
{
node = nodes[i];
nodeHasChildNodes = !!node.childNodes && !!node.childNodes.length;
if(!whitelist || whitelist[node.nodeType])
{
//condition here
if(!!node.dataset && !!node.dataset.foo)
{
//handle results here
}
if(nodeHasChildNodes)
{
recurseDOM(node, whitelist);
}
}
node = null;
nodeHasChildNodes = null;
}
}
}
You can then initiate it with the following:
recurseDOM(document.body, {"1": 1}); for speed, or just recurseDOM(document.body);
Example with your specification: http://jsbin.com/unajot/1/edit
Example with differing specification: http://jsbin.com/unajot/2/edit
Related
This is an example of working with attribute nodes. Why would I need to use attribute nodes when I am accessing the DOM in JavaScript
Learning how to setAtttribute,getAttribute,removeAttribute
Need to know why and when I use this ?
if(document.getElementById("plc").hasAttribute("class"))
penguin.setAttribute("class", "hidden");
var penguin = document.getElementById("plc");
alert(document.getElementById("plc").getAttribute("class"));
getAttribute, setAttribute, and removeAttribute are technically called methods, and they act on the DOM node by manipulating the attributes. The DOM node itself is what you've assigned to your variable penguin (but it should be assigned before you use it).
One common use case has to do with storing bits of data on a node. You can set that information and retrieve it later.
Say you have a couple penguins.
<div id='penguin-crowd'>
<div class='penguin' data-species='emperor'></div>
<div class='penguin' data-species='king'></div>
</div>
Now you want to list their species.
let penguins = document.getElementsByClassName('penguin');
for (let i = 0; i < penguins.length; i++) {
let species = penguin[i].getAttribute('data-species');
console.log(`This penguin's species is ${species}`);
}
Result:
// This penguin's species is emperor
// This penguin's species is king
Add another penguin to the crowd.
let penguinCrowd = document.getElementById('penguin-crowd');
let newPenguin = document.createElement('div');
newPenguin.classList.add('penguin');
newPenguin.setAttribute('data-species','emperor');
penguinCrowd.appendChild(newPenguin);
Now you have
<div id='penguin-crowd'>
<div class='penguin' data-species='emperor'></div>
<div class='penguin' data-species='king'></div>
<div class='penguin' data-species='emperor'></div>
</div>
removeAttribute removes the whole attribute from a node, so if for some reason that needs to happen, it's your go-to method.
setAttribute() for custom attributes you create ie node.className vs node.userName. className is a standard attribute vs userName is custom. For example, node.setAttribute('userName', 'someName') vs node.className = 'someName'.
getAttribute() gives you back attributes of the DOM vs properties like node.id. Most of the time the values are the same, but there are cases when they aren't. For example input's checked property: getAttribute() will retrieve checked/empty string, but input.checked will come back with true/false. There was more discussion on this in this question you can check out getAttribute() versus Element object properties?
removeAttribute() is simply to remove any attribute instead of setting it to null. In the official docs it says you should use removeAttribute() instead of setting the attribute value to null either directly or using setAttribute(). Many attributes will not behave as expected if you set them to null.
<body class="some-class xxx-term-i-want-to-extract some-other-class">
How do I extract "term-i-want-to-extract" from the body class, knowing that this always starts with "xxx-"?
Edit: The question is about getting "term-i-want-to-extract" and storing it inside a variable, for instance. Not about removing it from the body classes or returning the body classes without it. Thanks for your answers!
You can use the classList to get a list of all the classes the body tag has, and then use the $.map function to go over them and return only the relevant ones (after removed the xxx- string).
var classes = $.map($('body')[0].classList, function(cls, i) {
if (cls.indexOf('xxx-') === 0) {
return cls.replace('xxx-', '');
}
})
console.log(classes);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body class="some-class xxx-term-i-want-to-extract some-other-class">
Here's a JSfiddle
// get the jQuery element
var $body = $('body');
// get the contents of the `class` attribute
// and use String#split to divide it into an
// array of individual class strings
var classes = $body.attr('class').split(' ');
// Array#find is used to find the first class
// that starts with the selected pattern
// and then the pattern is sliced off of the
// full class string
var xxx = classes.find(function (className) {
return className.startsWith('xxx-');
}).slice(4);
xxx === 'term-i-want-to-extract'; // true
Array#find and String#startsWith are part of the ES2015 specification and therefore may not be available on all platforms. You may then need to use polyfills in older browsers like IE:
startsWith polyfill
find polyfill
I am new at JavaScript so I think my problem may be simple.
This works:
var convId = document.getElementById("wrapper");
convId.setAttribute("align","right");
But when I try to make it more specific:
var convId = document.getElementById("wrapper");
var convIdDl = convId.getElementsByTagName("dl");
convIdDl.setAttribute("align","right");
my definition list doesn't align to the right.
I have checked the HTML and CSS and everything is correct, but that shouldn't even matter
JavaScript overwrites them both.
The getElementsByTagName method returns a collection (to be more specific, a NodeList). You need to specify which element of that collection you want to use (just like you would when accessing an element in an array). Here I'm assuming you want the first:
convIdDl[0].setAttribute("align", "right");
As noted in the comments, you should definitely not be using the align attribute. CSS should be used in all cases.
The getElementsByTagName() function returns a collection of DOM elements, so you'll probably want to iterate through that and set the attribute on each element individually.
for(var i = 0; i < convIdDl.length; i++) {
convIdDl[i].setAttribute("align", "right");
}
hasClass is useful when I want to check if the element has some class. I want to do the same check for some other attribute. For example, I use
<div id="nav" data-available-for="users administrators guests"></div>
and want to check if this div is available for users. So, I would do it like
$('#nav').hasAttributeValue('data-available-for', 'users')
Are there any existing beautiful tiny solutions for this?
There is no need to explode strings or use indexOf. jQuery has a complete set of css-selectors build in and so you can use attribute-selectors.
you can combine .is() and attribute-selectors:
if( $('#nav').is('[data-available-for~="users"]') ) { ..
http://api.jquery.com/category/selectors/
http://api.jquery.com/attribute-contains-word-selector/
It's pretty straightforward:
$('#nav').attr('data-available-for').indexOf('users') != -1
You can make a jquery helper out of it.
I think you're looking for attr:
if ($('#nav').attr('data-available-for').split(' ').indexOf('users') != -1) {
// do stuff
}
Similarly, you can set attributes by passing a new value for the attribute as the second argument:
$('#nav').attr('data-available-for', 'users'); // data-available-for="users"
You can take advantage of CSS Selector:
if ($("#nav[data-available-for~=user]").length) {
// is available for 'user'
}
Or if you prefer have the element not mixed with the condition:
if ($("#nav").filter("[data-available-for~=user]").length) {
// is available for 'user'
}
Of course you can build a function on top of it that makes it more readable for you.
I would try to use some native javascript before falling back on a library.
<div id='strawberry-plant' data-fruit='12'></div>
<script>
var plant = document.getElementById('strawberry-plant');
//'Getting' data-attributes using getAttribute
var fruitCount = plant.getAttribute('data-fruit'); // fruitCount = '12'
// 'Setting' data-attributes using setAttribute
plant.setAttribute('data-fruit','7'); // Pesky birds
</script>
Where it's implemented you can use the dataset attribute on the serialized DOM object
<div id='sunflower' data-leaves='47' data-plant-height='2.4m'></div>
<script>
// 'Getting' data-attributes using dataset
var plant = document.getElementById('sunflower');
var leaves = plant.dataset.leaves; // leaves = 47;
// 'Setting' data-attributes using dataset
var tallness = plant.dataset.plantHeight; // 'plant-height' -> 'plantHeight'
plant.dataset.plantHeight = '3.6m'; // Cracking fertiliser
</script>
additionally you can use some really powerful selecting with querySelectorAll like this
<script>
// Select all elements with a 'data-flowering' attribute
document.querySelectorAll('[data-flowering]');
// Select all elements with red leaves
document.querySelectorAll('[data-foliage-colour="red"]');
</script>
querySelectorAll() is supported by almost 90% of people online according to caniuse.com
http://caniuse.com/queryselector
and that is just the start. check out the full power of css3 query selectors here:
http://www.w3.org/TR/selectors/#selectors
I'd like to get all <input /> elements with the placeholder property set, how could I do this without jquery? it should support IE6+, and other popular browsers.
Assuming all the fields have an ID
DEMO
var fieldsWithPlaceholder = [];
var inputs = document.getElementsByTagName("input");
for (var i=0, n=inputs.length;i<n;i++) {
if (inputs[i].getAttribute("placeholder") !=null) {
fieldsWithPlaceholder.push(inputs[i].id);
}
}
alert(fieldsWithPlaceholder);
Update: you can as posted elsewhere test for null, undefined or blank in one go:
if (inputs[i].getAttribute("placeholder")) { // there is something there
Use getElementsByTagName, filter for those with a placeholder property value that isn't undefined.
Incidentally, there is a big difference between attributes and properties, which are you really after? If you are setting an attribute called placeholder in the HTML, then you should use getAttribute. However, if you are setting the property of a DOM element (which is what I presumed from your post) then you should use the property directly. Hopefully you aren't mixing the two.
In some browsers, attributes and properties are kept synchronised but in others they aren't. For example, Firefox will not create element properties for non–standard attributes (i.e. those that aren't specified in the related markup standard), nor will it create or modify standard attributes based on property changes in versions that aren't consistent with HTML5.
get all the elements of type input first
document.getElementsByTagName("input")
loop over the above list and then use the
getAttribute on each of the element as follows getAttribute("placeholder") if it is not null or undefined then the input has it set.
Try something like this:
var myEls = [],
allEls = document.getElementsByTagName('input');
for (var i=0; i < allEls.length; i++) {
if (allEls[i].placeholder)
myEls.push(allEls[i]);
}