最近我們的項目中需要用到樹型菜單,以前使用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 IList<MyTreeViewItem> Items { get; set; }
public string Value { get; set; }
public bool Checked { get; set; }
public string Text { get; set; }
public string Index { get; set; }
public MyTreeViewItem Parent { get; set; }
public string HtmlDomName { get; set; }
}
字段說明:
Text:用於顯示的文本,比如:總裁
Value:顯示文本對應的ID,比如:0
Index:這個是結合表單的輔助屬性,View上使用
Checked:當前菜單項是否被選中,用戶提交表單后我們可以通過這個屬性判斷用戶的選擇項
Items:當前菜單下的子菜單集合
Parent:當前菜單的上級菜單
HtmlDomName:這個是結合表單的輔助屬性,View上使用
樹形菜單的輸出:
我上次面試過程中的遞歸邏輯還是有用的,這里我們仍然采用遞歸來輸出菜單項。我創建了一個Partial View
<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 MyTreeViewItem NodeItem { get; set; }
}
View:這是主要是加載菜單,至於如何使用jquery-tree,這里就不多說了,大家下可以自己下載demo。
{
<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類型,無論菜單項有多少層,都會按樹形數據結構層次組織好,方便我們查詢。
public ActionResult About(List<MyTreeViewItem> TreeView1_checkedNodes)
{
return View( this.GetAboutModel());
}
UI效果圖:下圖也是我上次沒有回答出來的面試題答案效果圖。
下圖是Controller接收到的參數: