当前位置: 首页 > 知识库问答 >
问题:

从MVC中动态填充的jsTree中获取选中的节点

申屠昌胤
2023-03-14

(更新:见文章底部)我对jstree(和JS)是个新手,并尝试在MVC4场景中使用它,在这个场景中节点必须从某个存储库动态加载。我已经成功地实现了这个功能,但是在确定用户有效地选择了哪些节点方面遇到了一些困难,因为这取决于UI状态。让我们从MVC控制器的方法开始,该方法返回节点并由JS调用。其代码如下所示:


public JsonResult GetTreeNodes(string id)
{
        // selectedIds contains some pre-selected IDs...

    IEnumerable nodes = _repository.GetChildNodes(id);
    return Json(
        (from n in nodes
         select new
                    {
                        id = n.Id,
                        data = n.Value,
                        attr = new
                                   {
                                       id = n.Id,
                                       selected = selectedIds.Any(s => s == n.Id)
                                   },
                        state = "closed"
                    }).ToArray(), JsonRequestBehavior.AllowGet);
}

这可以正常工作,并返回具有指定ID的节点的子节点,其形式为jstree所期望的。

我的视图有一个用于树的div和一个用于包含选中节点的隐藏字段。提交表单时,一个JS函数检索选中的节点,并将它们存储到一个隐藏字段中,以便MVC动作接收它们。视图如下:

    ...
<section>
    @using (Html.BeginForm())
    {
        @Html.Hidden("selectedNodes")
        <div id="tree" style="max-width: 70%"></div>
        <input id="submit" type="submit" value="Submit" />
    }
</section>

<script type="text/javascript">
    $(function () {
        fillTree();
        $("form:first").submit(function(e) {
            storeSelected();
        });
    });

    function fillTree() {
        $("#tree").jstree({
            checkbox: {
                real_checkboxes: true,
                checked_parent_open: true
            },
            plugins: ["themes", "json_data", "ui", "checkbox"],
            json_data: {
                "ajax": {
                    "type": 'GET',
                    "url": function(node) {
                        var url;
                        if (node == -1) {
                            url = "@Url.Action("GetTreeNodes", "Home")";
                        } else {
                            var nodeId = node.attr('id');
                            url = "@Url.Action("GetTreeNodes", "Home")" + "?id=" + nodeId;
                        }

                        return url;
                    },
                    "success": function (newData) {
                        return newData;
                    }
                }
            },
            animation: 200
        }).bind("open_node.jstree", function(event, data) {
            $("#tree").jstree("check_node", "li[selected=selected]");
        });
    }

    function storeSelected() {
        var checkedIds = [];
        $("#tree").jstree("get_checked", null, true).each(function() {
            checkedIds.push(this.id);
        });
        $("#selectedNodes").val(checkedIds.join(","));
    }
</script>

如您所见,当用户提交表单时,storeSelected函数使用jstree get_checked函数检索选中的节点。问题是,这里的节点是动态加载的,因此这只在引用用户提交表单时有效存在于页面中的节点时才起作用。如果我展开一个分支,选中和取消选中一些节点,然后在提交之前收缩相同的分支,我将得到一个已选中ID的空列表,只是因为表示树UI中节点的列表项元素在页面中不存在。

那么什么才是处理这个问题的最佳策略呢?我想对checked/unchecked事件做出反应,并跟踪用户所做的事情,这样我就可以知道他检查了哪些节点,而不管UI状态如何。所以我添加了另一个绑定,如下所示:

bind("check_node.jstree uncheck_node.jstree", function(event, data) {
            var id = data.rslt.obj[0].id;
            addToCheckedOrUnchecked(id, event.type == "check_node"); });

并实现addToCheckedOrUnchecked函数,以便它可以存储用户检查或取消检查的ID的列表。当然,当将一个ID添加到检查列表时,我必须将其从未检查列表中删除(如果存在的话),反之亦然。我还必须使用一些持久性机制来在不同的AJAX调用之间保存这些列表:我假设会话数据或cookie,这取决于浏览器的功能。

