an alternative to hiding data in divs? - javascript

All too often I find myself storing object data in hidden dom elements. I was curious if there was a way to attach this data to the dom node itself. When I try to create attributes 'on the fly', it doesn't seem to work. It would be much easier to access the property with this.something instead of accessing the html contained in a child. I feel like I should know how to do this, but I don't. Thanks.

There absolutely is! jQuery's .data().
$('#someId').data('myData', someValue); // To store the data
$('#someId').data('myData'); // To retrieve it again
Any JavaScript variable can be stored as data - it's not limited to strings.
Note that this doesn't actually attach data to the DOM node as you say (which should be avoided). jQuery keeps its own cache of all the data you store and the DOM nodes you wanted to attach it to. So, it's not the same as domNode.myData = someValue.

jQuery.data is the preferred way of doing this.

Related

Storing data to DOM - Element value vs data attribute

To store values in a DOM element, we can do it through data attribute
$("#abc").data("item", 1), to retrieve do $("#abc").data("item")
But today I learned that we can do it this way also:
$("#abc")[0].item = 1, to retrive do $("#abc)[0].item
What are the differences between them?
Which one is better? Which one gets a wider compatibility?
.data() exists for a couple reasons:
Some (mostly older) browsers had memory leak issues if you put a JS object into a property on a DOM object. This created a reference between the DOM and JS world (which have separate garbage collectors) which caused problems and could result in memory leaks. Keep references entirely in the JS world by using .data() instead of a DOM property solved that issue. I don't honestly know how much of an issue this still is in modern browsers. Hard to test, easier to just use the known-safe approach.
Historically, some host objects did not support arbitrary property addition with direct property syntax such as obj.prop = 1;. .data() made it so you could associate data with any object regardless of whether it had the ability to handle any arbitrary property.
Name collisions. .data() creates one and only one custom property on a DOM object which is just an id value (a string). You are then free to use any keys you want with .data() with zero worry about conflicting with a pre-existing property name on a DOM object. .data() is essentially it's own name space for custom properties.
Reading HTML5 "data-xxx" attributes. When you read a .data("xxx") property that has not yet been written to the actual jQuery data store, jQuery will read a "data-xxx" attribute on the DOM object. If it finds that attribute, it returns that value and actually coerces its type too so that "false" gets turned into the Javascript false. If you then write .data("xxx", "foo"), the value is not overwritten onto the DOM object, but is written to the jQuery storage and from then on all future reads writes are from the jQuery .data() store. One reason this is useful is that custom attributes (which are different than custom properties) can only be strings, but .data("xxx", yyy) can write and store any JS data type.
So, if you want to use a known-safe method that is not prone to memory leaks, even in older browsers, use .data() rather than making your own custom property on a DOM object.
I suspect it's possible that at some future time, browsers will be considered safe enough that you can store JS object references in custom DOM properties and not have to worry about memory leaks at which time there may be less reasons to use something like .data() - though issue #3 above will still exist.
There are some disadvantages to using .data().
If you store meaningful amounts of data in .data() and then you remove the corresponding DOM object without using jQuery's methods to remove it (such as you use .removeChild() directly or you just set .innerHTML on a parent), the data stored in the .data() store will be orphaned and never cleaned up because jQuery will not know the corresponding DOM object has been removed. This will result in some data in your javascript being kept in the data structure that you won't ever be using. While this isn't technically a leak (as the data is still there for use), it has much the same effect of wasting some memory. If you use .data(), you should only use jQuery methods for removing or replacing DOM objects because they prevent the wasted memory.
Because of the above issue, when you are using jQuery's methods that can result in the removal of DOM objects, jQuery has to do extra work to make sure .data() is cleaned up when using its own methods. This can slow down the performance of .html("xxx"), .remove(), etc...

caching object in DOM vs javascript variable

I've recently looked at some front end js code.
(1) In some cases the code creates a new JavaScript object from a JSON received from the back end. Then the newly created object is stored in a cache (say a map). This way the object would be cached on the front end for later use.
(2) I've also seen instances when the new JavaScript object (called eventObject here) is stored in the DOM like this: $(this).data('eventObject', eventObject);
Does it matter which way you store the data? I would personally cache eventObject in a JavaScript cache object (ie make your own cache class or a map). Isn't it simpler to cache like this than mess with the DOM and then you have to remember where you put what?
In my searches I've looked at XML DOM vs Object in Javascript
Isn't it simpler to cache like this than mess with the DOM and then you have to remember where you put what?
jQuery's data method actually doesn't mess with the DOM, it is just a convenient way to reference data objects by DOM nodes.
Of course, if you "have to remember whery you put it" the DOM references are not the best way to reference your objects. If a simple cache object seems cleaner to you, it probably will be cleaner.

How jQuery $.data() method differs from directly attaching variables to DOM elements?

I can do this:
$('#someid').data('dataIdentifier', 'someVariable');
And in my understanding I can do this:
document.getElementById('someid').dataIdentifier = someVariable;
What are the pros of using jQuery for this versus raw JavaScript?
From the documentation for jquery.data:
The jQuery.data() method allows us to
attach data of any type to DOM
elements in a way that is safe from
circular references and therefore free
from memory leaks. jQuery ensures that
the data is removed when DOM elements
are removed via jQuery methods, and
when the user leaves the page.
I don't know about the jQuery method, but a "pure javascript" approach is to use setAttribute(). setAttribute is the same as what happens when you attach arbitrary data attributes in the html. You can use getAttribute to read it.
document.getElementById('someid').setAttribute("dataIdentifier", "someVariable");
One advantage is that it will show up in the innerHTML property, which plain old properties will not. The disadvantage is you are limited to strings.

Using jQuery's datastore vs. expando properties

I'm developing code using jQuery and need to store data associated with certain DOM elements. There are a bunch of other questions about how to store arbitrary data with an html element, but I'm more interested in why I would pick one option over the other.
Say, for the sake of extremely simplified argument, that I want to store a "lineNumber" property with each row in a table that is "interesting".
Option 1 would be to just set an expando property on each DOM element (I hope I'm using the term 'expando' correctly):
$('.interesting-line').each(function(i) { this.lineNumber = i; });
Option 2 would be to use jQuery's data() function to associate a property with the element:
$('.interesting-line').each(function(i) { $(this).data('lineNumber', i); });
Ignoring any other shortcomings of my sample code, are there strong reasons why you would choose one means of storing properties over the other?
Using $.data will protect you from memory leaks.
In IE, when you assign a javascript object to an expando property on a DOM element, cycles that cross that link are not garbage collected. If your javascript object holds a reference to the dom object, the whole cycle will leak. It's entirely possible to end up with hidden references to DOM objects, due to closures, so you may leak without realizing it.
The jQuery datastore is set up to prevent these cycles from forming. If you use it, you will not leak memory in this way. Your example will not leak because you are putting primitives (strings) on the DOM element. But if you put a more complex object there, you risk leaking.
Use $.data so you won't have to worry.
If you are authoring a plugin you should use $.data. If you need to store the attribute often and rarely need to query the DOM for it then use $.data.
Update 5 years later: jQuery does not query the DOM based on expando properties set, and hasn't done so for a while. So use $.data. There's no reason to pollute the DOM when there is no pragmatic use to do so.
Using $.data doesn't modify the DOM. You should use $.data. If you're creating a plugin then you should store one object in $.data with properties on that object as opposed to storing each of those properties as different key/value pairs in the $.data structure.
Let me rephrase the question: What are the practical differences between the two data binding options available?
Actually there are three options:
$(e).prop('myKey', myValue);
$(e).data('myKey', myValue);
$(e).attr('data-myKey', myValue);
Note: OP’s e.myKey = myValue is practically the same as the .prop() line.
if you need more than strings, use .prop(), i.e. expando properties
if you need DOM/CSS transparency and/or HTML serialization use .attr('data-*')
if you need both you are out of luck
if you only use strings, but need no DOM, read on to weigh pros and cons yourself
what is with .data() → read the last two paragraphs
If you ever want to pass the data around with serialized HTML you need the .attr() solution. I.e. whenever you use things like .innerHTML or .html() or want to construct snippets from strings with data included. The same applies if you want to use CSS selectors like elem[data-myKey]. Disadvantage: you can only store strings.
If you don’t need your data to be visible in the DOM or available to CSS interaction .data() and .prop() might work. Their biggest advantage is: they can hold any Javascript value.
.prop() biggest disadvantage is the possibility of name collision. Only pick names you can be sure will not be used as native property ever. E.g. scope as key is a bad idea, because it exists on some HTML elements...
Now comes .data(). The other answers seem to swear on it, I avoid it. The memory leaks related to .prop() and expando properties in general belong to the past, so that is no advantage any more. But you will be secured against name collisions with HTML properties. That is an advantage. But you get a bunch of disadvantages:
$(e).data('myKey') draws its uninitialized value from the data-myKey attribute if available, runs JSON.parse() on those and sometimes returns that or falls back to the string value of the attribute. Once you set $(e).data('myKey', myValue) you lose the relationship with the data-myKey attribute, which nevertheless lives on with its “old” value, shown in DOM and in CSS interactions. On top, the key name you use is subject to possible name mangling. I.e. if you ever decide to read all key-value via $(e).data() the keys in that object might be different.
Because of this erratic behavior (mixing expando property technology with data-* attributes) and inconsistent get/set design I always avoid .data().—Fortunately that is easy to do with .prop() and .attr() (with data-* keys for compliance).
If you really want to use .data() to avoid name clashes with native properties, my advice: do not mix with data-* attributes, consider them a different thing, and avoid name clashes with those.—Does that make sense? For automatic clash avoidance you have to avoid clashes elsewhere manually. Great design.

What sort of memory leaks should I watch for with jQuery's data()?

Should I pair every data() call with a later removeData() call?
My assumptions: jQuery's remove() will remove elements from the DOM, and if I don't have any other references to remove, I don't have to do any more clean up.
However, if I have some javascript var or object referring to one of the elements being removed, I'll need to clean that up, and I'm assuming that applies to jQuery's data function, too, because it's referencing the elements somehow.
So if I do need to call removeData before remove, is there a shortcut to remove all data associated with an element or do I have to call each explicitly by name?
Edit: I looked through the source code and confirmed what Borgar and roosteronacid said. Remove takes the elements out of the dom and deletes any events and data stored with them - which is convenient, but makes me wonder when you would use removeData(). Probably not often.
jQuery's data does not keep a reference to the element so that you don't need to worry about memory leaks. Its intended purpose is to solve this exact problem.
A slight simplification of how it works:
An id member is added to each "touched" DOM node. All subsequent actions involving that DOM element use that id.
var theNode = document.getElementById('examplenode');
theNode[ 'jQuery' + timestamp ] = someInternalNodeID;
You can access the id using the same function jQuery uses:
someInternalID = jQuery.data( document.body );
When you append data to the node it stores that on the jQuery object, filed under the node's internal id. Your $(element).data(key,value) translates internally to something like:
jQuery.cache[ someInternalNodeID ][ theKey ] = theValue;
Everything goes into the same structure, including event handlers:
jQuery.cache[ someInternalNodeID ][ 'events' ][ 'click' ] = theHandler;
When an element is removed, jQuery can therefore throw away all the data (and the event handlers) with one simple operation:
delete jQuery.cache[ someInternalNodeID ];
Theoretically, you may therefore also remove jQuery without leaks occurring from any references. jQuery even supports multiple separate instances of the library, each holding it's own set of data or events.
You can see John Resig explaining this stuff in the "The DOM Is a Mess" presentation.
The whole point of jQuery is to abstract away from crappy JavaScript implementations and bugs in browsers.. such as memory leaks :)
.. Yup; all associated data to an element will be removed when that element is removed from the DOM.
By and large, javascript is fairly good about knowing when it's appropriate to collect the garbage, and unless you're writing very large-scale or long-running client-side apps, I'd say the memory involved is mostly inconsequential and trying to second-guess it isn't gonna gain you a lot.
Determining what unfinished lexical closures or other tricky javascript in jQuery might still be accessing your given data could be pretty complicated in some cases.
As far as I'm aware, though, if you store a reference to whatever you got with jQuery's data function then it would continue to exist after the element is removed, so removing that reference would be necessary as well. Some simple test cases would give you a more definite answer.

Categories

Resources