asp.net mvc中应用treeview

      最近我们的项目中需要用到树型菜单,以前使用WebForm时,树型菜单有微软提供的控件,非常方便,但现在需要在asp.netmvc中使用树形菜单,先说明下我们对树形菜单的需求:
      1:需要支持CheckBox,允许对菜单项进行选择;
      2:当选择父菜单时,它下面的子菜单全部选中;
      3:当取消父菜单的选中状态时,下面的子菜单也全部取消;
      4:要比较方便的与MVC结合。
     
      初步思路:
      思路一:jquery相关的树形菜单插件,由于项目中有应用到jquery,所以不考虑采用其它js框架的产品。
      思路二:asp.net mvc相应的控件,这里指的控制就好比分页控件之类,基本思路就是扩展HtmlHelper来实现,Html逻辑一般都封闭在dll中。
     
      经过一轮筛选后的结果:
     
      思路一:基于js的树形菜单果然有很多,最终我选择了https://github.com/daredevel/jquery-tree,从demo展示来看,它完全能够满足我上面提到的前三个需求。
      思路二:Telerik也有相应的树形菜单控件,它能够很好的结合MVC,最大特点是将View上的树形菜单数据传递给Controller很直观。
     
      最终方案:


      由于上面的两个产品不能完全符合我的要求,所以结合jquery-tree以及Telerik treeview设计理念来实现自己的树形菜单是最佳选择。
      jquery-tree本身只是一个基于前端的菜单,我们要想传递数据给Controller,唯一比较方便的方法就是通过ajax,但这需要开发人员有比较强的js操纵能力。
      Telerik treeview,有点杀鸡用牛刀的意思,Telerik提供了一整UI解决方案,如果我们只用其中的一个功能就引入它,有些不适时宜,但我非常喜欢它给出的思路,它能很好的将View的菜单数据传递给Controller,但Telerik由于提供的是控件,我们不太方便对它的样式做修改,我们更希望能够自由的控制UI显示逻辑。
     
       题外话:关于数据结构
       记的有一年,我去一家公司面试,当时估计是技术人员都不在,所以安排了一位年龄上比较大的面试官来面试我,目测应该是位级别不低的领导,当时不免有点小紧张。经过一番自我介绍以及工作经验介绍后,他现场给我出了一道题:
       写一个程序,打印出公司的组织结构图,比如最上层是CEO,下面是VP......
     
      我当时跟很大一部分.net程序员一样,做过几年项目后,什么数据结构啊,算法呀早就不记得了,那一刻只想到这是和树相关的数据结构,但不知为何,只想到了二叉树,心想总算想到了,于是开始定义二叉树数据结构,采用递归遍历数据,总之脑袋一片空白,写了一会,领导见我还没写完,有些不耐烦了,问我写完吗,我回答说还差一点,他等了一会见我还没写完,就说先给我看看,于时直接过来拿走我未写完的代码,我是多么的想争取那次机会呀,当时多么的舍不得交出去,最后的结果可想而知。
     
      并不是面试官问的问题有多么难,他考的问题只不过是计算机最基础的东西,而我正好忘记了。到现在我也面试过一些候选人,我也比较注意候选人的基础知识,其实说实在的,像做一些.net相关的企业级开发,大部分情况下我们是不会用到复杂数据结构的,算法就更不用说了,只有少数人会用到这些高级的东西。但这些数据结构以及算法知识会让你的视野更加开阔,在特定情况下能够给你一些方向,如果你对那块领域没有接触过,你是不会朝那方面想的。

      这次的树形菜单正好能够解答我以前那位面试官的问题。
     
      树形菜单数据结构:
      首先它属于树形数据结构,但不能定义成二叉树,因为一个总裁下面不可能只有两个副总,一个经理下面也不可能只有两个员工,下属应该是大于等于0的数据。
      下面是我定义的菜单数据对象:
  