请注意,在提交之前打开所有分支不是一个选项,因为这将意味着逐个分支查询服务器,并且其中一些树很大,而我希望保留带宽和响应性。

我的第一个问题涉及检查和取消检查节点事件:它们似乎没有文档化(我在搜索时发现了它们),而且似乎并不是所有浏览器都正确地处理它们。例如,在IE9中,事件似乎根本不会触发,而在FF中则会触发。无论如何,你能建议更有效的方法来解决这个问题吗?对于一个相对简单的任务来说,这似乎是一个很大的代码,而且由于我要在不同的页面中使用几个树,我可能必须创建一个HTML帮助器来避免MVC项目中的冗余。

通过反复试验,我进一步增强了JS代码,但仍然面临一些问题。我必须补充一点,我需要将收到的分支与尚未提交的用户编辑同步:如果我打开一个分支,取消选中一个(预选的)项并选中另一个,然后关闭分支并重新打开它,这不会触发一个新的ajax调用,因此我需要处理open_node事件,以便能够在显示节点复选框之前修改它们。不管怎样,树似乎忽略了我的同步代码。首先,用于将节点ID添加到会话存储(或cookie)中管理的已检查或未检查ID列表中的函数:


function addToCheckedOrUnchecked(id, checked) {
    // get arrays of IDs from storage
    var listChecked = getFromStore("checkedNodes");
    if (listChecked === null) {
        listChecked = [];
    }
    var listUnchecked = getFromStore("uncheckedNodes");
    if (listUnchecked === null) {
        listUnchecked = [];
    }

    // if checking, add ID to checked and remove from unchecked if present
    if (checked) {
        if ($.inArray(id, listChecked) == -1) {
            listChecked.push(id);
        }
        var i = $.inArray(id, listUnchecked);
        if (i > -1) {
            listUnchecked.splice(i, 1);
        }
        // else add ID to unchecked and remove from checked if present
    } else {
        if ($.inArray(id, listUnchecked) == -1) {
            listUnchecked.push(id);
        }
        var j = $.inArray(id, listChecked);
        if (j > -1) {
            listChecked.splice(j, 1);
        }
    }

    // update storage
    putInStore("checkedNodes", listChecked);
    putInStore("uncheckedNodes", listUnchecked);
}

这个函数只是从存储中读取列表,并根据接收到的id以及该id是被选中还是未被选中来更新它们。

然后我又增加了这个功能:


function synchChecks(data) {
    var listChecked = getFromStore("checkedNodes");
    var listUnchecked = getFromStore("uncheckedNodes");

        $(data).each(function () {
            var id = $(this).id;
            if ($.inArray(id, listChecked) > -1) {
                $(this).attr.selected = true;
                console.log("overridden=1: " + id);
            } else if ($.inArray(id, listUnchecked) > -1) {
                $(this).attr.selected = false;
                console.log("overridden=0: " + id);
            }
        });
}

这将从存储中获取列表,并覆盖接收到的每个节点的attr.selected值(在数据中),使其与用户编辑同步。最后,我在jstree调用中将所有内容粘合在一起:


