Creating a constant time data structure for DOM element references - javascript

Relevant discussion.
I understand I can build an array of references to elements/nodes. I realize also that I could use the neat trick of treating an array like a heap (index 2n and 2n+1 for children) to build a (potentially wasteful) binary search tree using it.
But all that's still not enough for the premature optimizer in me. Besides, implementing a BST is going to be bug prone.
Here's my question. Can I somehow use an element reference as index into javascript's hashes (which are objects, or vice versa?). If not, can I conjure up a unique string from an element reference, which I can then use as my hash key? If not, how the hell does jQuery do it?

The easiest option is to just use your own attribute on the DOM object:
var element = document.getElementById("test");
element.myData = "whatever";
Here's the general idea behind how jQuery's .data() function works that you could use in your own plain javascript. It uses one custom attribute on the object and then stores everything else in a data structure indexed by the value of that custom attribute.
var idCntr = 0; // global cntr
var data = {};
var element = document.getElementById("test");
var id = element.uniqueID;
if (!id) {
id = idCntr++ + "";
element.uniqueID = id;
}
data[id] = "whatever";
// then some time later, you can do this
var element = document.getElementById("test");
console.log(data[element.uniqueID]); // whatever
It is a bit more involved to store multiple attributes for a given object in the data object, but this is the general idea.
And, if you can use jQuery, it's trivial:
$("#test").data("myData", "whatever"); // sets the data
console.log($("#test").data("myData")); // retrieves the data
If you want to really see how jQuery's .data() works, you can step through the first call to set data and then retrieve it when using the unminified jQuery. It's easy to see what it does.

Related

set/attach invisible/unseen attribute/value into html element through vanilla js

EDIT
why i try to achieve this ?
Encapsulation purpose. generally every programmer encapsulate his/her code. also think about encrypt url or slug url and programmer also use uuid (Universally unique identifier) instead of id (integer id).
END EDIT
I set data attribute as a id to my html element and when I console log or debug html element I can see the data id (1,2)....
I want if anyone debug my html he can not see my data id(1,2) ...
how I will do it invisible or unseeable ? or is there is a way do it invisible or unseeable ?
below example..
const list = document.querySelectorAll('.item');
for(let x = 0; x < list.length; x++){
// list[x].setAttribute('data-id',(x+1));
list[x].dataset.id = x+1;
console.log( list[x]);
list[x].addEventListener('click',goCategoryPage)
}
function goCategoryPage(e){
e.preventDefault()
//
}
<ul class="list">
<li class="item">Category 1</li>
<li class="item">Category 2</li>
</ul>
The short and simple Answer is: Use something like list[x].myId = x+1; To set a custom property. As opposed to attributes, properties are not reflected in the DOM.
This gets the job done. But is considered bad practice. Because there are some caveats:
Your custom property today, might be a standard property in the future, but with a completely different meaning. To make sure this won't become an issue in the future, make sure to add a prefix like your apps name or at least "my" to your property.
It is okay to store primitive data like numbers, booleans or strings this way. But you might get issues with memory leaks if you start storing more complex data. Especially if you start to store references to other DOM Nodes.
More details on the topic of custom properties are discussed here: Can I add arbitrary properties to DOM objects? I think it is a question of personal taste and you really need to make sure you don't produce (future) name collisions and don't store complex data.
For primitive data like your id, I don't see a reason not to store it as a data attribute. There should be nothing to be ashamed of. It also is quite hard to keep a secret in JavaScript in the Browser.
There are two other approaches I'd consider if I had to store more complex data.
Use a WeakMap. You keep your WeakMap in a central place. It has the DOM Node as key and your data as Value. If the Node is garbage collected your data is too.
Create a new function (closure) for each node, which contains the ID and attach it to the click event of your Node. Event listeners are also garbage collected.
Here details on the closure approach:
const list = document.querySelectorAll('.item');
for(let x = 0; x < list.length; x++){
console.log(list[x]);
list[x].addEventListener('click', function () {
const id = x+1;
e.preventDefault();
// stuff to be done on click
});
}
For each iteration of the loop, a new function (with a new closure) is created. Inside this closure the value of x is the value of x in the current iteration - where the function is created.

jQuery: storing data between custom events

Given the following example ...
$("#some-id").on("custom-event-1", function()
{
this.someData = "test";
}).on("custom-event-2", function()
{
var test = this.someData;
...
});
... I am able to store and pass data between custom-event-1 and custom-event-2, however, is this the correct way of doing this? It works, but I'm not sure if things should be done this way, or if the jQuery data method should be used instead. If this approach is acceptable, where exactly is the data being stored? I understand where the jQuery data method stores the data, but the example above I'm not sure about.
I'd recommend you to use .data(id,value).
DOM elements are like a regular object. Ex: A div is an instance of the prototype HTMLDivElement. Test it via document.createElement('div').constructor.
Doing this.someData = 1 is similar to a = new Date(); a.someData = 1 or a = []; a.someData = 1.
It does work but it's not "clean". There is a performance cost to change the structure of an object. And you assume that the object doesn't have the attribute someData already used for something else.
With .data() you are safe.

Associate an element with a unique number

I'm looking for a way to associate a DOM element with a unique number, such that no other element in the DOM will be associated with that number. Obviously, I can't use an Id attribute because not all elements have an Id.
The most obvious way to do this is to (somehow) acquire a number that will give the element's position within the DOM, but I'm not sure if this is feasible. Ultimately, given an arbitrary element from the DOM, I'd like to have a way of mapping that element to a number.
Everyone is asking why I need to do this -- given a DOM element, I need to use that element as a key in a JS Object. JS Objects must be strings. So, technically, I do not need a unique number, per se, but I need a unique value that can be turned into a "short string" and used as the key in a JS object.
Technically, I do not need a unique number, per se, but I need a unique value that can be turned into a "short string" and used as the key in a JS object.
I see that you have jQuery tagged. This might be a possible solution to your problem. jQuery has a way to associate an element to an object:
var ele = /* your element */
$(ele).data({name: "Paul"});
// later you can use the element as a key
$(ele).data(); //returns {name: "Paul"}
This will avoid the whole "assigning unique id" mess and just let jQuery does all the hard work for you (creating a map data structure).
Edit by Roamer-1888
Basically, I need to "tag" an element uniquely so that if I encounter the same element in the future, I will know I've seen it before. The "tag" value will be the key in my JS object. I will associate various values in the tagged element. I don't want to use Map or WeakMap because they might not be supported on all browsers.
To meet this requirement, you would typically use jQuery's .data() as follows :
//...
var elementData = $(ele).data('myApplication');
if(!elementData) { // this element was not previously encountered
elementData = {
prop_A: ...,
prop_B: ...,
prop_C: ...
};
$(ele).data('myApplication', elementData);
}
// Here `elementData` is guaranteed to exist and its properties can be read/written.
//...
This technique is particularly useful for preseving state in jQuery plugins. Here is an example
Multiple applications/modules can do this without interfering with each other.
I'm guessing that there might be a better way to solve your problem if we understood what the real problem was. But, at any point in time, you can find what position an element is at (if it is currently inserted in the document) with something like this:
var items = document.getElementsByTagName("*");
If you needed to then get a unique index for a DOM element (at this particular point in time), you can do so by just searching for it in that HTMLCollection. It's position in the collection is guaranteed to be a unique index.
But, of course as the DOM is modified, this HTMLCollection will change as will the index.
If you wanted to assign a non-changing unique index, you could just assign a property to each DOM element based on a monotomically increasing counter.
var idCntr = 1;
function assignIds() {
var items = document.getElementsByTagName("*");
for (var i = 0; i < items.length; i++) {
if (!items[i]._uniqueId) {
items[i]._uniqueId = idCntr++;
}
}
}
You can call this function as many times as you want and it will assign new unique IDs to any DOM elements that don't yet have an id, but will leave ids that were already assigned the same (so they will never change).
If you just want to be able to generate a unique ID for any given DOM node such that you can always have the same ID for that DOM node, then you can just again use a monotomically increasing counter.
var idCntr = 1;
function assignId(elem) {
if (!elem._uniqueId) {
elem._uniqueId = idCntr++;
}
return elem._uniqueId;
}
You can then call this on any DOM element that you want to assign a unique ID to and it will both assign the id to the element and return the id that it assigned. If you pass it an element that already has an id, it will leave that id in place and just return it.
Based on your latest edit, it appears you're just trying to generate an id string that you can use as a key in a JS object. You can certainly use the above assignId() function for that.
In a modern browser, you can also use a Map or a
WeakMap object which will accept the DOM object itself as the key - you don't need to manufacture your own string key. You can then look it up directly with the DOM element too (since it's the key).

