简体   繁体   中英

Filter through nested ul/li structure with input field, reset to original state if input is empty

I've got a JSON object I use to build a tree view using lists. The JSON object has the following structure;

const data = [
    { "name": "Node 1", "children":
        [
            { "name": "Node 1.1", "children":
                [
                    { "name": "Node 1.1.1", "children": [], "leaf": true },
                    { "name": "Node 1.1.2", "children": [], "leaf": true }
                ]
            },
            { "name": "Node 1.2", "children":
                [ { "name": "Node 1.2.1", "children": [], "leaf": true } ]
            }
        ]
    },
    { "name": "Node 2", "children":
        [
            { "name": "Node 2.1", "children": [] },
            { "name": "Node 2.2", "children":
                [ { "name": "Node 2.2.1", "children": [], "leaf": true } ]
            }
        ]
    },
    { "name": "Node 3", "children": [] }
];

This object is then used in the createTree function;

createTree(nodes, container)
{
    const list = document.createElement('ul');

    nodes.forEach((node) => {
        const listItem = document.createElement('li');
        listItem.textContent = node.name;
        list.appendChild(listItem);

        if (node.children) {
            const childList = document.createElement('li');
            this.createTree(node.children, childList);
            list.appendChild(childList);
        }
    });

    container.appendChild(list);
}

nodes initially is the data object, container is the element to place the tree in. This function builds the following tree view;

<ul>
    <li>Node 1</li>
    <li>
        <ul>
            <li>Node 1.1</li>
            <li>
                <ul>
                    <li>Node 1.1.1</li>
                    <li>
                        <ul></ul>
                    </li>
                    <li>Node 1.1.2</li>
                    <li>
                        <ul></ul>
                    </li>
                </ul>
            </li>
            <li>Node 1.2</li>
            <li>
                <ul>
                    <li>Node 1.2.1</li>
                    <li>
                        <ul></ul>
                    </li>
                </ul>
            </li>
        </ul>
    </li>
    <li>Node 2</li>
    <li>
        <ul>
            <li>Node 2.1</li>
            <li>
                <ul></ul>
            </li>
            <li>Node 2.2</li>
            <li>
                <ul>
                    <li>Node 2.2.1</li>
                    <li>
                        <ul></ul>
                    </li>
                </ul>
            </li>
        </ul>
    </li>
    <li>Node 3</li>
    <li>
        <ul></ul>
    </li>
</ul>

It's almost the way I want it, there's a lot of unnecessary li and ul tags, but because I'm using list-style-type: none it doesn't really matter, the unnecessary elements are collapses and not shown.

I've also added an input field, used to filter through the tree and only show the relevant leafs (hence the leaf value in the JSON object). It should show only relevant leafs, but also their parents. I've managed to partially do this with the following filter function;

$('#input').on('keyup', () => {
    const value = $('#input').val().toLowerCase().trim();
    const nodes = oldData.filter(function f(node) {
        if (node.name.toLowerCase().includes(value)) return true;

        if (node.children) {
            return (node.children = node.children.filter(f)).length;
        }
    });

    // Simply removes all children from the root element so there's only one tree, instead of the new tree being stacked on top of the new one
    this.removeTree();
    // Rebuild the new tree
    this.createTree(nodes, document.getElementById('root-element'));
});

This works in the sense it only shows the relevant leafs including their parent nodes, but if you empty the input field, only the elements visible after the search term will be made visible. I know why this is happening (the filter function removes every non-match) but I don't know how to prevent this from happening. I've tried storing the initial state of data in a variable outside the keyup event listener, and setting nodes to that variable the moment the input field is empty, but that doesn't seem to help.

I've also tried going through the actual HTML tree, but I can't even properly access the children of Node 1, 2 and 3 so I'm at a loss there as well.

In other words, what's the best way to filter the tree's leafs while also showing their parents, and restoring the original once the filter's empty?

Using jQuery.grep() ;

const nodes = $.grep(data, function f(node) {
    if (node.name.toLowerCase().includes(value)) return true;
});

As I understand you had 2 problems.

  1. The way you copy object. When you write let oldData = data; You get the link to the initial "data" object, not another object. There are many ways to copy objects, I used via JSON.
$(document).ready(() => {
  createTree(data, $('#container'));
  $('#asdf').on('keyup', () => {
    let dataCopy = JSON.parse(JSON.stringify(data));

    const value = $('#asdf').val().toLowerCase().trim();
    let nodes = {};
    if (!value) { nodes = data; } else {
      nodes = filterData(dataCopy, value);
    }

    // Simply removes all children from the root element so there's only one tree, instead of the new tree being stacked on top of the new one
    this.removeTree();
    // Rebuild the new tree
    this.createTree(nodes, $('#container'));
  });
});
  1. Inside the filter function, you should check if there any child nodes came back.
function filterData(data, name) {
  return data.filter(function(o) {
    if (o.children) o.children = filterData(o.children, name);
    return o.name.toLowerCase().includes(name) || o.children.length;
  });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM