Forum Moderators: open

Message Too Old, No Replies

code structure question for the experts

where do you store element data?

         

Skier88

7:36 am on Aug 6, 2011 (gmt 0)

10+ Year Member



When I'm writing javascript I often have to store data associated with an element on the page, but all I know are places not to store it - does anybody know where I should store this data? Or at least have a good idea?

Here's an example: a script magnifies an image when you click on it. The script has to calculate the target image's original x, y, width, and height, and the x, y, width, and height it should transform it to. To zoom back out you need all of this data, so it's a good idea not to re-calculate it. Where would you store this? Global variables are always frowned upon. As is attaching it directly to the element (el.jsdata=[x,y,width,height];). I often end up using closures or attaching to pre-existing global objects, but both solutions are awkward and bulky. What would you do?

Thanks for reading.

lucy24

9:09 pm on Aug 6, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Not being an expert, I just use global variables ;) In fact I've got a couple of page where the data is so vast compared to the code that I keep the two in separate files. (Wasn't sure you were even allowed to do this, but I tried it and mercifully it works.)

Do you have to calculate the width and height? Aren't they known (or knowable) already from the html?

Oh and psst! In the list of post options is one called "Disable graphic smileys". Good for keeping unwanted winks out of your code :) (There are other ways to avoid them, but if you're not using smileys anyway, that's the easiest.)

Fotiman

4:24 am on Aug 7, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I would recommend that you only use 1 global object, which acts as a namespace for your code. That object may contain multiple other objects, but you keep the global scope clean. Search for JavaScript namespace and you'll find many ways to achieve this.

Skier88

3:23 pm on Aug 7, 2011 (gmt 0)

10+ Year Member



Thanks for the replies. lucy, yes, it's sometimes knowable, but I wanted the code to be more flexible. In this particular case I knew the height but not the width, but I didn't want to change it in two places if I decided on a different height. And thanks for the tip - I didn't notice because it's not transforming on my account.