Is it okay to store the result of a JQuery selector in a variable?

Developers I know tend to call the same JQuery selectors over and over instead of storing the result in a variable. They are consistent with this approach.
For example, they do this:
var propName = $(this).attr('data-inv-name');
var propValue = $(this).attr('data-inv-value');
Instead of this:
var current = $(this);
var propName = current.attr('data-inv-name');
var propValue = current.attr('data-inv-value');
The latter approach feels correct to me but maybe I'm missing something. This is a simple example, but I've seen $(this) repeated dozens of times in the same function.
What is the best practice for development with JQuery? Call selectors repeatedly or store in a variable?
The shown analysis is a micro optimization. Using $(this) repeatedly versus storing $(this) in a variable and reusing it will not cause a significant hit to performance.
The times you really want to store the result is when there is an actual selector in there. The only hit you are taking by repeatedly calling $(this) is calling the jQuery constructor function which is very lightweight.
So in this instance go with what reads better. If there really is a dozen occurrences of $(this) in a row, then there should have either been some storing of the variable as indicated, or more likely there was an opportunity to take advantage of chaining which was missed.
If I'm going to use the same selector more than twice I always create a variable. The one change I would recommend is using $ before your variable name to signify that it is a jQuery object
var $current = $(this);
var propName = $current.attr('data-inv-name');
var propValue = $current.attr('data-inv-value');
In theory selecting the component many times demands more process then using one you already have...
If you don't have too many selectors in your page the diference will be almost null (I guess this is the more commom case)... Then you can think about what makes it more readable or easy to modify...
Sometimes you use the same element in a dozen of lines, in this case I prefer to assign this to a variable because when the element change I will need to change just one line (the line I assigned the variable)...

How does JavaScript distinguish between objects?

I have wriiten
document.createElement("p");
document.createElement("p")
How does Javascript intepreter knows do distinct between those two?
Id ? ( what is the Js property )? maybe something else ?
In this particular case:
document.createElement("p");
document.createElement("p")
the JavaScript runtime doesn't worry about the elements at all, because you didn't save the values returned. They're thrown away.
If you had written,
var p1 = document.createElementById('p');
var p2 = document.createElementById('p');
well then you've got two separate variables, and so you're keeping track of the difference.
It's important to be aware that the JavaScript interpreter itself doesn't really care too much about your DOM nodes. That's not really it's business. If you call methods and create objects, it's your problem to keep track of them.
Let's pick another real-life example: How does a human distinguish one orange from another one, in a basket? I look inside the basket, and notice that there're multiple orange-coloured, ball-shaped items
The JavaScript interpreter internally keeps track of the created objects. When an object isn't referred by anything (variables), the built-in Garbage Collector destroys the object.
It doesn't. createElement returns the object reference but if you don't assign it to anything or directly use it, it's lost.
var firstp = document.createElement("p");
var secondp = document.createElement("p");
Then you can assign an id or whatnot:
firstp.id = "first";
Keep in mind though, that this is not in the DOM yet. You have to insert it somewhere before it can be seen by the user/found with getElementById.
For that you can do something like this:
document.body.appendChild(firstp);
It doesn't. When you call document.createElement(), you create an element and it's inserted into the DOM. JavaScript can then grab certain p elements by ID, for example, or grab other p (and other) elements with getElementsByClassName().
If you'd assigned the returned values to variables, you'd be able to keep track of which one was which by referencing different variables. This is you distinguishing between them, not JavaScript, though.
You can refer to them by their index in the array of elemnts:
document.getElementsByTagName('p')[0]
You can assign id to them
document.getElementsByTagName('div')[0].id = 'first'
document.getElementsByTagName('div')[1].id = 'second'
And refer to them by id
document.getElementById('first')
It is up to you how you do it really...

Categories