Get path of object and convert it to string in JavaScript - javascript

How could I generate a string of an object's path? For example, say I want to turn the following path (not its contents) into a string:
object = grandparent.children[2].children[4].whatever;
I need a function like this:
function stringifyPath(obj) {
// somehow return "grandparent.children[2].children[4].whatever";
}

You simply can't do that in Javascript unless each object stores a reference to it's own parent and there's a known way to navigate children (like DOM nodes do).
This is because when you have an object embedded in another object (your parent/child relationship you speak of), it isn't really an object embedded in another. The child is an independent object and there's a reference to it in the parent. That same child could be stored in many different places. From Javascript's point of view, it doesn't actually have a parent. It's an object that many different things may have a reference to.
If each child object stored a reference to its own parent and there was a known way to walk children at any level, then it could be possible to write code to construct a path like you've said, but you'd have to be a lot more specific about what exactly these objects were and how you find out which child index a given object is.
For example, if these were DOM objects which meets both of the criteria (child contains reference to parent and there's a known way to navigate the children of any given object) and you wanted the root parent to be document.body, then you could do this:
function getSiblingPosition(obj) {
var siblings = obj.parentNode.children;
var elemCnt = 0;
for (var i = 0; i < siblings.length; i++){
if (siblings[i] === obj) {
return elemCnt;
} else {
if (siblings[i].nodeType === 1) {
++elemCnt;
}
}
}
}
function getPath(obj) {
var path = "";
while (obj && obj !== document.body) {
var cnt = getSiblingPosition(obj);
path = ".children[" + cnt + "]" + path;
obj = obj.parentNode;
}
path = "document.body" + path;
return path;
}
Working demo: https://jsfiddle.net/jfriend00/8w9v8kpf/

Related

Is there a way to test a css-selector query to an unappended element?

I have this code:
Element.prototype.queryTest = function(strQuery) {
var _r;
if (this.parentElement == null) {
_r = Array.prototype.slice.call(document.querySelectorAll(strQuery)).indexOf(this);
} else {
_r = Array.prototype.slice.call(this.parentElement.querySelectorAll(strQuery)).indexOf(this);
}
return !!(_r+1);
}
I am searching for some way to test a query to an unappended element.
I want to change the first code to make this work:
var t = document.createElement("span");
t.classList.add("asdfg");
console.log(t.queryTest("span.adsfg"));
If there is a way to detect if the element isn't appended I could create a new temporary unappended one and append the target one to the temporary one to test the css-selector query.
Is there a way to detect if the element hasn't been appended jet? Could the target element be accessible even after freeing the temporary parent one? I have tested it on Chrome and it is accessible but I don't know if that is the case for firefox.
I know I can use document.querySelectorAll("*") to get a list of nodes but... isn't too CPU-demmanding the process to turn this NodeList to an Array? This is why I prefer not to use that way.
Thanks in advance.
There is already a native Element.prototype.matches method which does that:
const el = document.createElement('span');
el.classList.add('test');
console.log(el.matches('span.test'));
Note that to check if a node is connected or not, there is the Node.prototype.isConnected getter.
I did it.
Element.prototype.querySelectorTest = function(strQuery) {
var _r;
if (this.parentElement != null) {
_r = Array.prototype.indexOf.call(this.parentElement.querySelectorAll(strQuery),this);
} else if (this == document.documentElement) {
_r = ((document.querySelector(strQuery) == this)-1);
} else {
_r = ((this == document.createElement("i").appendChild(this).parentElement.querySelector(strQuery))-1);
}
return !!(_r+1);
}
I changed the way it check the nodeList.
I renamed the function to a more proper name.
If the target element is the root one there's no need to make a querySelectorAll.
If you append the unappended element to a temporary one to test the child you don't loose the reference (variable value in case there is one).
This is not my native language so please consider that.

Reimplementing getElementsByClassName using Recursion - Javascript

I'm currently working on learning recursion and am trying to reimplement the getElementsByClassName function by walking the DOM using recursion. I finally feel like I've grasped the concepts but I'm having issues when I push the matching elements into a results array. Heres my code:
var getElementsByClassName = function(className){
var results = [];
var domWalker = function(node) {
for (var i=0; i<node.children.length; i++) {
if (node.children[i].classList.contains(className)) {
console.log(node.children[i])
results.push(node.children[i])
}
if (node.children[i].children.length > 0) {
domWalker(node.children[i])
}
}
return results;
};
domWalker(document.body);
console.log(results)
};
Basically, I need the results array to hold the matching elements it finds in HTML format like so:
[<div class="title"></div>, <button class="click"></button>]
... yet when I push these elements into my results array they change to the: [div.title, button.click] format.
I added the console.log statement above the call to results.push to see if the results appear in the proper format before they are pushed to the array which they do. The results being pushed to the array are the results I'm looking for, they just appear in the wrong format.
Why is push causing the format of my results to change and how can I get around this issue?
I solved this problem once upon a time. Although I haven't read through your solution, here is mine, heavily commented. I hope it helps:
var getElementsByClassName = function(className, node){
// The empty results array, which gets created whenever the function is
// called.
var results = [];
// Default the node to the document's body if it isn't set. This way, we can
// call the function recursively with child elements, but never have to
// worry about it the first time around.
node = node || document.body;
// If the node contains the class list in question, let's push it into the
// results array.
if (node.classList && node.classList.contains(className)) {
results.push(node);
}
// Now, let's fetch the child nodes of the current node.
var children = node.childNodes;
// If child nodes exist, then we proceed here.
if (children) {
// Let's now loop over all child nodes of the current node in question. This
// way, we'll be able to perform checks on each child node.
for (var i = 0; i < children.length; i++) {
// Fetch the i child node.
var child = children[i];
// At this point, we want to pass the child node back into the function,
// implementing recursion. The same checks above will occur, and if the
// node has the class name, it will be added to the results array from
// that function call.
//
// This returns an array, and we need to merge it with the current set
// of results, so we concat it.
//
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
results = results.concat(getElementsByClassName(className, child));
}
}
// We then return the combined results array from all the recursive function
// calls!
return results;
};
node.children[i] holds a reference to the HTML element
console.log() applies an implicit .toString() method giving what you see.
you need this additional code (to be extended to all possible tagNames you find):
var el = node.children[i];
el = '<' + el.tagName + (el.className ? ' class="' + el.className + '"': '') + '/>';
console.log(el);
results.push(el);

Generic tree implementation in Javascript

Is anyone aware of a generic tree (nodes may have multiple children) implementation for JavaScript?
It should be able to do atleast these things,
get parent node.
get children nodes.
get all the descendants.
remove all the descendants.
remove children nodes.
Some implementation similar to Adjacency List Model.
Background: I needed JavaScript based hierarchical data storing for my webpage i could not find a good JavaScript implementation of generic trees so what i did is i used ajax to store hierarchical data into database using Adjacency List Model and php. The problem comes when the user is opening the same page in two tabs of same browser or opened the page in two different browsers because both the instances are writing to same table reading from same table which is causing me problems any possible work around for this also answers my question.
Edit: Performance is really not my constraint at any point of time i will not have more than 50 entries.
You can try this: https://github.com/afiore/arboreal
Or this: https://github.com/mauriciosantos/buckets/ (only Binary Searched Trees, but olso other data structures)
If you need anything more sophisticated, you will need to write your own library (or at least one object with all methods you desribed).
EDIT:
This is my simple code to achieve tree functionality. Remove all descendants and remove all children is in fact the same... so:
function Node(value) {
this.value = value;
this.children = [];
this.parent = null;
this.setParentNode = function(node) {
this.parent = node;
}
this.getParentNode = function() {
return this.parent;
}
this.addChild = function(node) {
node.setParentNode(this);
this.children[this.children.length] = node;
}
this.getChildren = function() {
return this.children;
}
this.removeChildren = function() {
this.children = [];
}
}
var root = new Node('root');
root.addChild(new Node('child 0'));
root.addChild(new Node('child 1'));
var children = root.getChildren();
for(var i = 0; i < children.length; i++) {
for(var j = 0; j < 5; j++) {
children[i].addChild(new Node('second level child ' + j));
}
}
console.log(root);
children[0].removeChildren();
console.log(root);
console.log(root.getParentNode());
console.log(children[1].getParentNode());
Run it in Chrome (or other browser which supports console).
Although you did say "generic tree", what your specific requirement sounds simple enough for an already built-in DOMParser.
I respect other programmers' opinions, but still I think you can give DOM a try and see if it fits you.
Here's an simple illustration on how it works:
var tXML="<root><fruit><name>apple</name><color>red</color></fruit><fruit><name>pear</name><color>yellow</color></fruit></root>";
var tree=(new DOMParser).parseFromString(tXML,"text/xml");
//get descendants
var childs=tree.documentElement.childNodes;
for(var i=0;i<childs.length;i++)
{
if(childs[i].nodeName=="fruit")
{
document.write(childs[i].getElementsByTagName("name")[0].textContent);
document.write(": ");
document.write(childs[i].getElementsByTagName("color")[0].textContent);
document.write("<br />");
}
}
//get child node
var appl=tree.documentElement.getElementsByTagName("fruit")[0];
document.write(appl.getElementsByTagName("name")[0].textContent+"<br />");
//get parent node
document.write(appl.parentNode.nodeName);
document.write("<br />");
//remove child node
if(tree.documentElement.getElementsByTagName("color").length>1)
{
var clr=tree.documentElement.getElementsByTagName("color")[1];
clr.parentNode.removeChild(clr);
}
document.write("<textarea>"+(new XMLSerializer).serializeToString(tree)+"</textarea><br />");
//remove descendants
while(tree.documentElement.childNodes.length>0)
{
tree.documentElement.removeChild(tree.documentElement.childNodes[0]);
}
document.write("<textarea>"+(new XMLSerializer).serializeToString(tree)+"</textarea>");
I didn't "simplified" those long function names, so you may get a better idea.

Can I loop through 2 objects at the same time in JavaScript?

related (sort of) to this question. I have written a script that will loop through an object to search for a certain string in the referring URL. The object is as follows:
var searchProviders = {
"google": "google.com",
"bing": "bing.com",
"msn": "search.msn",
"yahoo": "yahoo.co",
"mywebsearch": "mywebsearch.com",
"aol": "search.aol.co",
"baidu": "baidu.co",
"yandex": "yandex.com"
};
The for..in loop I have used to loop through this is:
for (var mc_u20 in mc_searchProviders && mc_socialNetworks) {
if(!mc_searchProviders.hasOwnProperty(mc_u20)) {continue;}
var mc_URL = mc_searchProviders[mc_u20];
if (mc_refURL.search(mc_URL) != -1) {
mc_trackerReport(mc_u20);
return false;
}
Now I have another object let's call it socialNetworks which has the following construct:
var socialNetworks = {"facebook" : "facebook.co" }
My question is, can I loop through both of these objects using just one function? the reason I ask is the variable mc_u20 you can see is passed back to the mc_trackerReport function and what I need is for the mc_u20 to either pass back a value from the searchProviders object or from the socialNetworks object. Is there a way that I can do this?
EDIT: Apologies as this wasn't explained properly. What I am trying to do is, search the referring URL for a string contained within either of the 2 objects. So for example I'm doing something like:
var mc_refURL = document.referrer +'';
And then searching mc_refURL for one of the keys in the object, e.g. "google.com", "bing.com" etc. 9this currently works (for just one object). The resulting key is then passed to another function. What I need to do is search through the second object too and return that value. Am I just overcomplicating things?
If I understand your question correctly, you have a variable mc_refURL which contains some URL. You want to search through both searchProviders and socialNetworks to see if that URL exists as a value in either object, and if it does you want to call the mc_trackerReport() function with the property name that goes with that URL.
E.g., for mc_refURL === "yahoo.co" you want to call mc_trackerReport("yahoo"), and for mc_ref_URL === "facebook.co" you want to call mc_trackerReport("facebook").
You don't say what to do if the same URL appears in both objects, so I'll assume you want to use whichever is found first.
I wouldn't create a single merged object with all the properties, because that would lose information if the same property name appeared in both original objects with a different URL in each object such as in an example like a searchProvider item "google" : "google.co" and a socialNetworks item "google" : "plus.google.com".
Instead I'd suggest making an array that contains both objects. Loop through that array and at each iteration run your original loop. Something like this:
var urlLists = [
mc_searchProviders,
mc_socialNetworks
],
i,
mc_u20;
for (i = 0; i < urlLists.length; i++) {
for (mc_u20 in urlLists[i]) {
if(!urlLists[i].hasOwnProperty(mc_u20))
continue;
if (mc_refURL.search(urlLists[i][mc_u20]) != -1) {
mc_trackerReport(mc_u20);
return false;
}
}
}
The array of objects approach is efficient, with no copying properties around or anything, and also if you later add another list of URLs, say programmingForums or something you simply add that to the end of the array.
You could combine the two objects into one before your loop. There's several approaches here:
How can I merge properties of two JavaScript objects dynamically?
var everything = searchProviders;
for (var attrname in socialNetworks) { everything[attrname] = socialNetworks[attrname]; }
for(var mc_u20 in everything) {
// ...
}
for (var i = 0; i < mc_searchProviders.length; i++) {
var searchProvider = mc_searchProviders[i];
var socialNetwork = mc_socialNetworks[i];
if (socialNetwork != undefined) {
// Code.
}
}
Or am i horribly misunderstanding something?

Create an array with tree elements in Javascript

I need to create an array from tree elements in Javascript and being a newbie I don't know how to achieve this.
pseudo-code :
function make_array_of_tree_node(tree_node)
{
for (var i = 0; i < tree_node.childCount; i ++) {
var node = tree_node_node.getChild(i);
if (node.type ==0) {
// Here I'd like to put a link (node.title) in an array as an element
} else if (node.type ==6) {
// Here the element is a folder so a I need to browse it
make_array_of_tree_node(node)
}
}
}
// Some code
make_array_of_tree_node(rootNode);
// Here I'd like to have access to the array containing all the elements node.title
You can declare an array like this:
var nodes = [];
Then you can add things to it with:
nodes.push(something);
That adds to the end of the array; in that sense it's kind-of like a list. You can access elements by numeric indexes, starting with zero. The length of the array is maintained for you:
var len = nodes.length;
What you'll probably want to do is make the array another parameter of your function.
edit — To illustrate the pattern, if you've got a recursive function:
function recursive(data, array) {
if ( timeToStop ) {
array.push( data.whatever );
}
else {
recursive(data.subData, array);
}
}
Then you can use a second function to be the real API that other code will use:
function actual(data) {
var array = [];
recursive(data, array); // fills up the array
return array;
}
In JavaScript, furthermore, it's common to place the "recursive" function inside the "actual" function, which makes the recursive part private and keeps the global namespace cleaner.

Categories