Note that there are some explanatory texts on larger screens.

plurals
  1. POhow to code around storing data (objects, custom attributes) on DOM nodes
    primarykey
    data
    text
    <p>I wanted to write some pure javascript to better understand it (I realize in "real practice" that frameworks such as jQuery are much more advised and applicable, but this isn't really about how to use frameworks, more about how pure javascript works and best practices).</p> <p>Anyways, I wrote some simple javascript code. I wanted to create a set of groups of buttons that had one state at a time from the set {on,off} and each state would map to a corresponding function to be fired upon entering that state. Each group of buttons within the master set could contain only one button in the on state at a time. The concept is similar to the idea of radio buttons. <em>Why not use a radio button then?</em> Well semantically it's just suppose to be some buttons for some control elements, but either way I suppose I could have but the question isn't really about that.</p> <p>The thing is, to pull this off, I added a lot of custom attributes to specific <code>button</code> elements by <code>id</code> in my Javascript. I was doing some research, and found this <a href="https://stackoverflow.com/questions/3095336/is-it-bad-practice-to-add-properties-to-dom-nodes">question</a> and this <a href="https://stackoverflow.com/questions/9242009/storing-custom-data-in-dom-elements">question</a>, regarding using custom attributes on DOM node (objects). They seem to advocate against such a practice, one even goes so far as to say that doing so could cause potential memory leaks depending on the browser's implementation.</p> <p>However, for each button I create I need to keep track of a lot of attributes, and if I expand this script I may have to add even more. So what's the best way around storing them on the DOM node but still keeping track of everything and being able to use <em>this</em> in attached functions, etc. al?</p> <p>It wasn't readily obvious to me how to do this without at the minimum storing a reference of a well name spaced object to the DOM node <code>button</code> element. </p> <p>I was able to see that from this <a href="https://stackoverflow.com/questions/4469950/how-to-associate-javascript-objects-with-dom-nodes">question</a> jQuery has some way to do this, but I want to know how this is done with just pure javascript.</p> <p>Here's the full sample code I am working with:</p> <pre><code>&lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;title&gt;Button Test Script&lt;/title&gt; &lt;script language="javascript" type="text/javascript"&gt; window.button_groups = {}; function isset( type ) { return !(type==='undefined'); } function debug( txt ) { if( !isset(typeof console) ) { alert( txt ); } else { console.log(txt); } } function img( src ) { var t = new Image(); t.src = src; return t; } function turnGroupOff( group ) { if( isset(typeof window.button_groups[group]) ) { for( var i = 0; i &lt; window.button_groups[group].length; i++ ) { if( window.button_groups[group][i].toggle == 1) window.button_groups[group][i].click(); } } } /** * buttonId = id attribute of &lt;button&gt; * offImg = src of img for off state of button * onImg = src of img for on state of button * on = function to be fired when button enters on state * off = function to be fired when button enters off state */ function newButton( buttonId, offImg, onImg, group, on, off ) { var b = document.getElementById(buttonId); b.offImg = img( offImg ); b.onImg = img( onImg ); b.on = on; b.off = off; b.img = document.createElement('img'); b.appendChild(b.img); b.img.src = b.offImg.src; b.group = group; b.toggle = 0; b.onclick = function() { switch(this.toggle) { case 0: turnGroupOff( this.group ); this.on(); this.toggle = 1; this.img.src = this.onImg.src; break; case 1: this.off(); this.toggle = 0; this.img.src = this.offImg.src; break; } } if( !isset(typeof window.button_groups[group]) ) window.button_groups[group] = []; window.button_groups[group].push(b); } function init() { var on = function() { debug(this.id + " turned on") }; newButton( 'button1', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group1', on, function() { debug(this.id + " turned off"); } ); newButton( 'button2', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group1', on, function() { debug(this.id + " turned off (diff then usual turn off)"); } ); newButton( 'button3', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group2', on, function() { debug(this.id + " turned off (diff then usual turn off2)"); } ); newButton( 'button4', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group2', on, function() { debug(this.id + " turned off (diff then usual turn off3)"); } ); } window.onload = init; &lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;button id="button1" type="button"&gt;&lt;/button&gt; &lt;button id="button2" type="button"&gt;&lt;/button&gt; &lt;br/&gt; &lt;button id="button3" type="button"&gt;&lt;/button&gt; &lt;button id="button4" type="button"&gt;&lt;/button&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p><strong>UPDATE</strong></p> <p>The jQuery thing was a bit overkill for my purposes. I don't need to extend an arbitrary element. I have a good idea of how that is done now specific to jQuery (with the arbitrary randomly named attribute storing a cache index integer).</p> <p>I know ahead of time which host elements I need to extend, and how; also I can/want to setup an <code>id</code> attribute on each of them on the HTML side.</p> <p>So, inspired by the jQuery setup, I decided to also create a global cache variable except I am going to use the DOM node's id attribute as my cache key. Since it should be a unique identifier (by definition), and I have no plans to dynamically alter id's ever, this should be a simple task. It completely divorces my Javascript objects from the DOM objects, but it does make my code look quite a bit uglier and difficult to read with the many calls to <code>data</code>. I present the modifications below:</p> <pre><code>&lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;title&gt;Button Test Script&lt;/title&gt; &lt;script language="javascript" type="text/javascript"&gt; window.button_groups = {}; function isset( type ) { // For browsers that throw errors for !object syntax return !(type==='undefined'); } var c = { // For browsers without console support log: function( o ) { if( isset(typeof console) ) { console.log(o); } else { alert( o ); } }, dir: function( o ) { if( isset(typeof console) ) { console.dir(o); } } }; function img( src ) { // To avoid repeats of setting new Image src var t = new Image(); t.src = src; return t; } var cache = {}; function data( elemId, key, data ) { // retrieve/set data tied to element id if(isset(typeof data)) {// setting data if(!isset(typeof cache[elemId])) cache[elemId] = {}; cache[elemId][key] = data; } else { // retreiving data return cache[elemId][key]; } } var button_groups = {}; // set of groups of buttons function turnGroupOff( group ) { // turn off all buttons within a group if( isset(typeof window.button_groups[group]) ) { for( var i = 0; i &lt; window.button_groups[group].length; i++ ) { if( data(window.button_groups[group][i].id, 'toggle') == 1) window.button_groups[group][i].click(); } } } /** * buttonId = id attribute of &lt;button&gt; * offImg = src of img for off state of button * onImg = src of img for on state of button * on = function to be fired when button enters on state * off = function to be fired when button enters off state */ function newButton( buttonId, offImg, onImg, group, on, off ) { var b = document.getElementById(buttonId); data( b.id, 'offImg', img( offImg ) ); data( b.id, 'onImg', img( onImg ) ); data( b.id, 'on', on ); data( b.id, 'off', off ); var btnImg = document.createElement('img'); btnImg.src = data( b.id, 'offImg' ).src; data( b.id, 'img', btnImg ); b.appendChild( btnImg ); data( b.id, 'group', group ); data( b.id, 'toggle', 0 ); var click = function() { switch(data(this.id,'toggle')) { case 0: turnGroupOff( data(this.id,'group') ); (data(this.id,'on'))(); data(this.id,'toggle',1); data(this.id,'img').src = data(this.id,'onImg').src; break; case 1: (data(this.id,'off'))(); data(this.id,'toggle',0); data(this.id,'img').src = data(this.id,'offImg').src; break; } } b.onclick = click; if( !isset(typeof window.button_groups[group]) ) window.button_groups[group] = []; window.button_groups[group].push(b); } function init() { var on = function() { c.log(this.id + " turned on") }; newButton( 'button1', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group1', on, function() { c.log(this.id + " turned off"); } ); newButton( 'button2', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group1', on, function() { c.log(this.id + " turned off (diff then usual turn off)"); } ); newButton( 'button3', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group2', on, function() { c.log(this.id + " turned off (diff then usual turn off2)"); } ); newButton( 'button4', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group2', on, function() { c.log(this.id + " turned off (diff then usual turn off3)"); } ); } window.onload = init; &lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;button id="button1" type="button"&gt;&lt;/button&gt; &lt;button id="button2" type="button"&gt;&lt;/button&gt; &lt;br/&gt; &lt;button id="button3" type="button"&gt;&lt;/button&gt; &lt;button id="button4" type="button"&gt;&lt;/button&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p><strong>UPDATE 2</strong></p> <p>I found that through using the power of <em>closure</em> I truly only need to store one "special" attribute, that is the group the button belonged to.</p> <p>I changed the <code>newButton</code> function to the following, which through closure, eliminates the need to store many of those other things I was:</p> <pre><code>function newButton( buttonId, offImg, onImg, group, on, off ) { var b = document.getElementById(buttonId); offImg = img( offImg ); onImg = img( onImg ); var btnImg = document.createElement('img'); btnImg.src = offImg.src; b.appendChild( btnImg ); data( b.id, 'group', group ); var toggle = 0; var click = function(event) { switch(toggle) { case 0: turnGroupOff( data(this.id,'group') ); if( on(event) ) { toggle = 1; btnImg.src = onImg.src; } break; case 1: if( off(event) ) { toggle = 0; btnImg.src = offImg.src; } break; } } b.onclick = click; if( !isset(typeof window.button_groups[group]) ) window.button_groups[group] = []; window.button_groups[group].push(b); b = null; } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload