如何递归 DOM 树?

发布于 2024-10-09 03:36:37 字数 593 浏览 3 评论 0原文

因此,我有一系列嵌套的 ul 元素作为树的一部分,如下所示:

<ul>
<li>
    <ul>
        <li>1.1</li>
        <li>1.2</li>
    </ul>
    <ul>
        <li>2.1</li>
        <li>
            <ul>
                <li>2.2</li>
            </ul>
        </li>
    </ul>
    <ul>
        <li>3.1</li>
        <li>3.2</li>
    </ul>
</li>
</ul>

假设当 3.1 是选定节点时,当用户单击上一个节点时,选定节点应该是 2.2。坏消息是,可能有任意数量的深度。如何使用 jquery 找到与当前选定节点相关的前一个节点 (li)?

So I have a series of nested ul elements as part of a tree like below:

<ul>
<li>
    <ul>
        <li>1.1</li>
        <li>1.2</li>
    </ul>
    <ul>
        <li>2.1</li>
        <li>
            <ul>
                <li>2.2</li>
            </ul>
        </li>
    </ul>
    <ul>
        <li>3.1</li>
        <li>3.2</li>
    </ul>
</li>
</ul>

Let's say when 3.1 is the selected node and when the user clicks previous the selected node should then be 2.2. The bad news is that there could be any number of levels deep. How can I find the previous node (li) in relationship to the currently selected node using jquery?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

无力看清 2024-10-16 03:36:37

DOM 定义了文档遍历类,允许顺序处理树内容。 TreeWalker 和 NodeIterator 类是完美的候选者。

