Fabien Huet

Fabien
Huet

Web ninja //
CTO on demand

Home Github About me

How to « find deep » and « get parent » in JavaScript nested objects with recursive functions and the reference concept (level beginner)

in JavaScript

Working with nested objects is frequent in JavaScript ; so I made this screen cast around this question. But the goal here is to help JavaScript beginners to understand how to build a simple recursive function and to get a better intuition about the reference system in JavaScript. The full text contents is in the post and you have also a fiddle to play with the code and see it in action.

The first recursive function : to display a hierarchy

The first recursive function we are going to build will display a hierarchy. But before, we need a few Datas.

A few datas

Let’s build a « data » object that represents the hierarchy you want to display in a tree. In real life project, this will be generated from an external JSON you’ve loaded.

var datas = {
    'tree': [
        {
            'name': 'name1',
            'tree': [
                {'name': 'name2'},
                {'name': 'name3'},
                {
                    'name': 'name4',
                    'tree': [
                        {'name': 'name5'},
                        {'name': 'name6'}
                    ]
                },
                {'name': 'name7'}
            ]
        },
        {
            'name': 'name8',
            'tree': [
                {'name': 'name9'}
            ]
        }
    ]
}

The function itself

The general idea is to make a function « buildTree » that will take the « datas.tree » array as parameter, and for each item in this array, it will add an element to the DOM based on the item’s « name » property. In the same time the loop will test the object to see if it has a « tree » array property. And if there’s one, it will call again the function with the new founded array as parameter.

// Build the tree :
var buildTree = function(tree) {
_.each(tree, function(item) {
document.body.innerHTML += item.name;
if(item.tree) buildTree(item.tree);
});
}
buildTree(datas.tree);

This was a very simple recursive function calling itself when needed.

Digression : Underscorejs

There’s one thing you may not be familiar with in this function : the _ syntax. It’s not vanilla javascript, this is a function provided by a wonderful library : Underscore.js (actually, I use lo-dash in my projects but they share the same API). As said on the website, this « is a utility-belt library for JavaScript ». It provides almost a hundred functions that (in my opinion) should be native in javascript.

If you don’t know / don’t use it, you really should give it a try. The syntax is straight forward. The library is light. There are many functions like findWhere, debounce, memoize, isNumber… that you will end up writing by yourself (but way less optimized). And the basic functions like _.each are more intuitives and easier to use than the native foreach loop or a for loop.

Plus, if you have some spare time, go read the development source code with all it’s comments. Some parts are genius and you will learn a lot.

Improve the display

The elements are displayed, but this is not really interesting, we have :

name1name2name3name4name5name6name7name8name9name10name11

Let’s improve the code to actually display the hierarchy by adding a container with a text node to a parent container and pass this new container as a parameter to the function.

// Build the tree :
var buildTree = function(tree, container) {
    _.each(tree, function(item) {
        var newContainer = document.createElement('div');
        var nameText = document.createTextNode(item.name);
        newContainer.appendChild(nameText);

        container.appendChild(newContainer);
         if(item.tree) buildTree(item.tree, newContainer);
    });
}
buildTree(datas.tree, $('div#container')[0]);

The tree should now look like that :

name1
----name2
----name3
----name4
--------name5
--------name6
----name7
name8
----name9
----name10
--------name11

Update the model

Nota bene : if your are not familiar with the use of the word « model » in a development context think about it as the « datas » of your view.

What if you want to update the « name » of, for example, the « name5 » object ? At first, you will need to be able to target that object. Of course, the object is stored here : datas.tree[0].tree[2].tree[0] and this is a way to target it.

But if you want to make an update after a user input, the only information you are going to get from the DOM is probably « name5 ». So, to target it in the model, you’ll need to build a reference to that object.

Click events

Let’s add some buttons and click events :

// Build the tree :
var buildTree = function(tree, container) {
    _.each(tree, function(item) {
        var newContainer = document.createElement('div');
        var button = document.createElement('button');
        button.id = item.name;
        button.innerHTML = item.name;
        newContainer.appendChild(button);

        container.appendChild(newContainer);
         if(item.tree) buildTree(item.tree, newContainer);
    });
}
buildTree(datas.tree, $('div#container')[0]);

$('div#container').on('click', 'button', function (e) {
    alert(e.target.id);
});

Now, when we click on an element of the hierarchy, it alerts it’s id. Great !

How this click event works

Another small digression about a library : jQuery.

The following explanation is only interesting for you if you barely know javascript and jQuery. Otherwise, you can skip it.

Let’s focus on this line :

