I currently have a nested javascript object of unknown depth. The object is generated purely dynamically, so I don't know the names of the parents of the property I want to delete ( I could rework some stuff to get them If I Have To, but I'm trying to avoid that).
I currently have a function called search_tree(name) that searches through all the properties of the object until it finds a property name : "name" , and then returns that object for adding data at that location of the tree.
However, I now need to delete that object from the tree, and have not yet gotten it to work. I have tried:
obj = search_tree(name);
delete obj;
and that doesn't work, I'm assuming because obj isn't actually the object in the tree, but a reference to it?
delete search_tree(name);
also yielded no results. Can this be done in this way, or will I need to alter the search_tree function to somehow return the heritage as well (or just make a different one)? Thanks
The code from search_tree
function search_tree(element, matchingName){
if(element.name == matchingName){
return element;
}else if (element.children != null){
var result = null;
for(var child in element.children){
result = searchTree(element.children[child], matchingName);
}
return result;
} else {
return null;
}
}
Just realized this function may be a bit unclear without explanation. Each object in the tree has a child object called "children," in which any number of other objects will be stored. I added the extra "children" layer because the objects often have child objects that I do not want to be searched through as part of the tree. Element is the object being searched through
Are you looking to remove the object from the tree? And that's all you're looking to do?
If so, then store the "parent" node of the tree inside of your search.
Write a second function -- maybe prune_tree, where you pass in the parent and the object (or an object with both as properties), and then do a for ... in search of parent. If parent[key] === object, delete parent[key];
You now have a full tree where that particular parent no longer contains that object (or you should).
Given that search_tree should be recursive, give it one more parameter (parent), which you feed it once for every level of depth you hit (each child will have the same parent). Be sure to account for the parent being the root (and thus not having a parent).
When you find the object you want to kill, return { object : objectNode, parent : parentNode };
Put that into your prune function.
The reference to parentNode means that when you delete parentNode.objectNode, it will also be deleted from the tree (because it's just a reference, afterall).
EDIT:
Based on the above:
function prune_tree (parent, child) {
var key = "";
for (key in parent) { if (parent.hasOwnProperty(key) && parent[key] === child) {
delete parent[key];
}
}
function search_tree (name, element, parent) {
var key = "";
if (element.name === name) {
return prune_tree(parent, element);
} else if (!element.children) {
return null;
} else {
parent = element.children;
for (key in children) { if (children.hasOwnProperty(key) {
return search_tree(name, children[key], parent);
}}
}
}
I'm not 100% sure of what you're actually doing when you're recursing (like whether you're depending on a specific return value, or whatever... I don't even know if there are multiple objects which might have that same name, on different branches -- including the root node).
But something like what I've got there should recurse your tree.
It's setting parent to element.children (the place where the children are stored), and then looping through each object in children to call the function over again, passing in parent to the next set. So element is going to be a child element, and parent is going to be the children object which holds it.
If element.name is an exact match to name, then call the separate function prune_tree, and pass it the saved parent element, and the current child element.
Inside of prune_tree, just iterate through the keys of parent, until you find the child element you're looking for. Then delete it off of the parent.
There shouldn't really be any surprises here, and this particular set of functions will likely keep on running until every single node on every single branch has been visited... ...so if you've got more than, say, 2000 nodes, you might want to consider breaking this up into chunks, or else it's going to break the call stack of some browsers.
Assuming you've got only 1000 nodes or less, or you're only targeting browsers with bigger stacks, this should prune everything with the same name.
Again, this all comes down to whether this is your intended outcome, or if you're depending on getting return values, to do something with them, or if you just expect to fire this, pass it the root of a tree, and expect the function to purify the branches.
I'm also not sure whether you want to do something with the pruned objects, or if the point is just to have a tree clean from the tyranny of whatever is named "name".
Related
Fiddle : http://jsfiddle.net/p35cobgx/3/
Below is my tree structure :
Node
Node-1
Node-1-1......
Now I want to store back reference of all parents for each of the nodes like below :
Node
Node-1 (should store back reference of only Node because this is
last parent for Node-1)
Node-1-1(should store back reference of all parents i.e Node-1 and
Node because Node is the last parent for Node-1-1).
But problem with my code is i am unable to store reference of Node for Node-1-1.
Use this condition for assigning the parent:
obj.parent = data.parent || null;
I'm not sure if that's exactly the answer you wanted, but here it is:
You can access a parent node by using:
node.parentNode
This is a property and holds the reference for the parent node.
Also, it's sometimes better to access parent element, instead of node, because whitespace can also be a node, so you can use this property:
node.parentElement
The algorithm I would use is to store only the immediate parent in the node.
So if A is the parent to B and B of C
That makes it A->B->C
C.parent = B;
B.parent = A;
If I wanted all parents of C, I would do the below
var parentArray = getParent(C).slice();
this.parents = [];
//use parentArray as you like now!
function getParent(obj){
if(obj.parent){
this.parents.push(obj.parent) //this.parents is an array
getParent(obj.parent);
}else{
return this.parents;
}
}
So if you make a call getParent(C); you get all its parents B and A.
If you make a call getParent(B); you get only A.
If you make a call getParent(A); you get no parents as it doesn't have any parents.
Hope that helps!
I'm building some app which scans the DOM for elements and their children. Right now, I can get the data I need with the following line of code:
var bodyObj = document.getElementsByTagName("BODY")[0].children;
The problem, that the bodyObj object is spammed with unnecessary methods and attributes, I only want to keep the children method and clean the rest. Any way of achieving this?
PS
Pure Js.
Body object with only the children property
You might think to try something like:
var bodyObj = document.getElementsByTagName("BODY")[0];
for (var key in bodyObj){
if (bodyObj.hasOwnProperty(key) && key != 'children'){
delete bodyObj[key];
}
}
...like Florian Cabirol suggested in their answer. However, the <body> object's properties are non-enumerable, meaning they won't show up in the loop. To get the non-enumerable properties, you might try (in ES5+):
Object.getOwnPropertyNames(bodyObj);
But it still won't get them. Next, you might think, "I'll just make an array of all possible property/method names that are in HTMLBodyElements, and loop through them, removing them if they exist." However, if you do something like:
delete bodyObj.baseURI;
console.log(bodyObj.baseURI);
You'll notice that it didn't delete it. That's because you can't remove properties from a DOM object. Attempts to redefine them or to delete them will silently fail.
Body object's children's HTMLElements
To get document.getElementsByTagName("BODY")[0].children without any of its properties/methods, you could do:
var bodyObj = document.getElementsByTagName("BODY")[0].children;
bodyObj = Object.keys(bodyObj).map(function(key) { return bodyObj[key]; });
This would convert it from an HTMLCollection to an array, if you're okay with that. You could always wrap it in an object: bodyObj = {children: bodyObj};.
You can do something like this :
for (var key in bodyObj){
if (bodyObj.hasOwnProperty(key) && key != 'children'){
delete bodyObj[key];
}
}
I am trying to associate some "private" data with DOM elements. Rather than adding that data to the DOM element itself (I'd like to avoid changing the DOM element), I have a separate data object that I want to use as a map.
Rather than:
document.GetElementById('someElementId').privateData = {};
I want to do
internalPrivateDataMap[document.GetElementById('someElementId')].privateData = {};
Not all the elements have an id field, and some are created dynamically, so I can't use the id as the key.
This works fine for most elements, but for "a" elements, the key being used seems to be the href of the element, I think because the DOM defines a toString() function for a elements.
The result of this is that if I have two "a" elements with the same href, they are sharing privateData, which I don't want.
My current workaround is to generate an internal uniqueID I can use as a key, but that requires me to modify the DOM element, which I am trying to avoid.
As you noticed, this doesn't work reliably and I know no way to make it work without either giving every element a (generated) ID or at least assign a unique ID to a new custom element field; DOM nodes simply don't have the necessary properties to work as keys in a map.
So you really have these solutions left:
Assign each element a generated ID unless it already has one
Assign a unique ID to a new private field. That way, you can keep the memory impact per DOM node small and still keep your private data in a different place. Don't forget that you need to clean the private data somehow when the DOM elements are deleted.
Use something like jQuery which has element.data() to read and put private data into a DOM element
Use your own element.privateData = {}; Note that you still need cleanup for event handlers which keep references to the element or you will have unexpected memory leaks.
For anyone who's ok with an inefficient solution, you could create a custom map class that leverages node equality. Here's the basic idea, extend as needed:
// Map that has dom elements as keys. Note: O(N) lookup, insertion and deletion
class ElementMap {
constructor() {
this.pairs = [];
}
set(element, value) {
const pair = this.pairs.find(p => p.element === element);
if (pair) {
pair.value = value;
} else {
this.pairs.push({ element, value });
}
}
get(element) {
return this.pairs.find(p => p.element === element)?.value || null;
}
delete(element) {
const idx = this.pairs.findIndex(p => p.element === element);
if (idx >= 0) {
this.pairs = [...this.pairs.slice(0, idx), ...this.pairs.slice(idx + 1)];
}
}
}
I'm experiencing an odd behavior (maybe it isn't odd at all but just me not understanding why) with an javascript array containing some objects.
Since I'm no javascript pro, there might very well be clear explanation as to why this is happening, I just don't know it.
I have javascript that is running in a document. It makes an array of objects similar to this:
var myArray = [{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...];
If I print out this array at the place it was created like JSON.stringify(myArray), I get what I was expecting:
[{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...]
However, if I try to access this array from a child document to this document (a document in a window opened by the first document) the array isn't an array any more.
So doing JSON.stringify(parent.opener.myArray) in the child document will result in the following:
{"0":{"Id":"guid1","Name":"name1"},"1":{"Id":"guid2","Name":"name2"},...}
And this was not what I was expecting - I was expecting to get the same as I did in teh parent document.
Can anyone explain to me why this is happening and how to fix it so that the array is still an array when addressed from a child window/document?
PS. the objects aren't added to the array as stated above, they are added like this:
function objTemp()
{
this.Id = '';
this.Name = '';
};
var myArray = [];
var obj = new ObjTemp();
obj.Id = 'guid1';
obj.Name = 'name1';
myArray[myArray.length] = obj;
If that makes any difference.
Any help would be much appreciated, both for fixing my problem but also for better understanding what is going on :)
The very last line might be causing the problem, have you tried replacing myArray[myArray.length] = obj; with myArray.push(obj);? Could be that, since you're creating a new index explicitly, the Array is turned into an object... though I'm just guessing here. Could you add the code used by the child document that retrieves myArray ?
Edit
Ignore the above, since it won't make any difference. Though, without wanting to boast, I was thinking along the right lines. My idea was that, by only using proprietary array methods, the interpreter would see that as clues as to the type of myArray. The thing is: myArray is an array, as far as the parent document is concerned, but since you're passing the Array from one document to another, here's what happens:
An array is an object, complete with it's own prototype and methods. By passing it to another document, you're passing the entire Array object (value and prototype) as one object to the child document. In passing the variable between documents, you're effectively creating a copy of the variable (the only time JavaScript copies the values of a var). Since an array is an object, all of its properties (and prototype methods/properties) are copied to a 'nameless' instance of the Object object. Something along the lines of var copy = new Object(toCopy.constructor(toCopy.valueOf())); is happening... the easiest way around this, IMO, is to stringency the array withing the parent context, because there, the interpreter knows it's an array:
//parent document
function getTheArray(){ return JSON.stringify(myArray);}
//child document:
myArray = JSON.parse(parent.getTheArray());
In this example, the var is stringified in the context that still treats myArray as a true JavaScript array, so the resulting string will be what you'd expect. In passing the JSON encoded string from one document to another, it will remain unchanged and therefore the JSON.parse() will give you an exact copy of the myArray variable.
Note that this is just another wild stab in the dark, but I have given it a bit more thought, now. If I'm wrong about this, feel free to correct me... I'm always happy to learn. If this turns out to be true, let me know, too, as this will undoubtedly prove a pitfall for me sooner or later
Check out the end of this article http://www.karmagination.com/blog/2009/07/29/javascript-kung-fu-object-array-and-literals/ for an example of this behavior and explanation.
Basically it comes down to Array being a native type and each frame having its own set of natives and variables.
From the article:
// in parent window
var a = [];
var b = {};
//inside the iframe
console.log(parent.window.a); // returns array
console.log(parent.window.b); // returns object
alert(parent.window.a instanceof Array); // false
alert(parent.window.b instanceof Object); // false
alert(parent.window.a.constructor === Array); // false
alert(parent.window.b.constructor === Object); // false
Your call to JSON.stringify actually executes the following check (from the json.js source), which seems to be failing to specify it as an Array:
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
//stringify
I'm making a Google Chrome Extension that uses context menus as its main UI. Each menu item triggers the same content script, but with different parameters. What I basically did is store every item (and its corresponding data) in the form of a JSON object that has the following form :
{name, parent_id, rule_number, meta_array[], childCount}
name, child_count and parent_id are used to create the hierarchy when the context menus are built. The data that's passed to the script is rule_number (int) and meta_array (array of strings). All of these objects are stored into an array called indexData[].
When a menu item is clicked, the id provided is just used as an index in the "indexData" array to get the right data and pass it to the script.
For example:
// Iterates through the objects
for(var j = 0; j < objectsArray.length; j++) {
// Context menu created with unique id
var id = chrome.contextMenus.create({
"title": objectArray[j].name,
"onclick": injectScript,
"parentId": objectsArray[j].parent_id });
// Stores the objects at the corresponding index
indexData[id] = objectsArray[j]; }
Now, there was a particular large set of data that comes back often. Instead of listing every single of these elements every time I wanted them as part of my menu, is just added a boolean parameter to every JSON object that needs this set of data as its children. When the menus are created, a function is called if this boolean is set to true. The script then just iterates through a separate list of objects and makes them children of this parent object. The created children even inherit certain things from the parent object.
For example, if a parent object had a meta_array like such ["1", "2", "3", "4"], its children could all look like so ["1", "2", custom_children_data[3], "4"].
The problem is that this last part doesn't work. While the children are created just fine and with the right name, the data that's associated with them is wrong. It's always going to be the data of the last object in that separate list. This is what the function looks like:
// Iterate through children list
for(var i = 0; i < separateList.length; i++){
// Just copying the passed parent object's data
var parentData = data;
var id = chrome.contextMenus.create({
"title": separateList[i].name, // Get item [i] of the children list (works fine)
"onclick": injectScript,
"parentId": parentId // Will become a child of parent object
});
// Trying to change some data, this is where things go wrong.
parentData.meta[2] = separateList[i].meta;
// Save in indexData
indexData[id] = parentData; }
On the loop's first iteration, parentData.meta[2] gets the right value from the list and this value is thereafter saved in indexdata. But on subsequent iterations, all the values already present in indexData just get swiped away and replaced by the latest data being read from the list. When the last value is read, all the newly added elements in indexData are therefore changed to that last value, which explains my problem. But why on earth would it do that ? Does Java somehow treat arrays by address instead of value or something in this case ?
Maybe I'm missing something really obvious, but after many attempts I still can't get this to work properly. I tried to be as specific as possible in my description, but I probably forgot to mention something, so if you want to know anything else, just ask away and I'll be happy to provide more details.
Thanks.
The problem would be indexData[id] = parentData where you are making indexData[id] a reference to parentData, and then modifying parentData on the next iteration of your loop.
Since parent data is not a simple array (It contains at least one array or object), you cannot simply use slice(0) to make a copy. You'll have to write your own copy function, or use a library which has one.
My guess is that this is where your problem lies:
// Just copying the passed parent object's data
var parentData = data;
This does not, in fact, copy the data; rather, it creates a reference to data, so any modifications made to parentData will change data as well. If you're wanting to "clone" the data object, you'll have to do that manually or find a library with a function for doing so.