function fillTree() {
    $("#tree").jstree({
        checkbox: {
            real_checkboxes: true,
            checked_parent_open: true
        },
        plugins: ["themes", "json_data", "ui", "checkbox"],
        json_data: {
            "ajax": {
                "type": 'GET',
                "url": function(node) {
                    var url;
                    if (node == -1) {
                        url = "@Url.Action("GetTreeNodes", "Home")";
                    } else {
                        var nodeId = node.attr('id');
                        url = "@Url.Action("GetTreeNodes", "Home")" + "?id=" + nodeId;
                    }

                    return url;
                },
                "success": function (newData) {
                    synchChecks(newData);
                    return newData;
                }
            }
        },
        animation: 200
    }).bind("open_node.jstree", function (event, data) {
        openingNode = true;

        var tree = $("#tree");
        tree.jstree("check_node", "li[selected=selected]");

        // get and synch children
        var listChecked = getFromStore("checkedNodes");
        var listUnchecked = getFromStore("uncheckedNodes");

        $(data.inst._get_children(data.rslt.obj[0])).each(function () {
            var id = $(this).attr("id");
            if ($.inArray(id, listChecked) > -1) {
                $(this).addClass("jstree-checked");
                $(this).removeClass("jstree-unchecked");
            } else if ($.inArray(id, listUnchecked) > -1) {
                $(this).addClass("jstree-unchecked");
                $(this).removeClass("jstree-checked");
            }
        });

        openingNode = false;
    }).bind("check_node.jstree uncheck_node.jstree", function (event, data) {
        if (!openingNode) {
            var id = data.rslt.obj[0].id;
            addToCheckedOrUnchecked(id, event.type == "check_node");
        }
    });
}

在ajax调用success时,我调用synchChecks来与用户编辑同步接收到的节点(尚未更新,因为没有任何内容发布到服务器)。此外,在open_node上,我检索checked和unchecked ID列表,并使用它们覆盖被打开节点的每个子节点的check状态。最后,当用户检查或取消检查一个节点时,我调用addToCheckedOrUnchecked来更新我的ID列表。

无论如何,这似乎行不通。例如,如果我打开一个分支,取消选中一个预选节点,关闭它,然后重新打开它,我可以从调试器中看到列表ID与预期的一样(未选中的列表中有我未选中的节点的ID),并且从循环中的li检索到的ID是正常的,但是我对li类的编辑似乎没有效果,我看到的分支带有来自服务器的检查。那么,假设没有其他可行的方法,您有什么提示可以让这个方法按预期工作吗?

共有1个答案

倪德业
2023-03-14

看来我设法使它工作至少在FF,但我开放的建议。我回答自己,因为这可能对其他读者有用,所以我花了很多时间在网上搜索,以发现JStree的所有特性。下面提到的是1.0版本和MVC4。

首先,服务器部分:我有一个控制器,它的树动作正好返回View()和它的post对应物:


[HttpPost]
public ActionResult Tree(string checkedNodes, string uncheckedNodes)
{
    TempData["message"] = String.Format(CultureInfo.InvariantCulture,
                                        "c=[{0}] | u=[{1}]", 
                                        checkedNodes, uncheckedNodes);
    return RedirectToAction("Index");
}

此操作只是在TempData中设置一条消息,以在索引页中显示接收到的节点。注意,作为一个动态加载的树,操作接收到的不是所选节点ID的列表,而是用户操作的列表:需要选择的ID列表(checkedNodes)和需要取消选择的ID列表(uncheckedNodes)。在服务器端为存储库执行这些操作将使存储库与用户在jsTree中所做的同步。

服务器部分的核心是按需返回节点的操作(由ajax调用),使用存储库从某个存储中检索节点:


public JsonResult GetTreeNodes(string id)
{
    // some sample categories to be preselected
    string[] aSampleCategories = new[] {"categories-fun", "languages-ita"};

    IEnumerable nodes = _repository.GetChildNodes(id);
    return Json(
        (from n in nodes
         select new
                    {
                        id = n.Id,
                        data = n.Value,
                        attr = new
                                   {
                                       id = n.Id,
                                       selected = aSampleCategories.Any(s => s == n.Id)
                                   },
                        state = "closed"
                    }).ToArray(), JsonRequestBehavior.AllowGet);
}

现在,到客户端。下面是视图的形式:

@using (Html.BeginForm())
{
    @Html.Hidden("checkedNodes")
    @Html.Hidden("uncheckedNodes")
    <div id="tree"></div>
    <input id="submit" type="submit" value="Submit" />
}

