如何为初学者创建动态可拖放的带复选框的 MVC TreeView






4.93/5 (8投票s)
本文介绍了如何使用 ASP.NET MVC 和 Gijgo Treeview JQuery 插件的数据源来表示 TreeView 结构中的数据。TreeView 控件可以显示一个分层(或嵌套、或递归)的数据集合,并以可展开的节点树的形式呈现。
引言
树状视图(Tree view)是一种用于排列文档或记录的结构或模式,当处理分层模式时,它会变得更加有用。让我们通过一个例子来使其更容易理解。
现在假设有一个足球比赛正在组织,并且有两类用户参与此比赛:
第一类是赛事组织者,第二类是球队老板。这两类用户在比赛中都扮演着自己的角色。
赛事组织者的角色
(1) 他可以添加新球队及其球员
(2) 他可以删除球队。
(3) 他可以通过拖放将球员(子节点)从一支球队转移到另一支球队。
球队老板的角色
(1) 他可以选择谁将代表他的球队出场。
(2) 他不能转移球员(节点)。
以上示例仅为更好地理解 TreeView。我们可以在多种场景中使用这种 TreeView 方法。
TreeView 及其操作演示
本文的特色
在本文中,我们可以执行以下操作。
(1) 我们可以添加新节点,无论是父节点还是子节点。
(2) 我们可以删除选定的节点。
(3) 我们可以将任何子节点拖放到层次结构中的任何位置(将一个节点拖放到另一个节点后,它将动态保存在数据库中)。
(4) 我们可以获取选定的节点以保存记录。
目录
(1) 数据库:在本节中,我们将使用其内容在数据库中创建表。
(2) MVC Web 项目:在本节中,我们将逐步创建一个 MVC Web 应用程序。
(3) DB 层:在本节中,我们将创建一个类库项目,以便在 Mvc 项目中集成实体数据模型。
(4) 代码使用:在本节中,我们将讨论代码逻辑和功能。
(1) 数据库
步骤 1.1:在您的数据库中创建表。为此,您可以执行下面提到的查询。
USE [Your DataBase Name]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[HierarchyDetails]
(
[Id] [int] NOT NULL IDENTITY(1,1),
[HierarchyName] [nvarchar](50) NULL,
[PerentId] [Int] NULL,
CONSTRAINT [PK_HierarchyDetails] PRIMARY KEY CLUSTERED ( [Id] ASC )
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
INSERT [dbo].[HierarchyDetails] ([HierarchyName], [PerentId]) VALUES (N'Team1',null)
INSERT [dbo].[HierarchyDetails] ([HierarchyName], [PerentId]) VALUES (N'Player1',1)
INSERT [dbo].[HierarchyDetails] ([HierarchyName], [PerentId]) VALUES (N'Player2',1)
执行查询后,您的表将显示为
Select * from HirarchyDetails
(2) 创建 MVC Web 应用程序项目
步骤 2.1:打开 Visual Studio,选择 -> 文件 -> 新建 -> 项目。
步骤 2.2:选择 ASP.NET Web 应用程序选项,然后设置项目名称和位置。
步骤 2.3:选择 MVC 模板,然后点击确定。
步骤 2.4:更新 Bootstrap 版本 (3.7.7):-> 工具 -> NuGet 包管理器 -> 包管理器控制台。 然后运行此命令:
PM> Update-Package bootstrap -Version 3.7.7
步骤 2.5:在此步骤中,我们将 Gijgo JQuery 插件集成到我们的项目中以用于 Treeview。
Gijgo.com 的 jQuery Tree 是 jQuery Javascript 库的一个插件。
它是一个非常快速且可扩展的工具,可以为任何树结构添加高级交互控件。
此插件允许您使用 Bootstrap 或 Material Design 样式创建树结构。
根据 MIT 许可证分发的免费开源工具。
有关 Gijgo Treeview 的更多信息,请参阅以下链接。
链接:http://gijgo.com/Tree/configuration
现在在我们的项目中,在脚本部分添加一个新的文件夹,并将 javascript 文件 gijgo.js 添加到此文件夹中。 从 http://code.gijgo.com/1.3.0/js/gijgo.js 复制代码,然后粘贴到您的本地 gijgo.js 文件中。
我们将 gijgo.js 文件保存在本地,因为我们可以对其进行自定义。
步骤 2.6:编辑 gijgo.js 文件中的 **updateChildrenState** 方法,以处理单个子父节点复选框状态。 请参阅下图(打开 gijgo.js 文件,找到 UpdateChildrenState,然后将 length 从 1 更新为 0)。
(3) 创建 DB 层(类库)以用于 Entity Framework
步骤 3.1:在解决方案中添加新的类库项目。请参阅下图。
步骤 3.2:选择类库。
步骤 3.3:从类库项目中删除自动生成的类。
步骤 3.4:在类库项目中添加一个新文件夹,用于添加实体模型。然后添加新项。
步骤 3.5:选择 **ADO.NET 实体数据模型**,然后单击添加按钮。
步骤 3.6:选择模型内容,如图所示。
步骤 3.7:选择您的数据连接 -> 单击新建连接按钮。
步骤 3.8:填充连接属性,并从数据库中选择相关表,如图所示。
步骤 3.9:从 **app.config** 复制自动生成的连接字符串,并将其粘贴到我们项目中的 **Web.config** 文件中。
步骤 3.10:将 DB 层引用添加到我们的 mvc Web 项目中。
(4) 代码使用:创建 [模型 -> 视图 -> 控制器] 并讨论功能。
(A) 模型
在 Models 文件夹中,我添加了两个类。
第一个 -> AddNode.cs:这个模型类用于绑定我们的部分视图以添加新节点。
public class AddNode
{
[Required(ErrorMessage = "Node type is required.")]
public string NodeTypeRbtn { get; set; }
[Required(ErrorMessage = "Node Name is required.")]
public string NodeName { get; set; }
//requiredif is conditional attributes which is used to validate Input type based on some condition
[requiredif("NodeTypeRbtn","Cn",ErrorMessage ="Parent Node is required")]
public int? ParentName { get; set; }
}
第二个 -> HierarchyViewModel.cs:这个模型类用于存储来自数据库的层次结构详细信息列表。
public class HierarchyViewModel
{
public int Id { get; set; }
public string text { get; set; }
public int? perentId { get; set; }
public virtual List<HierarchyViewModel> children { get; set; }
}
第三个 -> 数据实体模型(在 DB 层中从数据库自动生成)。
public partial class HierarchyDetail
{
public int Id { get; set; }
public string HierarchyName { get; set; }
public Nullable<int> PerentId { get; set; }
}
创建 validationhelper 文件夹和 requiredif.cs 类以进行条件验证。
requiredif 是一个条件属性,用于根据某些条件验证输入字段。在我们的例子中,当通过部分视图添加新节点时,我们使用条件属性。请参阅下面的代码。
1 将此代码复制并粘贴到 requiredif.cs 类中。
public class requiredif : ValidationAttribute, IClientValidatable
{
private RequiredAttribute _innerAttribute = new RequiredAttribute();
private const string DefaultErrorMessageFormatString = "The {0} field is required.";
private readonly string _dependentPropertyName;
public object TargetValue { get; set; }
public requiredif(string dependentPropertyName, object targetValue)
{
_dependentPropertyName = dependentPropertyName;
this.TargetValue = targetValue;
ErrorMessage = DefaultErrorMessageFormatString;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(this._dependentPropertyName);
if (field != null)
{
// get the value of the dependent property
var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);
// compare the value against the target value
if (
(dependentvalue == null && this.TargetValue == null) ||
(dependentvalue != null && dependentvalue.Equals(this.TargetValue))
)
{
// match => means we should try validating this field
if (!_innerAttribute.IsValid(value))
// validation failed - return an error
return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
public IEnumerable <ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "requiredif"
};
string depProp = BuildDependentPropertyId(metadata, context as ViewContext);
// find the value on the control we depend on;
// if it's a bool, format it javascript style
// (the default is True or False!)
string targetValue = (this.TargetValue ?? "").ToString();
if (this.TargetValue.GetType() == typeof(bool))
targetValue = targetValue.ToLower();
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
yield return rule;
}
private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
// build the ID of the property
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this._dependentPropertyName);
// unfortunately this will have the name of the current field appended to the beginning,
// because the TemplateInfo's context has had this fieldname appended to it. Instead, we
// want to get the context as though it was one level higher (i.e. outside the current property,
// which is the containing object (our Person), and hence the same level as the dependent property.
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField))
// strip it off again
depProp = depProp.Substring(thisField.Length);
return depProp;
}
}
2:在脚本文件夹中创建一个 Js 文件,然后粘贴此 javascript 代码。通过此操作,我们将能够将条件属性添加到 JQuery 验证并启用客户端验证。
/// <reference path="jquery.validate-vsdoc.js"/>
/// <reference path="jquery.validate.unobtrusive.js"/>
$.validator.unobtrusive.adapters.add(
'requiredif',
['dependentproperty', 'targetvalue'],
function (options) {
options.rules['requiredif'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['targetvalue']
};
options.messages['requiredif'] = options.message;
});
$.validator.addMethod('requiredif', function (value, element, parameters) {
var desiredvalue = parameters.targetvalue;
desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
var actualvalue = {}
if (controlType == "checkbox" || controlType == "radio") {
var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
actualvalue = control.val();
} else {
actualvalue = $("#" + parameters.dependentproperty).val();
}
if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
var isValid = $.validator.methods.required.call(this, value, element, parameters);
return isValid;
}
return true;
});
(B) 控制器
(1):在 RouteConfig 文件中,将控制器名称从 Home 更改为 Hierarchy。
(2):HierarchyController.cs 类中有两个方法:第一个是 **GetHierarchy()**,第二个是 **GetChild()**。这些方法用于获取 Treeview 的数据源。这里 **GetChild()** 是一个递归方法(当一个方法调用自身时,它被称为递归方法,递归方法会多次调用自身,直到满足条件)。
现在我们举一个例子来理解这两个方法的_功能。
假设我们有一个包含一些内容的表,其最终视图结果如下所示:
表格
结果视图
当执行 **GetHierarchy()** 方法时,它会从数据源(parentid 为 Null)获取所有详细信息,并填充我们的模型 **HierarchyViewModel** 的所有属性,然后执行递归方法 **GetChild()**,该方法接受两个参数:第一个是 **HierarchyDetail 列表**,第二个是 **Id**,用于填充 **HierarchyViewModel 列表**。这个递归方法会多次调用自身,直到满足条件(只要 id 存在于 ParentId 列中)。
在此图 [P0] 中,Id 是 1,它是表中两个节点(**P1** 和 **P2**)的父 ID。因此,父节点 **[P0]** 将有两个子节点,子节点数为 2。请参阅下面显示的图 **[P1]** 和 **[P2]**。
图 [P0]
在此图 [P1] 中,id 是 2,它是表中一个节点 (P1) 的父 ID。因此,父节点 **[P1]** 将有一个子节点。
图 [P1]
在此图 [P1.1] 中,id 是 4,它不是表中任何行的父 ID。因此,父节点 **[P1.1]** 将没有子节点。
图 [P1.1]
在此图 [P2] 中,id 是 3,它不是表中任何行的父 ID。因此,父节点 **[P2]** 将没有子节点。
图 [P2]
最后,**GetHierarchy()** 返回一个 JSON 结果,该结果将作为 gijgo Tree view 对象的 dataSource 传递。
[注意]:如果您想在 treeview 中添加嵌套节点。您可以从 Hierarchy Details 表中删除 where 子句,这样您就可以启用下拉列表来呈现所有选项。
以下是 HierarchyController.cs 类的完整代码。
public class HierarchyController : Controller
{
// GET: Hierarchy
public ActionResult Index()
{
using (TreeViewEntities context = new TreeViewEntities())
{ // Remove the where clause if you want to add new node in child Node.
var plist = context.HierarchyDetails.Where(p=>p.PerentId==null).Select(a => new
{
a.Id,
a.HierarchyName
}).ToList();
ViewBag.plist = plist;
}
GetHierarchy();
return View();
}
public JsonResult GetHierarchy()
{
List<HierarchyDetail> hdList;
List<HierarchyViewModel> records;
using (TreeViewEntities context = new TreeViewEntities())
{
hdList = context.HierarchyDetails.ToList();
records = hdList.Where(l => l.PerentId == null)
.Select(l => new HierarchyViewModel
{
Id = l.Id,
text = l.HierarchyName,
perentId = l.PerentId,
children = GetChildren(hdList, l.Id)
}).ToList();
}
return this.Json(records, JsonRequestBehavior.AllowGet);
}
private List<HierarchyViewModel> GetChildren(List<HierarchyDetail> hdList, int parentId)
{
return hdList.Where(l => l.PerentId == parentId)
.Select(l => new HierarchyViewModel
{
Id = l.Id,
text = l.HierarchyName,
perentId = l.PerentId,
children = GetChildren(hdList, l.Id)
}).ToList();
}
[HttpPost]
public JsonResult ChangeNodePosition(int id, int parentId)
{
using (TreeViewEntities context = new TreeViewEntities())
{
var Hd = context.HierarchyDetails.First(l => l.Id == id);
Hd.PerentId = parentId;
context.SaveChanges();
}
return this.Json(true,JsonRequestBehavior.AllowGet);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddNewNode(AddNode model)
{
try
{
if (ModelState.IsValid)
{
using (TreeViewEntities db = new TreeViewEntities())
{
HierarchyDetail hierarchyDetail = new HierarchyDetail()
{
HierarchyName = model.NodeName,
PerentId = model.ParentName,
};
db.HierarchyDetails.Add(hierarchyDetail);
db.SaveChanges();
}
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
}
catch (Exception ex)
{
throw ex;
}
return Json(new { success = false }, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public JsonResult DeleteNode(string values)
{
try
{
using (TreeViewEntities context = new TreeViewEntities())
{
var id = values.Split(',');
foreach (var item in id)
{
int ID = int.Parse(item);
context.HierarchyDetails.RemoveRange(context.HierarchyDetails.Where(x => x.Id == ID).ToList());
context.SaveChanges();
}
}
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
throw ex;
// return Json(new { success = false,Exception= ex }, JsonRequestBehavior.AllowGet);
}
}
}
(C) 视图 UI 和 JavaScript
(1):在此处为 Index actionMethod 添加一个视图,然后编写脚本来处理 Gijgo Treeview
。此脚本用于从数据库绑定数据并提交(保存)已更改父节点的节点。
(A) 可拖放脚本:在脚本部分,**nodeDrop** 是处理拖放功能的事件。在这里,当我们开始拖动一个节点时,它会获取其 ID;当我们将其拖放到另一个节点上时,它会获取其 ID 作为父 ID。稍后,我们将在回调函数中使用这些 ID 将数据 POST 到数据库。一旦从服务器获得成功响应,我们就会发送一个 Ajax 调用来更新 UI。请参阅下面的脚本。
tree.on('nodeDrop', function (e, Id, PerentId) {
currentNode = Id ? tree.getDataById(Id) : {};
console.log("current Node = " + currentNode);
parentNode = PerentId ? tree.getDataById(PerentId) : {};
console.log("parent Node = " + parentNode);
//Privent to drop event for root node
if (currentNode.perentId === null && parentNode.perentId === null) {
alert("Parent node is not droppable..!!");
return false;
}
// console.log(parent.HierarchyLevel);
var params = { id: Id, parentId: PerentId };
$.ajax({
type: "POST",
url: "/Hierarchy/ChangeNodePosition",
data: params,
dataType: "json",
success: function (data) {
$.ajax({
type: "Get",
url: "/Hierarchy/GetHierarchy",
dataType: "json",
success: function (records) {
// refresh the User panel
Usertree.destroy();
Usertree = $('#Usertree').tree({
primaryKey: 'Id',
dataSource: records,
dragAndDrop: false,
checkboxes: true,
iconsLibrary: 'glyphicons',
//uiLibrary: 'bootstrap'
});
}
});
}
});
});
Index 的完整代码
@model DynamicTreeView.Models.AddNode
<div class="col-md-12" style="margin:100px auto;">
<div class="modal fade in" id="modalAddNode" role="dialog" aria-hidden="true">
@Html.Partial("_AddNode")
</div>
<div class="col-md-5">
<div class="panel panel-primary">
<div class="panel-heading">Tournament organizer -: [ Add team and its players ]</div>
<div class="panel-body">
<div id="tree"></div>
<div class="clearfix">
</div>
<br />
<div>
<button id="btnDeleteNode" data-toggle="modal" class='btn btn-danger'> Delete Node <span class="glyphicon glyphicon-trash"></span> </button>
<button id="btnpopoverAddNode" data-toggle="modal" class='btn btn-warning'> Add Node <span class="glyphicon glyphicon-plus"></span> </button>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">Team owner -: [ Select team and players who will play ]</div>
<div class="panel-body">
<div id="Usertree"></div>
<div class="clearfix">
</div>
<br />
<div>
<button id="btnGetValue" class="btn btn-warning">Get Checked Value</button>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script src="@Url.Content("~/Scripts/conditional-validation.js")" type="text/javascript"></script>
<script src="~/Scripts/Gijgo/gijgo.js"></script>
<link href="http://code.gijgo.com/1.3.0/css/gijgo.css" rel="stylesheet" type="text/css" />
<script type="text/javascript">
//'Hierarchy/GetHierarchy'
$(document).ready(function () {
var Usertree = "";
var tree = "";
$.ajax({
type: 'get',
dataType: 'json',
cache: false,
url: '/Hierarchy/GetHierarchy',
success: function (records, textStatus, jqXHR) {
tree = $('#tree').tree({
primaryKey: 'Id',
dataSource: records,
dragAndDrop: true,
checkboxes: true,
iconsLibrary: 'glyphicons',
//uiLibrary: 'bootstrap'
});
Usertree = $('#Usertree').tree({
primaryKey: 'Id',
dataSource: records,
dragAndDrop: false,
checkboxes: true,
iconsLibrary: 'glyphicons',
//uiLibrary: 'bootstrap'
});
tree.on('nodeDrop', function (e, Id, PerentId) {
currentNode = Id ? tree.getDataById(Id) : {};
console.log("current Node = " + currentNode);
parentNode = PerentId ? tree.getDataById(PerentId) : {};
console.log("parent Node = " + parentNode);
if (currentNode.perentId === null && parentNode.perentId === null) {
alert("Parent node is not droppable..!!");
return false;
}
// console.log(parent.HierarchyLevel);
var params = { id: Id, parentId: PerentId };
$.ajax({
type: "POST",
url: "/Hierarchy/ChangeNodePosition",
data: params,
dataType: "json",
success: function (data) {
$.ajax({
type: "Get",
url: "/Hierarchy/GetHierarchy",
dataType: "json",
success: function (records) {
Usertree.destroy();
Usertree = $('#Usertree').tree({
primaryKey: 'Id',
dataSource: records,
dragAndDrop: false,
checkboxes: true,
iconsLibrary: 'glyphicons',
//uiLibrary: 'bootstrap'
});
}
});
}
});
});
$('#btnGetValue').click(function (e) {
var result = Usertree.getCheckedNodes();
if (result == "") { alert("Please Select Node..!!") }
else {
alert("Selected Node id is= " + result.join());
}
});
//delete node
$('#btnDeleteNode').click(function (e) {
e.preventDefault();
var result = tree.getCheckedNodes();
if (result != "") {
$.ajax({
type: "POST",
url: "/Hierarchy/DeleteNode",
data: { values: result.toString() },
dataType: "json",
success: function (data) {
alert("Deleted successfully ");
window.location.reload();
},
error: function (jqXHR, textStatus, errorThrown) {
alert('Error - ' + errorThrown);
},
});
}
else {
alert("Please select Node to delete..!!");
}
});
},
error: function (jqXHR, textStatus, errorThrown) {
alert('Error - ' + errorThrown);
}
});
// show model popup to add new node in Tree
$('#btnpopoverAddNode').click(function (e) {
e.preventDefault();
$("#modalAddNode").modal("show");
});
//Save data from PopUp
$(document).on("click", "#savenode", function (event) {
event.preventDefault();
$.validator.unobtrusive.parse($('#formaddNode'));
$('#formaddNode').validate();
if ($('#formaddNode').valid()) {
var formdata = $('#formaddNode').serialize();
// alert(formdata);
$.ajax({
type: "POST",
url: "/Hierarchy/AddNewNode",
dataType: "json",
data: formdata,
success: function (response) {
// $("#modalAddNode").modal("hide");
window.location.reload();
},
error: function (response) {
alert('Exception found');
// $("#modalAddNode").modal("hide");
window.location.reload();
},
complete: function () {
// $('.ajax-loader').css("visibility", "hidden");
}
});
}
});
//Close PopUp
$(document).on("click", "#closePopup", function (e) {
e.preventDefault();
$("#modalAddNode").modal("hide");
});
$('.rbtnnodetype').click(function (e) {
if ($(this).val() == "Pn") {
$('.petenddiv').attr("class", "petenddiv hidden");
$("#ParentName").val("");
}
else {
$('.petenddiv').attr("class", "petenddiv");
}
});
});
</script>
}
2:在视图部分添加 _Addnode.cshtml 部分视图。
@model DynamicTreeView.Models.AddNode <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> <h4 class="modal-title">Add Node</h4> </div> <div class="modal-body"> @using (Html.BeginForm("AddNewNode", "Hierarchy", FormMethod.Post, new { @id = "formaddNode", @class = "form-horizontal", role = "form", enctype = "multipart/form-data" })) { @Html.AntiForgeryToken() @Html.ValidationSummary(true) <div class="col-md-12"> <div class="col-md-6 row"> <div class="input-group"> <input type="text" class="form-control" value="Perent Node" readonly="readonly"> <span class="input-group-addon"> @Html.RadioButtonFor(model => model.NodeTypeRbtn, "Pn", new { @class = "btn btn-primary rbtnnodetype" }) </span> </div> </div> <div class="col-md-6"> <div class="input-group "> <input type="text" class="form-control" value="Child Node" readonly="readonly"> <span class="input-group-addon"> @Html.RadioButtonFor(model => model.NodeTypeRbtn, "Cn", new { @class = "rbtnnodetype" }) </span> </div> </div> <br /> @Html.ValidationMessageFor(m => m.NodeTypeRbtn, "", new { @class = "alert-error" }) </div> <div class="clearfix"> </div> <div class="col-md-12"> <div class="petenddiv hidden"> @Html.Label("Select Perent") @Html.DropDownList("ParentName", new SelectList(ViewBag.plist, "Id", "HierarchyName"), "--select--", new { @class = "form-control" }) @Html.ValidationMessageFor(m => m.ParentName, "", new { @class = "alert-error" }) </div> </div> <div class="clearfix"> </div> <div class="col-md-12"> <div> @Html.Label("Node Name") @Html.TextBoxFor(model => model.NodeName, new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.NodeName, "", new { @class = "alert-error" }) </div> </div> <div class="clearfix"> </div> <br /> <br /> <div class="col-md-12"> <div> <div class="pull-left"> <input type="submit" id="savenode" value="S A V E" class="btn btn-primary" /> </div> <div class="pull-right"> <input type="button" id="closePopOver" value="C L O S E" class="btn btn-primary" /> </div> </div> </div> <div class="clearfix"> </div> } </div> </div> </div>
历史
- 2017 年 5 月 9 日:第一个版本