Hash by object reference in javascript - javascript

Anyone ever need to hash by object reference in javascript?
Maybe you want to group the children of uls by a hash of their DOM nodes, or attributes by instances of constructed objects, or many other things.
I have read this and don't want to use Weakmaps until they're stable.
The only way I can think to do it is storing parents in an array and enforcing reference uniqueness there, then hashing by the index of the reference in the array. (example is finding all the common UL parents of LIs):
var lis = document.getElementsByTagName('li'); // Get all objects
var parent, key, li;
for (var i=0; i<lis.length; i++ ) { // For every LI
storeItemByParent(lis[i], lis[i].parentNode);
}
var parents = [];
var itemsByParent = {};
function storeItemByParent (item, parent){
var key;
// Does this parent already exist? if so, use that index as the hash key
for (var j=0; j<parents.length; j++) {
if(parents[j] === parent) {
key = j;
break;
}
}
// This is a new parent? If so, track it and use that index as the hash key
if (key == undefined) {
parents.push(parent);
key = parents.length;
}
// Finally! The lis in a hash by parent "id".
itemsByParent[key] = itemsByParent[key] || [];
itemsByParent[key].push(item);
}
Is there a better way to store attributes or children or other things attached to an object instance without adding them as properties of the object itself?

You've pretty much got the right approach there, though I'd wrap it up nicely behind an interface, complete with my own add and find methods.
You can simply your coding somewhat by using <array>.indexOf, supported in all modern browsers and easily polyfilled when needed.
There is a downside to your approach however:
Should an element by removed from the DOM, that element won't be garbage collected because your array is still holding onto a reference to it.
While this isn't a show stopper, of course, it is worth keeping in mind.
There is, however, a totally different approach that you can take as well. May not be better -- but different.
Please forgive any minor errors in the code, I am typing this freehand:
function elementDataHash = {};
function setElementData(el, data) {
var hash = el.getAttribute("data-element-hash");
if !(hash) {
// http://stackoverflow.com/questions/6860853/generate-random-string-for-div-id
hash = generateRandomString();
el.setAttribute("data-element-hash", hash);
}
elementDataHash[hash] = data;
}
function getElementData(el) {
var hash = el.getAttribute("data-element-hash");
return elementDataHash[hash];
}

Related

How would I dynamically convert form fields to multi-dimensional js object?

I'm struggling to dynamically convert a set of inputs into a multi-dimensional object for passing in an ajax call.
Assume I have a Person, with multiple Addresses.
My fields currently look like this:
<input name='Person[name]' value='Bradley'/>
<input name='Person[addresses][home]' value='123 Anywhere Drive.'/>
<input name='Person[addresses][work]' value='456 anywhere Road.'/>
How would one convert my fields into ab object that looks like this:
Person :
{
name: 'Bradley',
addresses:
{
home: '123 Anywhere Drive.',
work: '456 anywhere Road.'
}
}
I need to do this dynamically (function needs to work regardless of the inputs provided) and work at N-depth.
(Note: jQuery available).
http://jsfiddle.net/w4Wqh/1/
Honestly I think there's a way to do this in a regex.. but I couldn't figure it out. So, it's a bit of ugly string manipulation. Either way, this should get you on the right track I think:
function serialize () {
var serialized = {};
$("[name]").each(function () {
var name = $(this).attr('name');
var value = $(this).val();
var nameBits = name.split('[');
var previousRef = serialized;
for(var i = 0, l = nameBits.length; i < l; i++) {
var nameBit = nameBits[i].replace(']', '');
if(!previousRef[nameBit]) {
previousRef[nameBit] = {};
}
if(i != nameBits.length - 1) {
previousRef = previousRef[nameBit];
} else if(i == nameBits.length - 1) {
previousRef[nameBit] = value;
}
}
});
return serialized;
}
console.log(serialize());
Quick explanation. This just grabs anything with a 'name' attribute, and then iterates over them. For each iteration, it grabs the name and splits it on '['. This gets you basically how far into the object you need to put things. So, for Person[addresses][work], you would get Person, addresses], work].
Then, there's the tricky part. Since objects are always passed around by reference, we can see if the serialized variable has 'Person' in it. If not, it adds it, and sets the value to an empty object.. which is generic enough to be used for storing more things, or replaced if necessary. If there are no more levels that we need to go through, it just takes the value of the element and assigns it to the reference it has. Otherwise, the code grabs a reference to whatever it just made, and loops again, performing the same operation. So, for Person[addresses][work]..
Does serialized.Person exist? No. Setting serialized.Person to {}. This is not the end of the loop, store reference to serialized.Person as previousRef.
Does previousRef.addresses exist? (serialized.Person.addresses) No. Setting previousRef.addresses to {}. This is not the end of the loop, store reference to previousRef.addresses as previousRef.
Does previousRef.work exist? (serialized.Person.addresses.work) No. Setting previousRef.work to {}. Wait. This is the end of the loop. Setting previousRef.work to the value in the element.

How to use strings to find and update an object

In javascript, I'm loading an json object, and trying to update values within it. Elements in html have ids that match their path, such that the "values.one" element in this:
{"values":{"one":"val1","two":"val2"},"OtherStuff":"whatever"}
would be related to this element:
<input id="values_one" type="text">val1</div>
When this element loses focus, I want to grab the value of "#values_one" (using jQuery) and set use whatever value is there into my json object. I've figured out how to get the value from the existing json object (see my horrid code below), but I end up with nothing but the value, so setting it doesn't affect the json object. Any suggestions on how I can do this (assuming that I don't know whether the element that lost focus was "values_one", "values_two", or any other element that might map to the json object)?
Here's the code that I have at this point (that isn't working), but happy to scrap it for something that actually works:
var saveEdit = function() {
var data = getJson();
pathElements = $(this).attr('id').split('_');
length = pathElements.length;
var path = data[pathElements[0]];
index = 1;
while (index < length) {
path = path[pathElements[index]];
length -= 1;
}
path = $(this).text(); //resets "path", but not data.values.one
$(this).unbind();
}
Loop one shorter than the length, so that you get the element that contains the property that matches the last identifier:
var path = data;
for (index = 0; index < length - 1; index++) {
path = path[pathElements[index]];
}
path[pathElements[length - 1]] = $(this).text();
Firstly, there's no such thing as a "JSON Object". Yes, I'm being pedantic, but you should either have a "JSON String", or a "Javascript Object".
It looks like you're trying to modify a JSON string via events, instead of just having the object itself available to reference and modify. Why bother keeping it in a string? When you're ready to export the data (perhaps saving to a db), then just stringify(); the object and be on your way.
Take a look at the following jsFiddle for a working implementation that you can build off of: http://jsfiddle.net/julianlam/WRZPF/

How can I select elements on dom (with jQuery) that has some data attribute that ends with "created"?

I have all my elements, on a project, that are being transferred with data attributes so the javascript can know what to do.
But I have one problem, one part of the application must get all the created variables to compare time, and I have they saved like (data-product-created, data-category-created, data-link-created, etc...). It would be a huge headache if I had to put them manually on the jQuery selector...
Do jQuery has some method to search custom data attributes existence?
Like: element[data-(.*)-created]
You could create a low level Javascript method to loop through elements and pull their created values:
var getCreateds = function($els) {
var returnSet = [];
$els.each(function(i, el){
var elDict = {'el': el};
$.each(el.attributes, function(i, attr){
var m = attr.name.match(/data\-(.*?)\-created/);
if (m) elDict[m[1]] = attr.value;
});
returnSet.push(elDict);
});
return returnSet;
};
See demo
Based on mVChr answer (THANK YOU), i've rewritten the code on a simple, better and clean code:
jQuery('div').filter(function(){
for( var i in this.attributes ){
if(typeof this.attributes[i] !== 'object') continue;
if(this.attributes[i].name.match(/data\-(.*?)\-created/)) return true;
}
return false;
})
Changes:
+ Verifying attribute type... some attributes are browser internal functions or callbacks.
~ Using filter handler from jQuery, its better and cleaner way.
~ Using for instead of $.each, faster indeed.

Is it possible to link container to other one?