public class MyTreeViewItem
    {
        public IList<MyTreeViewItem> Items { getset; }
        public string Value { getset; }
        public bool Checked { getset; }
        public string Text { getset; }
        public string Index { getset; }
        public MyTreeViewItem Parent { getset; }
        public string HtmlDomName { getset; }
    }

     
       字段说明:
       Text:用于显示的文本,比如:总裁
       Value:显示文本对应的ID,比如:0
       Index:这个是结合表单的辅助属性,View上使用
       Checked:当前菜单项是否被选中,用户提交表单后我们可以通过这个属性判断用户的选择项
       Items:当前菜单下的子菜单集合
       Parent:当前菜单的上级菜单
       HtmlDomName:这个是结合表单的辅助属性,View上使用
      
       树形菜单的输出:
       我上次面试过程中的递归逻辑还是有用的,这里我们仍然采用递归来输出菜单项。我创建了一个Partial View

@model MvcTreeView.Controllers.MyTreeViewItem
<ul>
    <li>
        @if (null == Model.Parent)
        {
            Model.HtmlDomName = "TreeView1_checkedNodes[" + Model.Index + "]";
        }
        else
        {
            Model.HtmlDomName = Model.Parent.HtmlDomName + ".Items[" + Model.Index + "]";
        }
        <input name='@(Model.HtmlDomName + ".Checked")' type="checkbox" onchange='setValue(this)'  value='False' />
        <input  name='@(Model.HtmlDomName + ".Text")' type="hidden" value="@Model.Text" />
        <input  name='@(Model.HtmlDomName + ".Index")' type="hidden" value="@Model.Index" />
        <span>@Model.Text</span>
        <input  name='@(Model.HtmlDomName + ".Value")' type="hidden"
                                        value="@Model.Value" />
        @if (null != Model.Items)
        {
            for (var i = 0; i < Model.Items.Count; i++)
            {
            @Html.Partial("NodeItem", Model.Items[i])
            }
        }
    </li>
</ul>

   

       View向Controller的数据传递:

       无论是ajax还是直接post表单,最终的目的都是接收View中的数据,要想传递比较复杂的数据类型,我们需要对ASP.NET MVC Model Binding 有一定了解,之前也讲解过MyTreeViewItem的结果,有普通的数据类型,比如 string,bool,也是对象类型,比如MyTreeViewItem类型的Parent,也是基于集合的属性IList<MyTreeViewItem>,要想让表单中的数据直接传递给Controller,我们需要对表单元素的name进行特殊处理才行。如果大家对这部分不太理解,这篇文章可以参考:Understanding-ASP-NET-MVC-Model-Binding
     
       比如我是这样定义Model的:
    

public class AboutModel
    {
        public MyTreeViewItem NodeItem { getset; }

    }



       View:这是主要是加载菜单,至于如何使用jquery-tree,这里就不多说了,大家下可以自己下载demo。 
    

@using (Html.BeginForm())
{
    <div id="accordion">
        <h3>
            <a href="#">All components in default behaviour</a></h3>
        <div id="example-0">
            <div>
             @if (null != Model.NodeItem)
            {
                @Html.Partial("NodeItem", Model.NodeItem)
            }
             
            </div>
        </div>
    </div>
    <input type="submit" id="example-0-button" />
}

  

     Controller:这是最重要的就是接收参数,它是一个List类型,无论菜单项有多少层,都会按树形数据结构层次组织好,方便我们查询。
    

[AcceptVerbs(HttpVerbs.Post)]
        public ActionResult About(List<MyTreeViewItem> TreeView1_checkedNodes)
        {
            return View(this.GetAboutModel());
        }

 
        UI效果图:下图也是我上次没有回答出来的面试题答案效果图。

        

 

        下图是Controller接收到的参数:

 

   


        
        
       

      

 

posted on 2012-09-09 20:27  min.jiang  阅读(12227)  评论(20编辑  收藏  举报