[ASP.net MVC4] 使用@Html.DropDownListFor()+Partial View部份檢視實現連動下拉選單(無for-loop方式)

[ASP.net MVC4] 使用@Html.DropDownListFor()+Partial View部份檢視實現連動下拉選單CascadingDropDown(無for-loop方式)

前言

之前文章提過,要產生表單輸入標籤:[ASP.net MVC 4] 生成控制項的方式選擇(以文字方塊TextBox為例)

儘量使用@Html.XXXFor()方式,可以享有intellisense不容易打錯字、ViewModel修改屬性名稱時Visual Studio可以在編譯時期幫忙檢查錯誤的好處

連動下拉選單的第二層DropDownList呢?

一般做法在Controller組完第二層下拉選單的Html字串或Json字串後,回傳給Ajax的callback function來顯示第二層下拉選單

※參見:[ASP.net MVC] ViewModel修改資料+DropDownList下拉選單連動

如果想改用@Html.DropDownListFor()的話?

請見以下

 

實作

1.連動下拉選單的傳統做法流程

Step 1.當第一層下拉選單觸發javascript的onchange事件

Step 2.在onchange事件中,取得第一層下拉選單選擇的值

Step 3.把選擇的值透過Ajax傳給Controller的Action

Step 4.Action依據剛剛的值產生不同的第二層選單List並回傳

Step 5.Ajax的callback function接到回傳值後,顯示在View上

改用@Html.DropDownListFor()方式,流程也是一樣

但由於要用Html Helper來呈現第二層下拉選單,所以在第4步驟需要追加一個Partial View(部份檢視),放入@Html.DropDownListFor()語法


2.

因為部份檢視裡會用到@Html.DropDownListFor()來顯示第二層下拉選單,此部份檢視(Partial View)可以宣告使用ViewModel

在Models資料夾加入SecondLevelViewModel.cs類別

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MvcApplicationDropDownList.Models
{
    public class SecondLevelViewModel
    {
        public string FirstLevel { get; set; }
        public string SecondLevel { get; set; }
    }
}

Controller完整代碼

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplicationDropDownList.Models;

namespace MvcApplicationDropDownList.Controllers
{
    public class HomeController : Controller
    {
        //資料來源
        //第一層資料
        List<string> firstLevelItems = new List<string>() { 
         "A","B"
        };
        //第二層資料
        List<SecondLevelViewModel> secondLevelItems = new List<SecondLevelViewModel>();
        public HomeController()
        {
            //第一層有A、B
            //第二層為{A1、A2、A3}或{B1、B2}
            secondLevelItems.Add(new SecondLevelViewModel() { FirstLevel = "A", SecondLevel = "A1"});
            secondLevelItems.Add(new SecondLevelViewModel() { FirstLevel = "A", SecondLevel = "A2" });
            secondLevelItems.Add(new SecondLevelViewModel() { FirstLevel = "A", SecondLevel = "A3" });
            secondLevelItems.Add(new SecondLevelViewModel() { FirstLevel = "B", SecondLevel = "B1" });
            secondLevelItems.Add(new SecondLevelViewModel() { FirstLevel = "B", SecondLevel = "B2" });
            
        }
        [HttpGet]
        public ActionResult Index()
        {
            //用Linq撈出第一層資料,資料來源可能是DB、Web API...等等
            var items = (from c in firstLevelItems
                         select new SelectListItem { Text=c,Value=c }).ToList();
            //加入「請選擇」
            items.Insert(0, new SelectListItem() { Text="請選擇",Value="-1" });
            ViewData["FirstLevelItems"] = items;

            return View();
        }
        //使用Get Method是為了讓瀏覽器輸入Url方便觀察值的變化
        [HttpGet]
        public ActionResult ShowSecondDropDownList(string FirstLevel)
        {
            //使用Linq撈出第二層的資料
            var items = (from s in secondLevelItems
                         where s.FirstLevel == FirstLevel
                         select new SelectListItem  { Text=s.SecondLevel,Value=s.SecondLevel }).ToList();
            //加入「請選擇」
            items.Insert(0, new SelectListItem() { Text = "請選擇", Value = "-1" });
            ViewData["SecondLevelItems"] = items;
            return View();
        }

    }
}

在Views資料夾下加入Shared資料夾並加入部份檢視ShowSecondDropDownList.cshtml

@model MvcApplicationDropDownList.Models.SecondLevelViewModel

@Html.DropDownListFor(Model=>Model.SecondLevel,(IEnumerable<SelectListItem>)ViewData["SecondLevelItems"])

而Index.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
    <script type="text/javascript">
        $(document).ready(init);
        function init()
        {
            //當網頁上的DOM都載入時,第一層下拉選單也呈現出來了
            //↓顯示第二層下拉選單,沒加這兩段↓的話,第一次網頁載入時第二層下拉選單不會出現
            var FirstLevel = $("select[name='YourDropDownListName']").val();//第一層選擇的值
            ShowSecondDropDownList(FirstLevel);

            //為第一層下拉選單註冊onchange事件
            $("select[name='YourDropDownListName']").change(function () {
                var FirstLevel = $(this).val();//選擇的值
                ShowSecondDropDownList(FirstLevel);
            });
        }
        function ShowSecondDropDownList(FirstLevel)
        {
            $.ajax({
                url: "@Url.Action("ShowSecondDropDownList","Home")",
                 type: "get",
                 data: { FirstLevel: FirstLevel },
                 async: false,
                 success: function (data) {
                     $("span").empty().html(data);
                 }
             });
        }
    </script>
</head>
<body>
    @*第一層下拉選單,因為範例關係,這頁沒有ViewModel,不然應該儘量用DropDownListFor()*@
    @Html.DropDownList("YourDropDownListName", (IEnumerable<SelectListItem>)ViewData["FirstLevelItems"])
    <span></span>
    @*span裡要放第二層下拉選單的Html字串*@
</body>
</html>

圖解說明各流程

image

簡單明瞭~

連動下拉選單完成~

image

 

因為之前MSDN論壇有人問過↓類似問題,所以再把以下情況順便說明

如果Index.cshtml這個View剛好是編修畫面的View

狀況:假設會員甲在DB Table中儲存的選單資料是A、A3,然後進到編修畫面

以圖解說明,如何在Index.cshtml一載入畫面時,兩個下拉選單就已經預先選好A、A3

先看View(Index.cshtml)

image

再看Controller

image

注意上面兩張圖畫線畫框的地方即可完成

※我是覺得這樣就差不多,但執行後可以發現

就算使用者下拉第一層選單資料"A"時,第二層選單仍會停選在A3,也許有些人覺得正常應該要從”請選擇”開始選吧?

這其實有解法的,就交給看倌們自行發揮囉

image

 

 

結語

實務上,兩個下拉選單的值應該會用其他DB資料表來儲存

套用本範例時,請注意追加修改第二層下拉選單的ViewModel

在controller用LINQ撈出資料List,搭配View使@Html.DropDownListFor()即可達到不必寫for-loop也可以產生下拉選單

 

本文範例檔,放到MSDN訂閱期滿。