JS代码如下(我使用了几个日志调用来检查发生了什么):storeSelected通过从临时JS存储中检索要选择或取消选择的节点的ID来存储它们。addToCheckedOrUnchecked获取一个节点ID和一个布尔值(告知该节点应该被选中还是不选中),并相应地更新该存储。

然后,synch函数:当ajax调用成功完成时使用synchChecks,并用用户编辑覆盖从服务器收到的检查;打开节点时使用open_node处理程序,需要以类似的方式同步它的子节点。这里是完整的代码,除了一些处理JS存储的实用程序函数(使用sessionStorage或在不可用时返回到cookie)。希望这能帮助某人,如果有人建议一个更好的方法/实现,我很乐意知道。谢谢!

<script type="text/javascript">
    var openingNode = false;
    function storeSelected() {
        $("#checkedNodes").val(getFromStore("checkedNodes").join(","));
        $("#uncheckedNodes").val(getFromStore("uncheckedNodes").join(","));
    }

    function addToCheckedOrUnchecked(id, checked) {
        console.log("[BEG addToCheckedOrUnchecked]");

        // get arrays of IDs from storage
        var listChecked = getFromStore("checkedNodes");
        if (listChecked === null) {
            listChecked = [];
        }
        var listUnchecked = getFromStore("uncheckedNodes");
        if (listUnchecked === null) {
            listUnchecked = [];
        }

        // if checking, add ID to checked and remove from unchecked if present
        if (checked) {
            if ($.inArray(id, listChecked) === -1) {
                listChecked.push(id);
            }
            var i = $.inArray(id, listUnchecked);
            if (i > -1) {
                listUnchecked.splice(i, 1);
            }
            // else add ID to unchecked and remove from checked if present
        } else {
            if ($.inArray(id, listUnchecked) === -1) {
                listUnchecked.push(id);
            }
            var j = $.inArray(id, listChecked);
            if (j > -1) {
                listChecked.splice(j, 1);
            }
        }
        console.log("checked: " + listChecked + " | unchecked: " + listUnchecked);

        // update storage
        putInStore("checkedNodes", listChecked);
        putInStore("uncheckedNodes", listUnchecked);
        console.log("[END addToCheckedOrUnchecked]");
    }

    function synchChecks(data) {
        console.log("[BEG synchChecks]");

        var listChecked = getFromStore("checkedNodes");
        var listUnchecked = getFromStore("uncheckedNodes");

        $(data).each(function () {
            var id = $(this).id;
            if ($.inArray(id, listChecked) > -1) {
                $(this).attr.selected = true;
                console.log("overridden=1: " + id);
            } else if ($.inArray(id, listUnchecked) > -1) {
                $(this).attr.selected = false;
                console.log("overridden=0: " + id);
            }
        });

        console.log("checked: " + listChecked + " | unchecked: " + listUnchecked);
        console.log("[END synchChecks]");
    }

    function fillTree() {
        $("#tree").jstree({
            checkbox: {
                real_checkboxes: true,
                checked_parent_open: true
            },
            plugins: ["themes", "json_data", "ui", "checkbox"],
            json_data: {
                "ajax": {
                    "type": 'GET',
                    "url": function(node) {
                        var url;
                        if (node == -1) {
                            url = "@Url.Action("GetTreeNodes", "Home")";
                        } else {
                            var nodeId = node.attr('id');
                            url = "@Url.Action("GetTreeNodes", "Home")" + "?id=" + nodeId;
                        }

                        return url;
                    },
                    "success": function (newData) {
                        synchChecks(newData);
                        return newData;
                    }
                }
            },
            animation: 200
        }).bind("open_node.jstree", function (event, data) {
            console.log("[BEG open_node]");
            openingNode = true;

            var tree = $("#tree");
            tree.jstree("check_node", "li[selected=selected]");

            // get and synch children
            var listChecked = getFromStore("checkedNodes");
            var listUnchecked = getFromStore("uncheckedNodes");
            console.log("checked: " + listChecked + " | unchecked: " + listUnchecked);

            $(data.rslt.obj[0]).find("li").each(function () {
                var id = $(this).attr("id");
                if ($.inArray(id, listChecked) > -1) {
                    $(this).addClass("jstree-checked");
                    $(this).removeClass("jstree-unchecked");
                    console.log("overridden=1: " + id);
                } else if ($.inArray(id, listUnchecked) > -1) {
                    $(this).addClass("jstree-unchecked");
                    $(this).removeClass("jstree-checked");
                    console.log("overridden=0: " + id);
                }
            });

            openingNode = false;
            console.log("[END open_node]");
        }).bind("check_node.jstree uncheck_node.jstree", function (event, data) {
            console.log("[BEG " + event.type + "]");
            if (!openingNode) {

                var id = data.rslt.obj[0].id;
                addToCheckedOrUnchecked(id, event.type == "check_node");
            }
            console.log("[END " + event.type + "]");
        });
    }

    $(function () {
        // clear storage used for keeping track of checked nodes
        removeFromStore("checkedNodes");
        removeFromStore("uncheckedNodes");

        // build tree
        fillTree();

        // attach store logic to submit
        $("form:first").submit(function (e) {
            storeSelected();
        });
    });
