Jquery 在MVC中从动态填充的jsTree中获取选中的节点
更新:见文章底部 我不熟悉jstree和JS,并尝试在MVC4场景中使用它,其中节点必须从某个存储库动态加载。我已经设法做到了这一点,但是我在确定用户有效地选择了哪些节点时遇到了一些困难,因为这取决于UI状态。让我们从MVC控制器的方法开始,该方法返回节点并由JS调用。其代码如下所示:Jquery 在MVC中从动态填充的jsTree中获取选中的节点,jquery,asp.net-mvc,jstree,Jquery,Asp.net Mvc,Jstree,更新:见文章底部 我不熟悉jstree和JS,并尝试在MVC4场景中使用它,其中节点必须从某个存储库动态加载。我已经设法做到了这一点,但是我在确定用户有效地选择了哪些节点时遇到了一些困难,因为这取决于UI状态。让我们从MVC控制器的方法开始,该方法返回节点并由JS调用。其代码如下所示: public JsonResult GetTreeNodes(string id) { // selectedIds contains some pre-selected IDs...
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);
}
bind("check_node.jstree uncheck_node.jstree", function(event, data) {
var id = data.rslt.obj[0].id;
addToCheckedOrUnchecked(id, event.type == "check_node"); });
这可以正常工作,并以jstree期望的形式返回具有指定ID的节点的子节点
我的视图有一个用于树的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>
并实现addToCheckDorUnchecked函数,以便它可以存储用户检查或取消检查的ID列表。当然,当向选中列表添加ID时,我必须将其从未选中列表中删除(如果存在),反之亦然。我还必须使用一些持久性机制来在不同的AJAX调用之间保存这些列表:根据浏览器的功能,我假设是会话数据或cookie
请注意,在提交之前打开所有分支不是一个选项,因为这意味着逐分支查询服务器分支,并且其中一些树很大,而我希望保留带宽和响应度
我的第一个问题涉及到check和uncheck节点事件:它们似乎没有被记录下来。我发现它们在谷歌上到处搜索,而且似乎不是所有的浏览器都能正确处理它们。例如,在IE9中,事件似乎根本不会触发,而在FF中则会触发。不管怎样,你能提出更有效的方法来解决这个问题吗?对于一个相对简单的任务来说,这似乎是很多代码,由于我将在不同的页面中使用多个树,我可能必须创建一个HTML帮助程序,以避免MVC项目中的冗余
更新1
通过反复试验,我进一步增强了JS代码,但仍面临一些问题。我必须补充一点,我需要将收到的分支与尚未提交的用户编辑同步:如果我打开一个分支,取消选中一个预选项并选中另一个,然后关闭分支并重新打开它,这不会触发新的ajax调用,因此我需要处理open_node事件,以便能够在显示节点复选框之前修改节点复选框。无论如何,树似乎忽略了我的同步代码。首先,该函数用于将节点ID添加到已检查或未检查的ID列表中,并保存在会话存储或cookie中:
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调用成功时,我调用synchChecks来同步尚未更新的接收节点,因为没有任何内容通过用户编辑发布到服务器。此外,在open_节点上,我检索已检查和未检查的ID列表,并使用它们覆盖正在打开的节点的每个子节点的检查状态。最后,当用户选中或取消选中一个节点时,我调用addToCheckDorUnchecked来更新我的ID列表
无论如何,这似乎不起作用。例如,如果我打开一个分支,取消选中一个预选节点,关闭它,然后重新打开它,我可以从调试器中看到列表ID与预期的一样,未选中列表具有我未选中的节点的ID,并且从循环中的li检索到的ID是ok的,然而,我对li类的编辑似乎没有任何效果,我看到了带有来自服务器的检查的分支。所以,假设没有其他可行的方法,您有什么建议可以让它按预期工作吗?看来我至少在FF中成功地让它工作了,但我愿意接受建议。我回答自己,因为这可能对其他读者有用,我花了很多时间在谷歌上搜索,以发现jsTree的所有特性。以下是1.0版本和MVC4 首先,服务器部分:我有一个控制器,它有一个树操作,只返回视图和它的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调用demand,使用存储库从某个存储中检索它们:
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。AddToCheckDorUnchecked获取一个节点ID和一个布尔值,告诉您是否应该检查该节点,并相应地更新此存储
然后,synch函数:当ajax调用成功完成时,使用synchChecks,并使用用户编辑覆盖从服务器接收的检查;打开节点时使用open_节点处理程序,其子节点需要以类似的方式同步。下面是完整的代码,除了一些使用sessionStorage处理JS存储的实用程序函数,或者在不可用时返回到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>