Given an arbitrary HTML element with zero or more data-* attributes, how can one retrieve a list of key-value pairs for the data.
E.g. given this:
<div id='prod' data-id='10' data-cat='toy' data-cid='42'>blah</div>
I would like to be able to programmatically retrieve this:
{ "id":10, "cat":"toy", "cid":42 }
Using jQuery (v1.4.3), accessing the individual bits of data using $.data() is simple if the keys are known in advance, but it is not obvious how one can do so with arbitrary sets of data.
I'm looking for a 'simple' jQuery solution if one exists, but would not mind a lower level approach otherwise. I had a go at trying to to parse $('#prod').attributes but my lack of javascript-fu is letting me down.
update
customdata does what I need. However, including a jQuery plugin just for a fraction of its functionality seemed like an overkill.
Eyeballing the source helped me fix my own code (and improved my javascript-fu).
Here's the solution I came up with:
function getDataAttributes(node) {
var d = {},
re_dataAttr = /^data\-(.+)$/;
$.each(node.get(0).attributes, function(index, attr) {
if (re_dataAttr.test(attr.nodeName)) {
var key = attr.nodeName.match(re_dataAttr)[1];
d[key] = attr.nodeValue;
}
});
return d;
}
update 2
As demonstrated in the accepted answer, the solution is trivial with jQuery (>=1.4.4). $('#prod').data() would return the required data dict.
Actually, if you're working with jQuery, as of version 1.4.3 1.4.4 (because of the bug as mentioned in the comments below), data-* attributes are supported through .data():
As of jQuery 1.4.3 HTML 5 data-
attributes will be automatically
pulled in to jQuery's data object.
Note that strings are left intact
while JavaScript values are converted
to their associated value (this
includes booleans, numbers, objects,
arrays, and null). The data-
attributes are pulled in the first
time the data property is accessed and
then are no longer accessed or mutated
(all data values are then stored
internally in jQuery).
The jQuery.fn.data function will return all of the data- attribute inside an object as key-value pairs, with the key being the part of the attribute name after data- and the value being the value of that attribute after being converted following the rules stated above.
I've also created a simple demo if that doesn't convince you: http://jsfiddle.net/yijiang/WVfSg/
A pure JavaScript solution ought to be offered as well, as the solution is not difficult:
var a = [].filter.call(el.attributes, function(at) { return /^data-/.test(at.name); });
This gives an array of attribute objects, which have name and value properties:
if (a.length) {
var firstAttributeName = a[0].name;
var firstAttributeValue = a[0].value;
}
Edit: To take it a step further, you can get a dictionary by iterating the attributes and populating a data object:
var data = {};
[].forEach.call(el.attributes, function(attr) {
if (/^data-/.test(attr.name)) {
var camelCaseName = attr.name.substr(5).replace(/-(.)/g, function ($0, $1) {
return $1.toUpperCase();
});
data[camelCaseName] = attr.value;
}
});
You could then access the value of, for example, data-my-value="2" as data.myValue;
jsfiddle.net/3KFYf/33
Edit: If you wanted to set data attributes on your element programmatically from an object, you could:
Object.keys(data).forEach(function(key) {
var attrName = "data-" + key.replace(/[A-Z]/g, function($0) {
return "-" + $0.toLowerCase();
});
el.setAttribute(attrName, data[key]);
});
jsfiddle.net/3KFYf/34
EDIT: If you are using babel or TypeScript, or coding only for es6 browsers, this is a nice place to use es6 arrow functions, and shorten the code a bit:
var a = [].filter.call(el.attributes, at => /^data-/.test(at.name));
Have a look here:
If the browser also supports the HTML5 JavaScript API, you should be able to get the data with:
var attributes = element.dataset
or
var cat = element.dataset.cat
Oh, but I also read:
Unfortunately, the new dataset property has not yet been implemented in any browser, so in the meantime it’s best to use getAttribute and setAttribute as demonstrated earlier.
It is from May 2010.
If you use jQuery anyway, you might want to have a look at the customdata plugin. I have no experience with it though.
As mentioned above modern browsers have the The HTMLElement.dataset API.
That API gives you a DOMStringMap, and you can retrieve the list of data-* attributes simply doing:
var dataset = el.dataset; // as you asked in the question
you can also retrieve a array with the data- property's key names like
var data = Object.keys(el.dataset);
or map its values by
Object.keys(el.dataset).map(function(key){ return el.dataset[key];});
// or the ES6 way: Object.keys(el.dataset).map(key=>{ return el.dataset[key];});
and like this you can iterate those and use them without the need of filtering between all attributes of the element like we needed to do before.
You should be get the data through the dataset attributes
var data = element.dataset;
dataset is useful tool for get data-attribute
or convert gilly3's excellent answer to a jQuery method:
$.fn.info = function () {
var data = {};
[].forEach.call(this.get(0).attributes, function (attr) {
if (/^data-/.test(attr.name)) {
var camelCaseName = attr.name.substr(5).replace(/-(.)/g, function ($0, $1) {
return $1.toUpperCase();
});
data[camelCaseName] = attr.value;
}
});
return data;
}
Using: $('.foo').info();
You can just iterate over the data attributes like any other object to get keys and values, here's how to do it with $.each:
$.each($('#myEl').data(), function(key, value) {
console.log(key);
console.log(value);
});
I use nested each - for me this is the easiest solution (Easy to control/change "what you do with the values - in my example output data-attributes as ul-list) (Jquery Code)
var model = $(".model");
var ul = $("<ul>").appendTo("body");
$(model).each(function(index, item) {
ul.append($(document.createElement("li")).text($(this).text()));
$.each($(this).data(), function(key, value) {
ul.append($(document.createElement("strong")).text(key + ": " + value));
ul.append($(document.createElement("br")));
}); //inner each
ul.append($(document.createElement("hr")));
}); // outer each
/*print html*/
var htmlString = $("ul").html();
$("code").text(htmlString);
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/prism.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/themes/prism-okaidia.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1 id="demo"></h1>
<ul>
<li class="model" data-price="45$" data-location="Italy" data-id="1234">Model 1</li>
<li class="model" data-price="75$" data-location="Israel" data-id="4321">Model 2</li>
<li class="model" data-price="99$" data-location="France" data-id="1212">Model 3</li>
</ul>
<pre>
<code class="language-html">
</code>
</pre>
<h2>Generate list by code</h2>
<br>
Codepen: https://codepen.io/ezra_siton/pen/GRgRwNw?editors=1111
One way of finding all data attributes is using element.attributes. Using .attributes, you can loop through all of the element attributes, filtering out the items which include the string "data-".
let element = document.getElementById("element");
function getDataAttributes(element){
let elementAttributes = {},
i = 0;
while(i < element.attributes.length){
if(element.attributes[i].name.includes("data-")){
elementAttributes[element.attributes[i].name] = element.attributes[i].value
}
i++;
}
return elementAttributes;
}
If you know the name of keys you can also use object destructuring to get values like this
const {id, cat, cid } = document.getElementById('prod').dataset;
You can also skip keys you don't need and get the ones you need like this
const { cid, id } = document.getElementById('prod').dataset;
100% Javascript no jQuery ;)
DOMStringMap :
console.log(document.getElementById('target-element-id').dataset);
or custom variable :
var data = {};
Object.entries(document.getElementById('target-element-id').dataset).forEach(([key, val]) => {
data[key] = val;
});
console.log(data);
Related
I'm trying to convert a function writing on jquery to javascript vanilla
i know that .each() is .forEach() on pure javascript but i dont understand what i'm missing with my code !
here the jquery code :
addClickItems: function(classe) {
$(classe).each(function (index) {
$(classe + ":eq(" + (index) + ")").click(function () {
if (classe === ".droite") {
});
});
},
and here the javascript code :
clickImages : function (classe) {
//classe = new Object(diaporama);
Object.keys(classe).forEach(index => {
classe[index].addEventListener("click", function () {
});
});
},
thanks for help !
The jQuery.each() method enumerates all the individual DOM elements that are contained within the jQuery wrapped set of elements that you call it on:
$("div").each(function(index, value){
console.log(index, value);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="one">one</div>
<div id="two">two</div>
<div id="three">three</div>
Your attempt at conversion goes to using Object.keys(class).forEach, where Object.keys is not a set of elements, but keys/properties of an Object.
var myObj = {
key1: 10,
key2: true,
key3: "foo"
};
Object.keys(myObj).forEach(function(key, index){
console.log(index, key, myObj[key]);
});
So, the two uses of each are not analogous.
If you do indeed have DOM elements to enumerate, you need to get them into a JavaScript array and then you can call .forEach() on that array.
NOTE 1: Most modern browsers allow you to call .forEach on node lists/HTML Collections directly, but for compatibility with those browsers that do not, you need to convert the node list/HTML Collection into an array to be sure that the code will work. This is shown below.
NOTE 2: Be mindful that the callback function you pass to jQuery.forEach() is, itself, passed two arguments: index and value, where for the vanilla JavaScript Array.forEach(), the callback is passed value and index (reversed order).
var elements = document.querySelectorAll("div"); // Get all the elements into a node list
var elArray = Array.prototype.slice.call(elements); // Convert node list to array
// Now, enumerate the array with .forEach()
elArray.forEach(function(value, index){
console.log(index, value);
});
<div id="one">one</div>
<div id="two">two</div>
<div id="three">three</div>
In the original code classe appears to be a jQuery selector. You need to use document.querySelectorAll() to search for all the matching elements and iterate over that:
document.querySelectorAll(classe).forEach(elt => elt.addEventListener("click", function() {
...
});
I have a series of blog posts that have an array of tags as a data-tags html attribute such as ["foo", "bar", "baz"]. I'm looking to query the DOM elements that have foo included in the data-tags array.
Example markup:
<li data-tags="['foo', 'bar', 'baz']">...</li>
I know it's possible to query if the data attributes were stored as a singular value, ie:
document.querySelectorAll('[data-specifc-tag="foo"]');
Is it even possible to select elements which arrays include a specific value?
(Vanilla JS only, no jQuery please)
To sum up, the query you need is
document.querySelectorAll('li[data-tags*=\'"foo"\']')
But you have to make sure that each element in your html array is enlosed within double quotes. You may change it to a single quote, but make sure to update the query.
You can search for multiple queries by adding more rules as follows
document.querySelectorAll('li[data-tags*=\'"foo"\']'+'[data-tags*=\'"bar"\']')
Below is a snippet that applies those queries based on some value you may be interested in. I made sure to use the same html structure you put in your question.
Edit:
I added one more function, queryAll, that allows for value queries. So, you can search for elements that must have more than one value.
function entry(search){
var string = '"' + search + '"';
return '[data-tags*=\'' + string + '\']';
}
function queryAll() {
var queries = Array.prototype.slice.call(arguments).map(function(a){
return '[data-tags*=\'' + a + '\']';
});
//example: document.querySelectorAll('li[data-tags*=\'"foo"\']');
return document.querySelectorAll('li'+ queries.join(''));
}
function query(search) {
var string = '"' + search + '"';
//example: document.querySelectorAll('li[data-tags*=\'"foo"\']');
return document.querySelectorAll('li[data-tags*=\'' + string + '\']');
}
query("foo1"); // One li
query("foo"); //Two li's
queryAll("foo1", "bar1"); //one li
queryAll("foo1", "bar1Not"); //nothing
<ul>
<li data-tags='["foo", "bar", "baz"]'>...</li>
<li data-tags='["foo1", "bar1", "baz1"]'>...</li>
<li data-tags='["foo2", "bar2", "baz2"]'>...</li>
<li data-tags='["foo", "bar2", "baz2"]'>...</li>
</ul>
Here's a vanilla js solution to your problem:
const posts = document.querySelectorAll('[data-tags]');
const fooPosts = [];
posts.forEach(post => {
if (/foo/.test(post.getAttribute('data-tags'))) {
fooPosts.push(post);
}
});
// output the filtered posts
console.log(fooPosts);
One liner alternative:
document.querySelectorAll('[data-tags]').forEach(post => /foo/.test(post.getAttribute('data-tags')) && console.log(post));
Or splitted:
document.querySelectorAll('[data-tags]').forEach(post =>
/foo/.test(post.getAttribute('data-tags')) && console.log(post));
Please, note that, as mentioned in the comments, your html markup is invalid. Data attributes should not contain complex data structure like arrays or objects. As suggested, consider performing a .join() on your array data and outputting it in the attribute [data-tags] as a string. In it each value can be separated by comma for readability.
Using the valid markup your solution will be slightly different:
const posts = document.querySelectorAll('[data-tags]');
const fooPosts = [];
posts.forEach(post => {
if (post.getAttribute('data-tags').indexOf('foo') > -1) {
fooPosts.push(post);
}
});
console.log(fooPosts);
The above code is also faster as it's using .indexOf() to filter the DOM nodes.
Here's the above code in a reusable function:
const filterNodesByAttr = (attr, tag) => {
let filtered = [];
const items = document.querySelectorAll(`[${attr}]`);
items.forEach(item => {
if (item.getAttribute(attr).indexOf(tag) > -1) {
filtered.push(item);
}
});
return filtered;
};
const fooPosts = filterNodesByAttr('data-tags', 'foo');
console.log(fooPosts);
You can use CSS to get any element on the page.
I.e.
Get elements with href's [href]
Get divs with data-type of price div[data-type=price]
var attributes = ['data-rel','type'];
attributes.forEach((at)=>{
document.querySelectorAll('['+at+']').forEach((element)=>{
element.append('<b>'+at+'</b>');
});
});
<p>Hello</p>
<p data-rel="title">World!</p>
<p type="emoji">:-)</p>
I'm thinking for develop a web component form, so... I was wondering my self if it's possible read attributes from a html elements. For example:
<my-component input="3" data-input='"name", "address", "email"' textarea="1" data-textarea='"mensaje"'></my-component>
<script>
while i <= elementattributes.length{
elementattributes[i]
}
</script>
I know that I can access through .getAttribute, but this way will take more code... I just want to be something smart :P
Sorry, my english is poor... I hope you can understand what I want to do :P
:)
If you want to get all of the attributes and their values, you can do something like this:
function getElementAttrs(el) {
var attributes = [].slice.call(el.attributes);
return attributes.map(function(attr) {
return {
name: attr.name,
value: attr.value
}
});
}
var allAttrs = getElementAttrs(document.querySelector('my-component'));
console.log(allAttrs);
<my-component input="3" data-input='name,address,email' textarea="1" data-textarea='"mensaje"'></my-component>
The function getElementAttrs returns an array of objects to you with attribute name-value pairs as keys on the object so that you can loop over it, or just pull by key.
You can use dataset to get specific data attribute
var data = document.querySelector('my-component').dataset.input.split(',');
data.forEach(function(e) {
console.log(e)
})
<my-component input="3" data-input='name,address,email' textarea="1" data-textarea='"mensaje"'></my-component>
If you want to return all attributes from element you can use attributes which return array-like object, but you can create object with name-value from that
var data = document.querySelector('my-component').attributes, attr = {};
Array.from(data).forEach(function(e) {
attr[e.name] = e.value;
});
console.log(attr['data-input']);
<my-component input="3" data-input='name,address,email' textarea="1" data-textarea='mensaje'></my-component>
You can just use .attributes:
document.getElementById("ELEMENT_ID").attributes
This will return an object containing each attribute with its associated value. You can then index the attributes array for specific attributes and use .value for it's corresponding value.
still quite new to jQuery / Javascript and am struggling a little.
I have created an attribute called catno that I have assigned to several links. Then I want all instances inside banner-1 to be called into another link separated by a comma. i.e
New Link
I'm just struggling in making this happen. The code I have right now only returns the first catno.
Any help would be great!
JSFIDDLE
<div id="banner-1>
New Link
</div>
$sd('.buy-outfit').attr('href', function() {
return $sd("#banner-1 .add-catno").attr('catno') + ',';
});
Fiddle Demo
var arr_catno = $("#banner-1 a").map(function () { //create array using .map
return $(this).attr('catno'); // add element attribute catno to array
}).get().join(); //use .join() to get comma separated string of array
$('.new-link').attr('href', arr_catno); //assign it to href
.map()
Updated after OP's Comment
Fiddle Demo
$('[id^=banner-] .new-link').attr('href', function () {
return $(this).parent().find('a').map(function () {
return $(this).attr('catno');
}).get().join();
});
Attribute Starts With Selector [name^="value"]
.parent()
.find()
[id^=banner-] --> select all the elements with id starting with banner-
Use $.map to produce an array of catnos and them join them into a string.
$('.new-link').attr('href', function() {
return $.map($(".add-catno"), function (el) {
return $(el).data('catno');
}).join(',');
});
Note in the example that I've changed the catnos attributes to data attributes. Using data is considered good practice for when you want to add arbitrary data to your HTML.
Fiddle
I have following object:
$("input:checkbox:checked")
[
<input class="li_checker" type="checkbox" category_uid="1.3" category_language="da">,
<input class="li_checker" type="checkbox" category_uid="1.3.1" category_language="da">
]
If there is any helper in jQuery which allows me to get value of "category_uid" for all elements and returns it as the another array? Expected result:
["1.3", "1.3.1"]
Use map():
var myArray = $("input:checkbox:checked").map(function(){
return this.getAttribute("category_uid");
}).get();
As bpierre suggested, use .map(). His answer is correct.
If you need this behavior for different attributes, you might as well write is as a reusable function (“jQuery plugin”):
jQuery.fn.pluck = function(attr) {
return this.map(function() {
return this.getAttribute(attr);
}).get();
};
$('input:checkbox:checked').pluck('category_uid'); // ["1.3", "1.3.1"]
P.S. category_uid is not a valid attribute in HTML. Consider using custom data-* attributes instead, e.g. data-category-uid="foo".
Just for the fun of it, a third way, this one using attr:
var categories = [];
$("input:checkbox:checked").attr("category_uid", function(index, value) {
categories.push(value);
});
Live example
Off-topic: If you want to have arbitrary, custom attributes on HTML elements, recommend using the data- prefix defined by HTML5 (details). You can use it now, even if you're not using the HTML5 doctype (this is one of the places where HTML5 is just codifying — and reining in — current practice), and it future-proofs a bit.
var myarray=[];
$("input:checkbox:checked").each(function () {
myarray.push($(this).attr('category_uid'));
});
Live Demo
Something like
var checked_cats = new Array();
$("input:checkbox:checked").each(function() {
checked_cats.push($(this).attr('category_uid'));
});
(not tested)
p.s. saw your tweet.
Here's a more "pluginy" way to do this:
(function($){
$.fn.getAttributes = function(attribute){
var result = [];
$(this).each(function(){
var a = $(this).attr(attribute);
if(a)
result.push(a);
});
return result;
}
})(jQuery);
and then use it as follows :
var result = $("input:checkbox:checked").getAttributes("category_uid");
Haven't tested it, but it should work just fine.