首先,我们创建一个 TreeWalker 实例,它可以顺序迭代

  • 节点。
  • var walker = document.createTreeWalker(
        document.body, 
        NodeFilter.SHOW_ELEMENT,
        function(node) {
                var hasNoElements = node.getElementsByTagName('*').length == 0;
                return (node.nodeName == "LI" && hasNoElements) ? 
                            NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
        },
        false
    );
    

    接下来我们迭代到 TreeWalker 中的当前节点。假设我们在 myNode 中引用了当前节点。我们将遍历这个步行器,直到到达 myNode 或空值。

    while(walker.nextNode() != myNode && walker.currentNode);
    

    到达 TreeWalker 中的节点后,获取前一个节点是小菜一碟。

    var previousNode = walker.previousNode();
    

    您可以尝试此处的示例


    这是向后树步行器的通用版本。它是 TreeWalker 界面的一个小包装。

    function backwardsIterator(startingNode, nodeFilter) {
        var walker = document.createTreeWalker(
            document.body, 
            NodeFilter.SHOW_ELEMENT,
            function(node) {
                return nodeFilter(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
            },
            false
        );
    
        walker.currentNode = startingNode;
    
        return walker;
    }
    

    它需要一个起始节点和一个自定义过滤器函数来删除不需要的元素。以下是从给定节点开始并且仅包含叶 li 元素的示例用法。

    var startNode = document.getElementById("2.1.1");
    
    // creates a backwards iterator with a given start node, and a function to filter out unwanted elements.
    var iterator = backwardsIterator(startNode, function(node) {
        var hasNoChildElements = node.childElementCount == 0;
        var isListItemNode = node.nodeName == "LI";
    
        return isListItemNode && hasNoChildElements;
    });
    
    // Call previousNode() on the iterator to walk backwards by one node.
    // Can keep calling previousNode() to keep iterating backwards until the beginning.
    iterator.previousNode()
    

    使用交互式示例进行了更新。点击列表项可突出显示上一个列表项。

    DOM has defined document traversal classes that allow for sequential processing of tree content. The TreeWalker, and NodeIterator classes for perfect candidates for this.

    First we create a TreeWalker instance that can sequentially iterate through <li> nodes.

    var walker = document.createTreeWalker(
        document.body, 
        NodeFilter.SHOW_ELEMENT,
        function(node) {
                var hasNoElements = node.getElementsByTagName('*').length == 0;
                return (node.nodeName == "LI" && hasNoElements) ? 
                            NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
        },
        false
    );
    

    Next we iterate up to the current node in this TreeWalker. Let's say we had a reference to our current node in myNode. We will traverse this walker until we reach myNode or a null value.

    while(walker.nextNode() != myNode && walker.currentNode);
    

    Having reached our node in this TreeWalker, getting the previous node is a piece of cake.

    var previousNode = walker.previousNode();
    

    You can try an example here.


    Here is a generic version of a backwards tree walker. It's a tiny wrapper around the TreeWalker interface.

    function backwardsIterator(startingNode, nodeFilter) {
        var walker = document.createTreeWalker(
            document.body, 
            NodeFilter.SHOW_ELEMENT,
            function(node) {
                return nodeFilter(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
            },
            false
        );
    
        walker.currentNode = startingNode;
    
        return walker;
    }
    

    It takes a starting node, and a custom filter function to remove unwanted elements. Here is an example usage of starting at a given node, and only including leaf li elements.

    var startNode = document.getElementById("2.1.1");
    
    // creates a backwards iterator with a given start node, and a function to filter out unwanted elements.
    var iterator = backwardsIterator(startNode, function(node) {
        var hasNoChildElements = node.childElementCount == 0;
        var isListItemNode = node.nodeName == "LI";
    
        return isListItemNode && hasNoChildElements;
    });
    
    // Call previousNode() on the iterator to walk backwards by one node.
    // Can keep calling previousNode() to keep iterating backwards until the beginning.
    iterator.previousNode()
    

    Updated with an interactive example. Tap on a list item to highlight the previous list item.

    打小就很酷 2024-10-16 03:36:37

    你可以在 jQuery 中这样做:

    http://jsfiddle.net/haP5c/

    $('li').click(function() {
        var $this = $(this);
    
        if ($this.children().length == 0) {
            var $lis = $('#myUL').find('li'),
                indCount = 0,
                prevLI = null;
    
            $lis.each(function(ind, el) {
                if (el == $this[0]) {
                    indCount = ind;
                    return false;
                }
            });
    
            for (indCount=indCount-1; indCount >= 0; indCount--) {
                if ($($lis[indCount]).children().size() == 0) {
                    prevLI = $lis[indCount];
                    break;
                }
            }
    
            if (prevLI) {
                alert($(prevLI).text());
            }
        }
    });
    

    基本上,如果你点击一个元素,它将搜索前一个没有任何子节点的 LI 节点。如果是,则将其视为链中的前一个。正如您在 jsfiddle 上看到的,它运行得很好。

    You can do this like this in jQuery:

    http://jsfiddle.net/haP5c/

    $('li').click(function() {
        var $this = $(this);
    
        if ($this.children().length == 0) {
            var $lis = $('#myUL').find('li'),
                indCount = 0,
                prevLI = null;
    
            $lis.each(function(ind, el) {
                if (el == $this[0]) {
                    indCount = ind;
                    return false;
                }
            });
    
            for (indCount=indCount-1; indCount >= 0; indCount--) {
                if ($($lis[indCount]).children().size() == 0) {
                    prevLI = $lis[indCount];
                    break;
                }
            }
    
            if (prevLI) {
                alert($(prevLI).text());
            }
        }
    });
    

    Basically, if you click an element, it'll search for the previous LI node that doesn't have any children. If it does, it considers it the previous one in the chain. As you can see on the jsfiddle, it works perfectly.

    -小熊_ 2024-10-16 03:36:37

    一个相当简单的方法,无需任何 JavaScript 框架即可工作:

    var lis = document.getElementsByTagName("li"), prev;
    
    for (var i = 0; i < lis.length; i++) {
        if (lis[i] == myCurLI) break;
        prev = lis[i];
    }
    

    现在 prev 保存之前的 LI。


    编辑:请参阅我的其他答案,了解使用 XPath 的解决方案,该解决方案不存在 treeface 提到的缺陷。

    A rather simple approach that would work without any JavaScript-framework:

    var lis = document.getElementsByTagName("li"), prev;
    
    for (var i = 0; i < lis.length; i++) {
        if (lis[i] == myCurLI) break;
        prev = lis[i];
    }
    

    Now prev holds the previous LI.


    EDIT: Please see my other answer for a solution using XPath that doesn't have the flaws mentioned by treeface.

    白昼 2024-10-16 03:36:37

    以下是如何将其作为 jQuery 插件(根据 MIT 许可证授权)来实现。在 jQuery 加载后立即加载它:

    /*
     * jQuery Previous/Next by Tag Name Plugin.
     * Copyright (c) 2010 idealmachine
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to deal
     * in the Software without restriction, including without limitation the rights
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in
     * all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     * THE SOFTWARE.
     *
     */
    
    (function($) {
        $.fn.prevByTagName = function(containerSelector) {
            return this.map(function() {
                var container = containerSelector ? $(this).parents(containerSelector).last()[0] : document,
                    others = container.getElementsByTagName(this.tagName),
                    result = null;
                for(var i = 0; i < others.length; ++i) {
                    if(others[i] === this) {
                        break;
                    }
                    result = others[i];
                }
                return result;
            });
        };
        $.fn.nextByTagName = function(containerSelector) {
            return this.map(function() {
                var container = containerSelector ? $(this).parents(containerSelector).last()[0] : document,
                    others = container.getElementsByTagName(this.tagName),
                    result = null;
                for(var i = others.length; i--;) {
                    if(others[i] === this) {
                        break;
                    }
                    result = others[i];
                }
                return result;
            });
        };
    })(jQuery);
    
    // End of plugin
    

    查找最后一个没有 li 元素(最外面的 ul 具有 id myUL) >ul 孩子们,您可以在 $(this) 上使用该插件,如下所示:

    var result = $(this);
    while((result = result.prevByTagName('#myUL')).children('ul').length);
    

    您可以 在 jsFiddle 上尝试一下

    Here's how to do it as a jQuery plug-in (licensed under the MIT License). Load it just after jQuery has loaded:

    /*
     * jQuery Previous/Next by Tag Name Plugin.
     * Copyright (c) 2010 idealmachine
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to deal
     * in the Software without restriction, including without limitation the rights
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in
     * all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     * THE SOFTWARE.
     *
     */
    
    (function($) {
        $.fn.prevByTagName = function(containerSelector) {
            return this.map(function() {
                var container = containerSelector ? $(this).parents(containerSelector).last()[0] : document,
                    others = container.getElementsByTagName(this.tagName),
                    result = null;
                for(var i = 0; i < others.length; ++i) {
                    if(others[i] === this) {
                        break;
                    }
                    result = others[i];
                }
                return result;
            });
        };
        $.fn.nextByTagName = function(containerSelector) {
            return this.map(function() {
                var container = containerSelector ? $(this).parents(containerSelector).last()[0] : document,
                    others = container.getElementsByTagName(this.tagName),
                    result = null;
                for(var i = others.length; i--;) {
                    if(others[i] === this) {
                        break;
                    }
                    result = others[i];
                }
                return result;
            });
        };
    })(jQuery);
    
    // End of plugin
    

    To find the last previous li element (the outermost ul having the id myUL) that has no ul children, you could use the plug-in on $(this) like this:

    var result = $(this);
    while((result = result.prevByTagName('#myUL')).children('ul').length);
    

    You can try it out on jsFiddle.

    夜唯美灬不弃 2024-10-16 03:36:37

    我的简单方法存在缺陷,这让我有点“恼火”,这是一个使用 XPath 的解决方案:

    $('li').click(function() {
        var lis = document.getElementsByTagName("li"), 
            prev = null,
            myCurLI = this;
    
        if ($(this).children().length == 0) {
            // collect all LI-elements that are leaf nodes
            var expr = "//li[count(child::*) = 0]", xp, cur;
    
            xp = document.evaluate(expr, document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
    
            while (cur = xp.iterateNext()) {
                if (cur == myCurLI) break;
                prev = cur;
            }
    
            if (prev) alert($(prev).text());
            else alert("No previous list element found");
        }
    });
    

    注意:我从 Treeface 的答案中复制了事件处理代码,因为我对该框架不太熟悉。

    Mildly "annoyed" by the fact that my simple approach had flaws, here's one solution with XPath:

    $('li').click(function() {
        var lis = document.getElementsByTagName("li"), 
            prev = null,
            myCurLI = this;
    
        if ($(this).children().length == 0) {
            // collect all LI-elements that are leaf nodes
            var expr = "//li[count(child::*) = 0]", xp, cur;
    
            xp = document.evaluate(expr, document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
    
            while (cur = xp.iterateNext()) {
                if (cur == myCurLI) break;
                prev = cur;
            }
    
            if (prev) alert($(prev).text());
            else alert("No previous list element found");
        }
    });
    

    NOTE: I copied the event handling code from treeface's answer as I'm not that familiar with that framework.

    ~没有更多了~
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文