Fotiman, thanks, that sounds like the right solution. But I'm not sure how to implement it the most efficiently - in the simple example of wrapping my code in (function() { [...] })(); the program would still have to go through several levels of variables before arriving at the one that contains the data I'm looking for. In contrast, a closure (which I don't want to use in a loop) would only require the program to look up one level, and variables declared in the zoomout function's context would be the fastest to access and not clutter any namespace but the one they are used in. Is there a way to use your solution more efficiently, or am I being too picky?

Fotiman

1:27 pm on Aug 8, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I'm not sure if I totally understand your scenerio, but here's a pseudo code example (I have not tested this).


var MYNS = {
Magnifier: {
_data: {},
init: function(imgs) {
var i, x, y, w, h;
// iterate through imgs and calculate
for (i = 0; i < imgs.length; i++) {
x = ...; // calculate x value
y = ...; // calculate y value
w = ...; // calculate w value
h = ...; // calculate h value
// Store the data
MYNS.Magnifier._data[imgs[i]] = {
x: x,
y: y,
w: w,
h: h
}
}
},
ZoomIn: function (img) {
// access _data
// MYNS.Magnifier._data[img].x
// MYNS.Magnifier._data[img].y
// MYNS.Magnifier._data[img].w
// MYNS.Magnifier._data[img].h
},
ZoomOut: function (img) {
// access _data
// MYNS.Magnifier._data[img].x
// MYNS.Magnifier._data[img].y
// MYNS.Magnifier._data[img].w
// MYNS.Magnifier._data[img].h
}
}
}
MYNS.Magnifier.init(['img-1', 'img-2']);


In other words, MYNS (short for My NameSpace) is your only global object. Within that is a Magnifier object, which gets initialized with an array of images that you want the magnifier to apply to (your implementation may be different). Initialization would collect any data it needed and store it in a pseudo private _data variable (the _ implies private, but in this example _data is publicly accessible externally ... you could use a closure to make this truly private, but I didn't bother for this example). Then you have 2 other public methods, ZoomIn and ZoomOut, which each take some image reference as a parameter, and which could then look up the values from within MYNS.Magnifier._data. This is not an expensive lookup.

Hope that helps.

rocknbil

5:20 pm on Aug 8, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



The script has to calculate the target image's original x, y, width, and height, and the x, y, width, and height it should transform it to. To zoom back out you need all of this data, so it's a good idea not to re-calculate it. Where would you store this?


I would store these on output and reference the image object itself or hidden fields, or using HTML5, the data-[whatever] attribute.

<img src="some-thumb.jpg" id="image1234" width="300" height="125" alt="some image">
<input type="hidden" name="img1234-stuff" value="600, 250, 100, 300">

or HTML5

<img src="some-thumb.jpg" id="image1234" data-content="600, 250, 100, 300" width="300" height="125" alt="some image">

So for each function, leverage the windows by the image ID to collect whatever you need.

Skier88

9:19 pm on Aug 12, 2011 (gmt 0)

10+ Year Member



Thanks for your replies, but I still feel like there has to be some better solution. For example, Fotiman, in your example to retrieve a value the code has to:
1: search ZoomIn namespace for MYNS
2: search Magnifier namespace for MYNS
3: search MYNS namespace for MYNS
4: search global namespace for MYNS
5: search MYNS for Magnifier
6: search Magnifier for _data
7: search _data for img
8: search _data[img] for x

While I understand this isn't expensive, it certainly doesn't look fully optimized. And I guess I should specify that's what I'm really asking - your approach looks good in a number of ways, but I'm looking for something as efficient as possible as well.

And rocknbil, in your approach everything is stored as a string - I imagine that couldn't bode well when the data is used 50 times per second.

I point these issues out because in another example variable retrieval could be as simple as
1: search ZoomIn namespace for x
2: search Magnifier namespace for x

eg:

function setZoom(el) {
var x,y,w,h;
el.onclick=function() {
// el.style.width=w+'px';
};
}
setZoom(document.getElementById('zoomer'));


Of course, this uses excessive memory when applied multiple times.

Is there a way to combine the benefits of the two approaches?

Fotiman

1:28 pm on Aug 15, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month




For example, Fotiman, in your example to retrieve a value the code has to:
1: search ZoomIn namespace for MYNS
2: search Magnifier namespace for MYNS
...

"Search" is not really a good term here. There is no searching, it is accessing a named property of an object. This is essentially acting as a Map, and will be very efficient.

Skier88

2:38 am on Aug 16, 2011 (gmt 0)

10+ Year Member



My mistake, I just wasn't sure what to use instead. Maybe look up. But I think you agree that the time required is not zero so in the situation where the function is only applied once the closure is more efficient, if not by much. My goal isn't to get something to work but to get rid of that annoying thought that I could have done it better ...

brotherhood of LAN

2:57 am on Aug 16, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



A JS coder may confirm this for me, but as Fotiman says a variable name is more of a map than a search to find something. Declaring var x = 2, any future reference to it is going to point to an address in memory which is going to be very fast.

The idea of having one global variable keeps its variables encapsulated within the declaration, making it easy to reference and prevents you redeclaring the same variable names elsewhere that'd start causing problems.

Try a loop like for(i = 0;i < 100000;i++) and see if using a global namespace is any slower than just a regular variable declaration.

Skier88

4:01 am on Aug 16, 2011 (gmt 0)

10+ Year Member



I understand why a global namespace is a good thing, but that's really a separate issue - I can namespace my code without attaching everything to it directly.

Global variables are slower in javascript - see [dev.opera.com ]. I put together a few test cases - yours: [jsperf.com ], and something similar to a single-use case of the zooming example: [jsperf.com ].

brotherhood of LAN

4:20 am on Aug 16, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Thanks for putting them to the test.

jsperf.com/global


You've declared the variables as a string and yes, there appears to be a big difference in speed. If you declare the variables as integers there is a much narrower difference and it runs 10x faster for me. See jsperf.com/examplex

http://jsperf.com/zoomer


The comparison doesn't look fair as the namespaced example re-declares variables in the loop.

Fotiman

2:39 pm on Aug 16, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month




I imagine that couldn't bode well when the data is used 50 times per second.

The jsperf.com/zoomer example gives about 16 million operations per second for the "slow" example. In other words, you won't notice any difference between the two approaches if your goal is to run 50 times per second. So in that case, I would argue that code maintainability should be a factor. Be careful not to micro-optimize at the expense of maintainability, especially where it won't make a noticeable performance difference.

With that said, you could move the _data object up the namespace chain for a slight performance gain (about 3 million more operations per second) [jsperf.com...] But logically, its not as nicely encapsulated there (might be mingling with other, non-zoom related data).

Skier88

11:58 pm on Aug 16, 2011 (gmt 0)

10+ Year Member



The variables were strings because I was copying from dev.opera - I figured it wouldn't make a difference in the relative performance. Now I know that was a mistake but I don't know why less computation would narrow the gap - any ideas?

And I'm not sure what you mean about re-declaring variables in a loop. Both functions declare variables in the zoom function, and the only loop that sets variables is in the initialization function and isn't part of the performance test.

Fotiman, thanks for your comment about micro-optimization - I have definitely noticed myself making that mistake. It was actually part of the reason I posted this thread - my code either looked cluttered or verbose. I'm hoping for a solution that avoids this problem as well.

I created another test case at [jsperf.com ], adding an expando approach. It was faster than I expected, but I'm not sure if the gap would be so large on an actual DOM node with many more attributes.

I know attaching to the DOM with standard names is considered bad practice, so I've been looking at the html5 data-* approach closer. setAttribute and .dataset both convert the value to a string (in chrome and xp), but setting
el['data-zoomx']=10
does not. Is this expected behavior? If it is I might have found my answer...