I have an array of data. I have put this data on my site in different places over different attributes, how innerHTML value placeholder etc.
Is it possible to link this values with the array from where I can take data? So that when I change the data in array, it going automatic changed on the site?
Also I try to show how I did it mean:
var test = Array();
test['place1'] = 'NY';
var myspan = document.createElement('span');
myspan.innerHTML = test['place1'];
On some event the value of test['place1'] is changed to 'LA', and at the same moment the value of myspan.innerHTML must be changed too.
Native JS only please.
This needs to be manually managed. A simple solution would be something like this:
function Place(container, initVal) {
this.container = container ? container : {};
this.set(initVal);
}
Place.prototype.place = "";
Place.prototype.get = function() {
return this.place;
}
Place.prototype.set = function(val) {
this.place = val;
this.container.innerHTML = val;
}
var test = {}; // object
test['place1'] = new Place(document.createElement('span'), "NY")
test['place1'].set('New Value');
This is not a full-feature solution, but gives you an idea of the coordination that needs to take place.
If you're only supporting modern browsers, then the syntax can be cleaned up a bit by using getters/setters.
In the future, you'll be able to use Proxy, which will make it even easier and cleaner.
There is no native way to bind an attribute of an HTML element to the values of an array, but you aren't actually using an array; you're using an object, and it is a simple matter to define special features on an object. For example:
First, define your object:
function boundArray(){
this._bindings = {};
this.setBinding = function(key,element){
this._bindings[key] = element;
};
this.setValue = function(key,value){
this[key] = value;
if(this._bindings[key]){
this._bindings[key].innerHTML = value;
}
}
}
Then use it in your code:
// create a new instance of the boundArray
var test = new boundArray();
// create the HTML element to use, and add it to the DOM
var myspan = document.createElement('span');
document.body.appendChild(myspan);
// bind the HTML element to the required key in the boundArray
test.setBinding('place1',myspan);
// Now every time you set that key on the boundArray (using setValue), it will also change the innerHTML field on the element
test.setValue('place1','NY');
// You can access your information from the boundArray in the usual ways:
var somevar = test.place1;
var anothervar = test['place1'];
What you are talking about is an MVVM solution. Most MVVM JavaScript solutions uses some object that represents an observable, which is a field within the object. When the value in the object changes, the observable lets the framework know to update the DOM. It also listens to the DOM for change events, and updates the object in reverse. For arrays, it's a similar process: it listens for adds or removes of the array, and updates the UI accordingly.
As #MCL points out in the comments on this post below, there is a way to watch changes to an object, and it isn't overly difficult to generically attach to an element on the DOM. However, There are a lot of good frameworks out there that make this REALLY easy, so that may be something to consider.

JSON how find another value at the same index from a value in Javascript Object

A simple question I'm sure, but I can't figure it out.
I have some JSON returned from the server
while ($Row = mysql_fetch_array($params))
{
$jsondata[]= array('adc'=>$Row["adc"],
'adSNU'=>$Row["adSNU"],
'adname'=>$Row["adname"],
'adcl'=>$Row["adcl"],
'adt'=>$Row["adt"]);
};
echo json_encode(array("Ships" => $jsondata));
...which I use on the client side in an ajax call. It should be noted that the JSON is parsed into a globally declared object so to be available later, and that I've assumed that you know that I formated the ajax call properly...
if (ajaxRequest.readyState==4 && ajaxRequest.status==200 || ajaxRequest.status==0)
{
WShipsObject = JSON.parse(ajaxRequest.responseText);
var eeWShips = document.getElementById("eeWShipsContainer");
for (i=0;i<WShipsObject.Ships.length;i++)
{
newElement = WShipsObject.Ships;
newWShip = document.createElement("div");
newWShip.id = newElement[i].adSNU;
newWShip.class = newElement[i].adc;
eeWShips.appendChild(newWShip);
} // end for
}// If
You can see for example here that I've created HTML DIV elements inside a parent div with each new div having an id and a class. You will note also that I haven't used all the data returned in the object...
I use JQuery to handle the click on the object, and here is my problem, what I want to use is the id from the element to return another value, say for example adt value from the JSON at the same index. The trouble is that at the click event I no longer know the index because it is way after the element was created. ie I'm no longer in the forloop.
So how do I do this?
Here's what I tried, but I think I'm up the wrong tree... the .inArray() returns minus 1 in both test cases. Remember the object is globally available...
$(".wShip").click(function(){
var test1 = $.inArray(this.id, newElement.test);
var test2 = $.inArray(this.id, WShipsObject);
//alert(test1+"\n"+test2+"\n"+this.id);
});
For one you can simply use the ID attribute of the DIV to store a unique string, in your case it could be the index.
We do similar things in Google Closure / Javascript and if you wire up the event in the loop that you are creating the DIV in you can pass in a reference to the "current" object.
The later is the better / cleaner solution.
$(".wShip").click(function(){
var id = $(this).id;
var result;
WShipsObject.Ships.each(function(data) {
if(data.adSNU == id) {
result = data;
}
});
console.log(result);
}
I could not find a way of finding the index as asked, but I created a variation on the answer by Devraj.
In the solution I created a custom attribute called key into which I stored the index.
newWShip.key = i;
Later when I need the index back again I can use this.key inside the JQuery .click()method:
var key = this.key;
var adt = WShipsObject.Ships[key].adt;
You could argue that in fact I could store all the data into custom attributes, but I would argue that that would be unnecessary duplication of memory.

Categories

Resources