</script>
 类似资料:
  • 本文向大家介绍jQGrid动态填充select下拉框的选项值(动态填充),包括了jQGrid动态填充select下拉框的选项值(动态填充)的使用技巧和注意事项,需要的朋友参考一下 本文给大家分享一段代码关于技巧jqgrid动态填充select 下拉框的选项值,非常不多说了,直接给大家贴代码了,具体代码如下所示: 注意:要return以及async:false否则没有效果 在colModel:中设置

  • 我有一个调查,有一堆问题,包括实际问题,一些是或否的单选按钮和一个评论框。我加了一张照片给你看。下面是填充问题后的html结构。 此信息通过web服务和jquery动态填充。看起来这些问题是动态填充的,每个问题都包含相同的类名、范围名等。有40多个问题被附加到div中。 我想做的是选择一个特定的问题并选中一个特定的单选按钮。然后,如果单击某个单选按钮,则会自动选中其他单选按钮。我相信Jquery是

  • 问题内容: 我已经用AJAX填充了ASP.net下拉列表,现在我需要获取ID以C#方法存储到数据库中((我正在使用LINQ) 这是我的网络方法 我的test.aspx代码 问题答案: 如果在中添加选项,则无法从中获取所选值。您可以尝试以下

  • 在SpringMVC项目的控制器中,我确实有一个列表,我将其放在模型映射中,如下所示。 现在我想在thymeleaf视图中使用post方法动态填充它。为此,我使用了一个JavaScript脚本。这不是所有的html代码,但为了简单起见,我只添加了JS部分。 这是测试有效的后方法。 它向我显示了错误:错误解析模板[],模板可能不存在或任何已配置的模板解析程序都无法访问,这肯定是因为:。有没有办法动态

  • 这是我的主页。 当用户选择一个数据中心时,页面将重定向并显示该数据中心可用的FisiHost列表。 数据中心类 这是用户在选择数据中心时重定向的视图: 视图中的每一行表示可用于所选数据中心的FiscHost对象。 FisicHost类 这是处理第二个视图的控制器方法(显示该数据中心可用FisiHost列表的视图): 如果用户单击“CREDENCIALES”按钮,将弹出一个模式并显示该FisiHos

  • 问题内容: 对于我一生,我无法弄清楚如何使用WTForms预先填充BooleanField。我有一个名为“活动”的字段。默认情况下为不选中,并且不是必需的。所以我将其设置为… 然后我有一个EDIT PAGE,在其中显示要编辑的“问题”的表格。 如果’active’为True,我希望BooleanField(复选框)具有’checked’属性。如果为False,则不会。但是我什至无法弄清楚如何使复选