Languages
[Edit]
EN

JavaScript - display object as expandable tree

7 points
Created by:
Root-ssh
175400

In this article, we would liek to show how in simple way display object as expandable tree using JavaScript.

Presented solution creates tree using HTML elements. The tree can be created in expanded state by setting expanded argument in renderEntry() functuion that indicates how many levels in depth should be expanded. It means by using 0 we create collapsed tree.

Object displayed as expandable tree using JavaScript and HTML elements.
Object displayed as expandable tree using JavaScript and HTML elements.

Practical example:

// ONLINE-RUNNER:browser;

<!doctype html>
<html>
<head>
  <style>

    div.item {
        margin: 5px 0 0 0;
        border: 1px solid #e8e8e8;
    }
    
    div.item div.item {
        margin: 0;
        border-style: solid none none none;
    }

    div.expander {
        display: flex;
        cursor: pointer;
    }

    div.button {
        flex: 0;
        width: 0;
        height: 0;
    }

    div.button.expanded {
        margin: 6px 4.5px 0 1px;
        border-left: 5px solid transparent;
        border-right: 5px solid transparent;
        border-top: 8px solid #3685d6;
    }

    div.button.collapsed {
        margin: 4.5px 3.5px;
        border-top: 5px solid transparent;
        border-bottom: 5px solid transparent;
        border-left: 8px solid #3685d6;
    }
    
    div.type {
        padding: 0 0 2px 0;
        flex: 1;
        color: #39add0;
    }

    div.tree {
        margin: 0 0 0 16px;
        flex: 1 100%;
        display: table;
    }
    
    div.line {
        display: table-row;
    }

    div.key {
        padding: 0 16px 0 0;
        width: 0%;
        min-height: 19px;
        display: table-cell;
        vertical-align: top;
        color: #881280;
    }
    
    div.object {
        padding: 0;
        display: flex;
        flex-direction: column;
    }
    
    div.value {
        padding: 0 0 0 1px;
        min-height: 19px;
    }
    
    div.value.nil {
        color: #221199
    }
    
    div.value.boolean {
        color: #221199
    }
    
    div.value.number {
        color: #2771bb;
    }
    
    div.value.string {
        color: #d03131;
    }
    
    div.value.symbol {
        color: #c80042;
    }
    
    div.value.function {
        color: #770088;
    }
    
    div.value.bigint {
        color: #2771bb;
    }
    
    div.value.regexp {
        color: #ff5500;
    }

  </style>
