1

While converting some of my existing project from jQuery to plain JS, I've run into some issues with jQuery's .data() utility. So far, I've been unable to find a plain JS approach that will enable me to get and check all of the data-attributes in the navGlobal object.

var navGlobal = document.querySelector('.nav-global');

if (navGlobal !== null &&
    navGlobal.data() &&
    navGlobal.data().account &&
    navGlobal.data().account.accountData &&
    navGlobal.data().account.accountData.address &&
    navGlobal.data().account.accountData.address.zip) {

    // do something

}

What I've tried:

Initially, I thought I'd check for the existence of these data attributes with .getAttribute() and .hasAttribute(). But this only added to the confusion - I'm not sure how I would use vanilla JS to drill down to the nested properties (i.e., navGlobal.data().account.accountData.address). Chaining .getAttribute() doesn't really work, as most of these data attributes are attached to child elements of navGlobal, and not the navGlobal element itself.

What is the best approach?

asw1984
  • 713
  • 1
  • 7
  • 17
  • 3
    Possible duplicate of [how would i implement something like jquerys .data() in pure javascript?](https://stackoverflow.com/questions/19031317/how-would-i-implement-something-like-jquerys-data-in-pure-javascript) – Rob Aug 08 '17 at 20:23
  • I agree that the questions are similar, but the example given by the answerer is contrived and also doesn't address the scenario where there are nested properties. – asw1984 Aug 08 '17 at 20:32
  • Search on SO and you'll find a few more same questions asked and see if they apply. – Rob Aug 08 '17 at 21:07
  • 1
    What you've discovered is that `$().data()` doesn't only retrieve `data-attributes` and never set it. Instead, it builds a dictionnary and use the jquery element you called `data()` on to store the passed value. This allows to store any type of value, while data-attributes can only store DOMStrings (hence no Objects). To implement it yourself, you'd have to create your own map. Or if you're lazy, you can also simply attach these values to some arbitrary DOMElement custom property (e.g `elem._myOwn_data.account = {acccountData: { address: { zip: 'someVal' } } }`). – Kaiido Aug 09 '17 at 00:56
  • More info : https://stackoverflow.com/questions/5821520/where-is-jquery-data-stored – Kaiido Aug 09 '17 at 01:00

3 Answers3

2

jQuery's data function does two distinctly different things: it provides an interface for accessing data-attributes, and it provides an interface for associating data with a DOM node after the page has loaded. Regarding the former use-case:

Collecting data attributes recursively would look something like this.

var navGlobal = document.getElementsByClassName('nav-global')[0];
var data = Object.assign({}, navGlobal.dataset);
var children = navGlobal.getElementsByTagName("*");

Array.from(children).forEach(function (el) {
  Object.assign(data, el.dataset)
})

console.log(data)
<div class="nav-global" data-foo="bar">
  <div data-bar="baz">
    <div data-baz="quux"></div>
  </div>
</div>

Can't be more specific without seeing more of your code, but I would recommend having a better way of selecting elements with data such as a class name.

If you need nested data structures, and you need to implement them as data attributes, you'll have to do some extra work to make that happen.

If you look at the source for my old jQuery plugin, you'll see a simple way of taking a flat data structure and turning it into a nested one, and vice versa. This is roughly analogous to how PHP & Ruby encode nested parameters in the query string.

{
  "account[accountData][address][zip]": "12345"
}

becomes

{
  "account": {
    "accountData": {
      "address": {
        "zip": "1234"
      }
    }
  }
}

My library was intended for use with form inputs, but you could adapt the same principles to data attributes.

Though to be frank, I don't see why you don't just drop a JSON payload on the page instead.

Adam Lassek
  • 35,156
  • 14
  • 91
  • 107
  • `HTMLElement.dataset` returns a map of DOMStrings, according to their code, they want to store Objects, which isn't possible through `dataset`. – Kaiido Aug 09 '17 at 00:49
  • @Kaiido this is true, though nested structures could be supported in a similar fashion to how nested query parameters are done. I've updated with some more information. – Adam Lassek Aug 09 '17 at 01:24
  • What's the advantage of your lib over `window.JSON` API ? You are not storing objects, but stringifications of these objects. How are you gonna store a live node ? – Kaiido Aug 09 '17 at 01:27
  • Well, like I said in the edit I think just dropping a JSON payload on the page is the easiest solution here. The purpose of my lib was to take an HTML form and turn it into a nested object in JavaScript, consistent with the way it would be parsed on the backend. Sort of one-half of a data-binding solution. – Adam Lassek Aug 09 '17 at 01:38
  • The question as I am reading it is talking about 1. encoding nested data structures as data attributes, and 2. pulling data from a parent node and all children. I don't see a requirement for this data to be mutable. – Adam Lassek Aug 09 '17 at 01:45
  • The question is asking how to implement [jQuery's `.data` method](https://api.jquery.com/data/) which allows to store mutable objects. – Kaiido Aug 09 '17 at 01:49
  • That is what the question is asking if you don't read past the title. I am considering the substance of the question as well. – Adam Lassek Aug 09 '17 at 01:50
  • But this substance is led by misconception. OP probably got confused by the fact that `.data()` is *also* able to **retrieve** data-attributes, while what they are looking for has nothing to do with data-attributes. – Kaiido Aug 09 '17 at 01:52
  • That is how _you_ are interpreting the question, I see nothing @asw1984 has written to indicate that. – Adam Lassek Aug 09 '17 at 01:53
  • Perhaps you are correct, but we don't have enough information. – Adam Lassek Aug 09 '17 at 01:54
  • As you mentioned initially, your solution doesn't account for the nested data structures, but it's a step in the right direction. jQuery's .data() is doing a lot here, to where I'm now leaning toward keeping it's implementation. Unfortunately, most (if not all) of the vanilla JS alternatives I've come across (including in this post) aren't going to jive with IE 9 or 10, which I still have to support. Thanks for your suggestion! – asw1984 Aug 09 '17 at 16:56
  • @asw1984 to clarify, it _does_ account for nested data-attributes of child nodes, what it does not account for is additional data that is added by JS after pageload. If you have nested data structures attached to the DOM via `data()`, it would probably be coming from that is my guess. – Adam Lassek Aug 09 '17 at 20:55
  • @asw1984 to be honest, if you need deeply nested data structures that tie to specific DOM elements, you're much better off approaching this as a data-bound component of some kind. – Adam Lassek Aug 09 '17 at 21:03
1

As antony said, use the DOMElement.dataset object:

var div = document.querySelector('div');
var p = document.querySelector('p');

p.innerHTML = div.dataset.myattribute;
<div data-myattribute="3"></div>
<p></p><!-- Output element -->
clabe45
  • 2,354
  • 16
  • 27
0

Try using navGlobal.dataset instead of navGlobal.data().

Also if you want to check for the existence of nested properties, you could use a lodash has function that allows you to check for nested properties. E.g.

if (has(navGlobal.dataset, 'account.accountData.address.zip')) {
  ....
}
antony
  • 2,763
  • 19
  • 23
  • Thanks. This would be the ideal solution if using Lodash were an option. Without it, .dataset doesn't return data for child elements, like .data() does. – asw1984 Aug 08 '17 at 22:30