I want to associate a JavaScript object with an HTML element. Is there a simple way to do this?
I noticed HTML DOM defines a setAttribute method and it looks like this is defined for arbitrary attribute name. However this can only set string values. (You can of course use this to store keys into a dictionary.)
Specifics (though I'm mostly interested in the general question):
Specifically, I have HTML elements representing nodes in a tree and I'm trying to enable drag-and-drop, but the jQuery drop event will only give me the elements being dragged and dropped.
The normal pattern for getting information to event handlers seems to be to create the HTML elements at the same time as you are creating JavaScript objects and then to define event handlers by closing over these JavaScript objects - however this doesn't work too well in this case (I could have a global object that gets populated when a drag begins... but this feels slightly nasty).
JavaScript objects can have arbitrary properties assigned to them, there's nothing special you have to do to allow it. This includes DOM elements; although this behaviour is not part of the DOM standard, it has been the case going back to the very first versions of JavaScript and is totally reliable.
var div= document.getElementById('nav');
div.potato= ['lemons', 3];
If you're already using jQuery, you can use its data() method for this. This allows you to assign complex objects to the element if you want or you can leverage that method to hold a reference to an object (or some other data) at the very least.
It's worth noting that, under the hood, jQuery implements data() in precisely the way that bobince described in his answer, so you always use that directly, whether or not you're using jQuery.
Related
I'm part of a team that develops a custom element library for a big customer (according to custom element spec v1). We've developed like 30 components so far, half of those being autonomous custom elements, the other half being customized built-ins.
We're currently in the phase of refactoring and unifying the API of those components. Almost all of these components have custom attributes / properties.
With standard HTML elements the convention is to use data-attributes which are reflected to and from the dataset of the element.
I'm aware of the fact that autonomous custom elements allow for free attribute / property naming as long as the name doesn't conflict with what HTMLElement already has.
The idea behind data- attributes as I understand is to make sure that no libary will begin implementing a custom attribute which might conflict with future versions of the HTML standard.
The same, though, should be valid for property names. Imagine the HTML standard would get a new universal attribute, let's abstractly call it attr. Of course that would also reflect to a same-name property on the HTMLElement prototype.
My questions are:
What is the suggested approach to evade future conflicts here? Is it to always only use data--attributes for custom attribute implementations, and thus, avoid the conflict by working with the dataset instead of properties directly attached to the element?
If so, how would you then implement "boolean" data-attributes (where the attribute value doesn't matter, the state is implemented via absence/presence of that attribute)?
Is it possible to define own getters and setters for properties in the dataset of an element?
Please ask for clarification if anything is unclear. I tried to make the question as concise as possible.
I don't worry about future-proofing my custom element attributes or properties against what may become a predefined attribute or property of HTMLElement. If the spec changes, then I will deal with it then. But to try to guess everything that might be used in the future is not possible nor worth my time.
I avoid using data- attributes for custom elements. The dataset values are only strings and do not convert well. For example if I do this:
el.dataset.dog = {woof:10};
then my tag get's an attribute like this: data-dog="[object Object]" and this console.log(el.dataset.dog) displays "[object Object]". That defeats the purpose of many properties that may need to deal with objects.
I only create attributes for things that must be attributes. If I must allow the HTML to define default values or if I need to create a CSS attribute selector then I will create an attribute. But only under these two conditions.
I create properties (getter/setter) or functions for everything I need. It is easier to pass in data of any format into a property or function then it is to convert it into a string to pass in through an attribute.
I have even taken over one or more of the existing HTMLElement properties and attributes. This is rare, but sometimes I need to do something with the value or I just don't want the default operation to happen. Again, this is rare.
I have been evaluating a lot of different client-side JS template engines (doT, parrot, dust.js, microtemplating, underscore, etc). They all work similarly, using some type of tags to represent data, and with some giving the ability to embed pure JS into the template, including loops, if/then, etc. However, they all work by converting the template itself into a string, then into javascript, in order to interpolate the variables, execute loops, etc.
When this conversion happens, any event handlers that were attached to the original objects within the template (i.e. created by jQuery at document.ready) are of course lost. To add these handlers back to the resulting HTML would then necessitate going back and re-applying any such event handlers after each template rendering.
I'm trying to think of a way to create a template engine with full javascript support, but which preserves any events attached to the template before cloning.
Imagine a scenario where the template is for a list of items. Each item includes buttons which perform specific tasks on that item (i.e. edit, delete, rename, copy, you get the idea).
To make the code clean and easily maintainable, it would make sense to apply Click events to these buttons in the template HTML at document.ready(). Then each time the template is cloned for a new list item, the events are cloned too.
However, with current templating libraries, all events are lost at the cloning stage, which necessitates applying all events to the cloned object each time the list is updated. If this is live data, or if the user is adding new items to the list, this seems like it would become very convoluted to keep track of the events and ensure they are properly attached each time an item is added.
I know jQuery has a clone() function which clones events, and this works great for basic templates, but when you get try to incorporate arbitrary JavaScript into the template, this becomes impractical.
I am trying to avoid templates which depend on html element attributes to configure loops, decisions, etc, because the template code becomes very ugly. A clean template with simple tags for data substitutions, simple JS for-loops for repeated elements, and simple references to the source data, is desired.
Anyone have ideas on how to do this?
Instead of over-complicating templating, you should use event delegation, so that this problem does not even present itself.
And yes, jQuery had .delegate – it is deprecated, and has been replaced by .on, which can do the same thing, see http://api.jquery.com/delegate/#entry-longdesc
I'm making a little script right now and one of the inputs for an arg of the main function is an element. In order to make sure that raw JS works for this stuff I'm doing theelement.value to get the variable's value. Unfortunately, that doesn't work if thelement is a jquery function, like $('#foo'). Some other reasons I need this kind of compatibility is that I need access to theelement.style, once again something jquery doesn't support. I know there are jquery workarounds for these (.css(), .val() etc.) but if I use those, then regular js inputs (document.getElementbyId('foo')) won't work. Is there any way to use one "property" and make it universal against all inputs of elements (including stuff from libraries like prototype and Dojo)?
No it is not possible to make everything universal.
If you want to work on DOM elements, the input needs to be DOM elements and the framworks all have ways of getting the DOM elements out. You can not magically make a script that works with any library out there, that is why generic plugins normally have library specific extensions to add support.
It sounds to me like you have a function that takes an argument and you have issues when you pass different types as that argument:
function doSomething(element) {
// do something with element
}
You've mentioned that you want element to sometimes be a jQuery object and sometimes just a DOM object and then you also mentioned other libraries too (prototype, Dojo).
First off, your problem will go away entirely if you just call your function with a consistent type. The simplest type here that is available in all libraries is a DOM element. So, if you just enforce that element is passed as a DOM object reference and not a jQuery object or any other library object, then your problem will go away entirely. If the caller is using some other library and then wants to call your function, they can always get the DOM object from that library and pass the actual DOM object. For example, in jQuery, you can get a DOM object like this `$("#foo").get(0)
If you want to make it more universal and accept multiple types, then you have to write code to detect each type that might be passed and get the DOM object out of each type so you can then process them all identically. For example, if you wanted to detect either a jQuery object of a DOM object, you could do so like this:
function doSomething(element) {
// normalize element to be just a DOM object
// if it's a jQuery object, then reach in to get the first DOM object
if (element.jquery) {
element = element.get(0);
}
// do something with element here which is a DOM object
}
If you wanted to detect objects from other libraries too, then you'd have to write similar detection code for each other library and get the actual DOM object out of that other library's object.
In the following getElementsByTagName("p")[0] and getElementById("demo") access the same element.
Both of the following work, so I can't figure out why the jquery data function is even needed. Is the second not portable to all browsers.
$(document.getElementsByTagName("p")[0]).data("funcZ", function() {console.log("ZZZZZ")})
$(document.getElementById("demo")).data("funcZ")()
document.getElementsByTagName("p")[0].funcX = function() {console.log("XXXXX")}
document.getElementById("demo").funcX()
According the the jQuery website:
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.
It's possible that by attaching random fields to a DOM element, when the DOM element disappears, the fields remain in memory. It looks like jQuery handles that for you.
The $.data() method is perfect for hiding data as opposed to attaching it to a data-attribute. It's easily accessed by key/value, great for storing state information when creating plugins, or really anything.
So we all know you can create JavaScript objects quite simply as:
var myObject = new Object() ;
myObject.attribute = someValue ;
myObject.doSomething = someFunction ;
We can then use that object within JavaScript to manipulate the DOM using the standard techniques one would except eg:
document.getEmelemntByID("some ID").someAttribute = myObject.attribute;
Now this is simply using the facilities of JavaScript to query the DOM to find some element and then set an attribute, all well and good. You can do the same thing with jQuery and other tools, all of which are based on JavaScript.
One can call a JavaScript function with any of the various methods the DOM provides such as onClick, onChange, etc. etc.
So my question is how does one actually have a JavaScript object IN the dom, with say a draw method a click method, etc. etc. This would allow DOM to execute the draw method to render something on the DOM canvas and then call a method particular to that object on say click or double click?
The DOM tree is made up of Javascript objects, specifically different kinds of element objects. You can't put other objects in the DOM tree.
The DOM elements uses events for customisable actions, not methods. The click event for example is activated when someone clicks on the element. You hook up a method as event handler for an event, and that method will be called when the event is activated.
You can't make your own types of objects in the DOM and create new browser level events with javascript. You must create one of the types of objects that the browser DOM already supports and use the events that those objects support. It is possible to use native code to create browser plug-ins (like Adobe Flash) that will do some of what you're asking, but I don't think that's what you wanted as those aren't implemented in javascript and require downloads.
You can however use a generic object like a <div> and respond to click events on it in any way you want. Since a div has no default behavior for clicks and drawing, it's just a placeholder in the DOM. You can then embed your own objects inside of it or set properties on it to create whatever visual look you want. And, you can handle the various events on it (clicks, keys, etc...) to give it the desired behavior. So, you can think of a generic object like a <div> as scaffolding which you can build your own object look and behavior on.
There are even ways to put a javascript wrapper around the DOM object so most of your interaction with that object appears to be with your javascript object instead of with the actual DOM object (that's what both jQuery and YUI do). But, underneath, it's still a real DOM object with your own javascript wrapper around it.