</head>
<body>
  <script>

    const TO_STRING = Object.prototype.toString;
    
    const getType = (entry) => {
        const text = TO_STRING.call(entry);
        return text.slice(8, -1);
    };

    const iterateEntries = (entry, callback) => {
        for (const key in entry) {
            callback(key, entry[key]);
        }
    };

    const createButton = (expanded) => {
        const hButton = document.createElement('div');
        hButton.className = 'button ' + (expanded > 0 ? 'expanded' : 'collapsed');
        return hButton;
    };
    
    const createType = (type) => {
        const hType = document.createElement('div');
        hType.className = 'type';
        hType.innerText = type;
        return hType;
    };
    
    const createExpander = (type, expanded, onClick) => {
        const hExpander = document.createElement('div');
        hExpander.addEventListener('click', () => {
            const classes = hButton.classList;
            if (expanded > 0) {
                classes.remove('expanded');
                classes.add('collapsed');
                expanded = 0;
            } else {
                classes.remove('collapsed');
                classes.add('expanded');
                expanded = 1;
            }
            onClick(expanded);
        });
        hExpander.className = 'expander';
        const hButton = createButton(expanded);
        const hType = createType(type);
        hExpander.appendChild(hButton);
        hExpander.appendChild(hType);
        return hExpander;
    };

    const createKey = (key) => {
        const hKey = document.createElement('div');
        hKey.className = 'key';
        hKey.innerText = key + ':';
        return hKey;
    };

    const createLine = (key, value, expanded) => {
        const hLine = document.createElement('div');
        hLine.className = 'line';
        const hKey = createKey(key);
        const hEntry = renderEntry(value, expanded);
        hLine.appendChild(hKey);
        hLine.appendChild(hEntry);
        return hLine;
    };
    
    const createTree = (object, expanded) => {
        const hTree = document.createElement('div');
        hTree.className = 'tree';
        iterateEntries(object, (key, value) => {
            const hLine = createLine(key, value, expanded - 1);
            hTree.appendChild(hLine);
        });
        return hTree;
    };

    const renderValue = (value, clazz) => {
        const hItem = document.createElement('div');
        hItem.className = 'item value ' + clazz;
        hItem.innerText = value;
        return hItem;
    };

    const renderNull = () => {
        return renderValue('null', 'nil');
    };

    const renderUndefined = () => {
        return renderValue('undefined', 'nil');
    };

    const renderBoolean = (value) => {
        const text = value.toString();
        return renderValue(text, 'boolean');
    };

    const renderNumber = (value) => {
        const text = value.toString();
        return renderValue(text, 'number');
    };

    const renderString = (value) => {
        const text = `"${value}"`;
        return renderValue(text, 'string');
    };

    const renderError = (value) => {
        const stack = value.stack;
        if (stack) {
            return renderValue(stack, 'text');
        } else {
            const text = (value.name || 'Error') + ': ' + (value.message || '<unknown>');
            return renderValue(text, 'text');
        }
    };

    const renderSymbol = (value) => {
        const text = value.toString();
        return renderValue(text, 'symbol');
    };

    const renderFunction = (value) => {
        const text = 'function ' + (value.name || 'anonymous') + '() { /* ... */ }';
        return renderValue(text, 'function');
    };

    const renderBigint = (value) => {
        const text = value + 'n';
        return renderValue(text, 'bigint');
    };

    const renderRegexp = (value) => {
        const text = value.toString();
        return renderValue(text, 'regexp');
    };
    
    const renderObject = (object, expanded, type) => {
        let hTree = null;
        const onClick = (expanded) => {
            if (expanded) {
                if (hTree == null) {
                    hTree = createTree(object, 0);
                }
                hObject.appendChild(hTree);
            } else {
                hObject.removeChild(hTree);
            }
        };
        const hObject = document.createElement('div');
        hObject.className = 'item object';
        const hExpander = createExpander(type, expanded, onClick);
        hObject.appendChild(hExpander);
        if (expanded > 0) {
            hTree = createTree(object, expanded);
            hObject.appendChild(hTree);
        }
        return hObject;
    };

    const renderEntry = (entry, expanded = 10) => {
        const type = getType(entry);
        switch (type) {
            case 'Null':      return renderNull();
            case 'Undefined': return renderUndefined();
            case 'Boolean':   return renderBoolean(entry);
            case 'Number':    return renderNumber(entry);
            case 'String':    return renderString(entry);
            case 'Error':     return renderError(entry);
            case 'Symbol':    return renderSymbol(entry);
            case 'Function':  return renderFunction(entry);
            case 'BigInt':    return renderBigint(entry);
            case 'RegExp':    return renderRegexp(entry);
            case 'Array':     return renderObject(entry, expanded, `Array(${entry.length})`);
            default:          return renderObject(entry, expanded, type);
        }
    }; 


    // Usage example:
    
    const object = {
        parent: null,
        priority: 5,
        completed: true,
        tasks: [
            {id: 1n, type: 'lecture', toString: () => { }},
            {id: 2n, type: 'class',   toString: () => { }},
            {id: 3n, type: 'work',    toString: () => { }},
        ],
        filters: [
            /^lecture-/i,
            /^class-/i,
            /^work-/i
        ]
    };

    document.body.appendChild(renderEntry(object, 0));  // expansion disabled
//  document.body.appendChild(renderEntry(object, 1));  // 1 level expanded
//  document.body.appendChild(renderEntry(object, 2));  // 2 levels expanded

  </script>
</body>
</html>

 

See also

  1. JavaScript - write own compare JSONs function

  2. JavaScript - JSON difference visualization

Donate to Dirask
Our content is created by volunteers - like Wikipedia. If you think, the things we do are good, donate us. Thanks!
Join to our subscribers to be up to date with content, news and offers.
Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

❤️💻 🙂

Join