$('div#container').on('click', 'button', function (e);

This is a jQuery function. jQuery is a JavaScript library that’ll make your life easier to manipulate the DOM. To really master JavaScript, you should be able to make everything in raw js in a targeted browser ; but for a real world projet, you should always have a library to manipulate your DOM. Otherwise the compatibility issues will literally drown you.

Step by step exploration :

The second recursive function : to get a reference

Change the name or the id of the clicked element in the DOM would be easy, if you do : e.target.id = or e.target.innerHTML =, it’ll do the trick. But to change the name in the model of the page (the « datas » object), we have to find the reference to the subobject, and modify the « name » property of this subobject. After that, we need to regenerate the content in the DOM.

It may seems like a lot of work for nothing right now, but in more complex project it makes sense.

To do so, we’ll make a « getObject » recursive function to find our object in the datas object. The idea here is to make a first call to our recursive function from the click event. We pass the datas.tree array, the id of the DOM object and a callback as parameters. If the recursive function finds our object, it calls the callback. If the item as a tree property, the function calls itself and pass this tree property as a parameter.

// Change the name of the button and of it's model in the datas object
$('div#container').on('click', 'button', function (e) {
    getObject(datas.tree, e.target.id, function (target) {
        target.name = 'newName ' + Math.random();
        $('div#container').empty();
        buildTree(datas.tree, $('div#container')[0]);
    });
});

var getObject = function (container, id, callback) {
    _.each(container, function (item) {
        if (item.name === id) callback(item);
        if (item.tree) getObject(item.tree, id, callback);
    });
}

Passing an anonymous function as a parameter

In the code I just wrote you, you maybe saw something a bit shocking for a beginner or for a non javascript developer :

getObject(datas.tree, e.target.id, function (target) { … });

WTF, function() as a parameter ? Yes you can, because you do not really pass the function, you pass a reference to the function. So in the var getObject = function (container, id, callback){…} , when you call callback(item), what you do is that you tell the anonymous function in the getObject() call in the callback of the click event to execute. (read this twice, read the code and read this again).

You have to understand that, because it will define the execution context of your function and this is a key concept in JavaScript.

The reference and the object

Let me try to clarify the way object references works in javascript.

Variables are often explained to beginners as container where you can put values. In javascript this is not always the way it work. This is easy to understand, but hard to explain, so I’ll try to give you an intuition by playing with a pair variables and loging them in the console.

var a = 10;
var b = a;
a = 5;
console.log(a); // 5
console.log(b); // 10

Everything seems normal ; let’s try something else.

var a = {‘name’: ‘name1’};
var b = a;
a.name = ‘newName’;
console.log(b); // {name: ‘newName’}

This is not a bug, this is not a problem. This is what you should expect and this is powerful. Numbers, String, boolean… are passed as values. Objects (including functions, arrays…) are passed as references. Which means that if you pass an object as parameter of a function and update it, the original will be updated not a copy. If you want to make a copy, you’ll have to clone you object.

The third recursive function : get the path

At first we add a small div to display the path :

<div id='path'></div>

Get the parent of an object

To go further, we need to get the parent of the targeted object.

In JavaScript, there’s no clean and easy way to get the parent of a js nested object. When you are working with the DOM or an xml, you can target the parent node ; but if you try that with a Javascript object, this won’t do anything.

Once again, this is not a bug or a weird JavaScript behavior. Let me explain you the reason why this is not possible with the console again

var a = {
    p1: {key: 'value1'},
    p2: {key: 'value1'}
};
var b = getObject(a, {key: 'value1'});

The parent of a.p1 is obviously a. But what about the parent of b ; b is a reference to a.p1. It’s « parent » would be something like window. Which is really not an interesting information. What you and is a function getParent(b) that returns a.

That function does not natively exists. The way you do it is by leveraging the object reference system : When you build the hierarchy, for each object, you add a reference to it’s parent. Let’s update a bit the buildTree function.

// Build the tree :
var buildTree = function(tree, container, parentObject) {
    _.each(tree, function(item) {
        var newContainer = document.createElement('div');
        var button = document.createElement('button');
        button.id = item.name;
        button.innerHTML = item.name;
        item.parent = parentObject;
        newContainer.appendChild(button);

        container.appendChild(newContainer);
        if(item.tree) buildTree(item.tree, newContainer, item);
    });
}
buildTree(datas.tree, $('div#container')[0], datas);

So now, if we log the datas object, we can find a parent object as a property of each tree array element ; so we can get the parent of an item.

Build the path to an object

We are now set to build the path to the clicked object with a last recursive function :

This idea is that if the target has a parent, it adds the parent name to the path and call itself back with the updated path on the target parent. Else, it returns the path.

// Get path of an objet
var getPath = function (target, path) {
    if( target.parent ) {
        path = target.parent.name + '/' + path;
        return getPath(target.parent, path);
    }
    else return path;
}

This function has to be called on the click event :

// Get the path and display it
$('div#path').html( 'root/' + getPath(target, '') );

It will set the html of the div we want to display the path. It adds root/ at the beginning of the path and call the getPath function on the target the getObject function obtained with an empty path to start.

That’s all folks

If you have any question, ask in the comment. I’m not use to teach stuff so I could have under explained some points and over explained some others.