這個部落格裡,光是連動下拉選單為主題的文章我就寫了好幾篇,如下:
jQuery 對下拉選單 DropDownList 的操作 - 2:連動下拉選單
jQuery 練習題:ASP.NET MVC 連動下拉選單與 jQuery UI Autocomplete ComboBox
連動下拉選單 - 使用 jQuery EasyUI ComboBox
其實連動下拉選單的功能並不難做,當頁面上需要有連動下拉選單功能時,都需要再去動手做,從頁面、Javascript 程式以及後端程式,一次兩次的需求的話是還好,不會太麻煩,但是有很多頁面都需要做連動下拉選單的時候,就會覺得很麻煩了,最常見的就是「縣市鄉鎮市區連動下拉選單」,尤其是當有一堆表單或是同一個頁面上有很多個別欄位都需要縣市與鄉鎮市區資料的時候。
所以這邊就使用 ASP.NET MVC 的 Partial View 把縣市鄉鎮市區連動下拉選單的做成可以重複使用,而且可以應付同一個頁面有多組資料都需要縣市、鄉鎮市區連動下拉選單的需求。
P.S. 此功能是顯示台灣的縣市與鄉鎮市區資料。
Step.1
使用 ASP.NET MVC 4 with Bootstrap Layout 建立新專案,
有關「ASP.NET MVC 4 with Bootstrap Layout」的相關訊息可以參閱之前的文章:「使用 ASP.NET MVC 4 Bootstrap Layout Template (VS2012)」
建立完成的專案內容:
Step.2
加入 LocalDB 並且匯入資料以及建立 ADO.NET 實體資料模型
ZipCode
建立 ADO.NET 實體資料模型
Step.3
建立 LocalDB 與 ADO.NET 實體資料模型之後,正當要重新建置專案卻出現了錯誤訊息,
這是因為 「ASP.NET MVC 4 with Bootstrap Layout」這個 Project Template 所建立的專案檔預設是會對 View 進行編譯的,
如果把 csproj 檔案裡的 MvcBuildViews 修改為 false,那麼編譯就不會報錯,但是還是要解決 MvcBuildViews 為 true 時編譯出現錯誤的問題,解決的方式就是在專案根目錄下的 Web.Config 去加入 System.Data.Entity.Design 的 Assembly,
<compilation debug="true" targetFramework="4.5">
<assemblies>
<add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add assembly="System.Data.Entity.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</assemblies>
</compilation>
Step.4
建立 IZipCodeService 與其實作 ZipCodeService,
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Linq.Expressions;
5: using System.Web;
6: using ReuseableCascadeDropdownlist.Models;
7:
8: namespace ReuseableCascadeDropdownlist.Services
9: {
10: public class ZipCodeService : IZipCodeService
11: {
12: private DatabaseEntities db = new DatabaseEntities();
13:
14: /// <summary>
15: /// Determines whether the specified predicate is exists.
16: /// </summary>
17: /// <param name="predicate">The predicate.</param>
18: /// <returns></returns>
19: public bool IsExists(Expression<Func<ZipCode, bool>> predicate)
20: {
21: return this.db.ZipCode.Any(predicate);
22: }
23:
24: /// <summary>
25: /// Totals the count.
26: /// </summary>
27: /// <param name="predicate">The predicate.</param>
28: /// <returns></returns>
29: public int TotalCount(Expression<Func<ZipCode, bool>> predicate)
30: {
31: return this.db.ZipCode.Count(predicate);
32: }
33:
34:
35: /// <summary>
36: /// Finds the one.
37: /// </summary>
38: /// <param name="id">The identifier.</param>
39: /// <returns></returns>
40: public ZipCode FindOne(int id)
41: {
42: if (!this.IsExists(x => x.ID == id))
43: {
44: return null;
45: }
46: if (!this.IsExists(x => x.ID == id))
47: {
48: return null;
49: }
50: return this.db.ZipCode.FirstOrDefault(x => x.ID == id);
51: }
52:
53: /// <summary>
54: /// Finds the one by postal code.
55: /// </summary>
56: /// <param name="postalCode">The postal code.</param>
57: /// <returns></returns>
58: /// <exception cref="System.ArgumentNullException">沒有輸入 PostalCode</exception>
59: public ZipCode FindOneByPostalCode(int postalCode)
60: {
61: if (!this.IsExists(x => x.PostalCode == postalCode))
62: {
63: return null;
64: }
65: if (!this.IsExists(x => x.PostalCode == postalCode))
66: {
67: return null;
68: }
69: return this.db.ZipCode.FirstOrDefault(x => x.PostalCode == postalCode);
70: }
71:
72: /// <summary>
73: /// Finds all.
74: /// </summary>
75: /// <returns></returns>
76: public IQueryable<ZipCode> FindAll()
77: {
78: return this.db.ZipCode.AsQueryable();
79: }
80:
81: /// <summary>
82: /// Finds the specified predicate.
83: /// </summary>
84: /// <param name="predicate">The predicate.</param>
85: /// <returns></returns>
86: /// <exception cref="System.NotImplementedException"></exception>
87: public IQueryable<ZipCode> Find(Expression<Func<ZipCode, bool>> predicate)
88: {
89: return this.db.ZipCode.Where(predicate);
90: }
91:
92:
93: /// <summary>
94: /// Gets all cities.
95: /// </summary>
96: /// <returns></returns>
97: public Dictionary<string, string> GetAllCities()
98: {
99: var query = (from c in this.FindAll()
100: where c.IsEnabled
101: select new
102: {
103: CityCode = c.CitySort.ToString(),
104: CityName = c.City
105: })
106: .Distinct().OrderBy(x => x.CityCode);
107:
108: return query.ToDictionary(x => x.CityCode, x => x.CityName);
109: }
110:
111: /// <summary>
112: /// Gets all city dictinoary.
113: /// </summary>
114: /// <returns></returns>
115: /// <exception cref="System.NotImplementedException"></exception>
116: public Dictionary<string, string> GetAllCityDictinoary()
117: {
118: var query = (from c in this.FindAll()
119: where c.IsEnabled
120: select new
121: {
122: CityCode = c.CitySort,
123: CityName = c.City
124: })
125: .Distinct().OrderBy(x => x.CityCode);
126:
127: Dictionary<string, string> dict = new Dictionary<string, string>();
128:
129: foreach (var item in query)
130: {
131: if (dict.Keys.Count(x => x.Equals(item.CityName)).Equals(0))
132: {
133: dict.Add(item.CityName, item.CityName);
134: }
135: }
136: return dict;
137: }
138:
139: /// <summary>
140: /// Gets the name of the county by city.
141: /// </summary>
142: /// <param name="cityName">Name of the city.</param>
143: /// <returns></returns>
144: public Dictionary<string, string> GetCountyByCityName(string cityName)
145: {
146: var query = (from c in this.FindAll()
147: where c.IsEnabled && c.City == cityName
148: select new
149: {
150: PostalCode = c.PostalCode,
151: CountyName = c.County,
152: Sort = c.PostalCode
153: })
154: .Distinct().OrderBy(x => x.Sort);
155:
156: return query.ToDictionary(x => x.PostalCode.ToString(), x => x.CountyName);
157: }
158:
159:
160: public void Dispose()
161: {
162: this.db.Dispose();
163: }
164: }
165: }
Step.5
建立 ZipCodeController
2014-07-03 更新:
誤把 ZipCodeService.cs 的程式內容給貼上來,而沒有貼上正確的 ZipCodeController.cs,不過在 GitHub 上面所提供的程式並沒有問題,都是正確且可以執行。
using System.Text;
using System.Web.Mvc;
using ReuseableCascadeDropdownlist.Services;
namespace ReuseableCascadeDropdownlist.Controllers
{
public class ZipCodeController : Controller
{
private readonly IZipCodeService service;
public ZipCodeController()
{
this.service = new ZipCodeService();
}
/// <summary>
/// Gets the city drop downlist.
/// </summary>
/// <param name="selectedCity">The selected city.</param>
/// <returns></returns>
public ActionResult GetCityDropDownlist(string selectedCity)
{
StringBuilder sb = new StringBuilder();
var cities = this.service.GetAllCityDictinoary();
if (string.IsNullOrWhiteSpace(selectedCity))
{
foreach (var item in cities)
{
sb.AppendFormat("<option value=\"{0}\">{1}</option>", item.Key, item.Value);
}
}
else
{
foreach (var item in cities)
{
sb.AppendFormat("<option value=\"{0}\" {2}>{1}</option>",
item.Key,
item.Value,
item.Key.Equals(selectedCity) ? "selected=\"selected\"" : "");
}
}
return Content(sb.ToString());
}
/// <summary>
/// Gets the county drop downlist.
/// </summary>
/// <param name="cityName">Name of the city.</param>
/// <param name="selectedCounty">The selected county.</param>
/// <returns></returns>
public ActionResult GetCountyDropDownlist(string cityName, string selectedCounty)
{
if (!string.IsNullOrWhiteSpace(cityName))
{
StringBuilder sb = new StringBuilder();
var counties = this.service.GetCountyByCityName(cityName);
if (string.IsNullOrWhiteSpace(selectedCounty))
{
foreach (var item in counties)
{
sb.AppendFormat("<option value=\"{0}\">{1}</option>",
item.Key,
string.Concat(item.Key, " ", item.Value)
);
}
}
else
{
foreach (var item in counties)
{
sb.AppendFormat("<option value=\"{0}\" {2}>{1}</option>",
item.Key,
string.Concat(item.Key, " ", item.Value),
item.Key.Equals(selectedCounty) ? "selected=\"selected\"" : "");
}
}
return Content(sb.ToString());
}
return Content(string.Empty);
}
protected override void Dispose(bool disposing)
{
this.service.Dispose();
base.Dispose(disposing);
}
}
}
Step.6
建立 TaiwanZipCode.js
1: ;
2: (function (window) {
3: //===========================================================================================
4: if (typeof (jQuery) === 'undefined') { alert('jQuery Library NotFound.'); return; }
5:
6: var TaiwanZipCode = window.TaiwanZipCode =
7: {
8: ActionUrls: {}
9: };
10: //===========================================================================================
11:
12: jQuery.extend(TaiwanZipCode, {
13:
14: Initialize: function (actionUrls) {
15: /// <summary>初始化函式</summary>
16: /// <param name="actionUrls" type="Object"></param>
17:
18: jQuery.extend(TaiwanZipCode.ActionUrls, actionUrls);
19: },
20:
21: Settings: function (options) {
22:
23: //var options = {
24: // CityID: '縣市下拉選單 Tag ID',
25: // CountyID: '鄉鎮市區下拉選單 Tag ID',
26: // SelectedCity: '已選擇縣市',
27: // SelectedCounty: '已選擇鄉鎮市區'
28: //};
29:
30: TaiwanZipCode.SetCityDropDownlist(options.CityID, options.SelectedCity);
31: $(options.CityID).change(function () {
32: TaiwanZipCode.SetCountyDropDownlist(options.CityID, options.CountyID, options.SelectedCounty);
33: });
34: $(options.CityID).trigger('change');
35: },
36:
37: SetCityDropDownlist: function (cityDropDownListID, selectedCityCode) {
38: /// <summary>設定縣市下拉選單</summary>
39: /// <param name="cityDropDownListID" type="Object">縣市下拉選單ID</param>
40: /// <param name="selectedCityCode" type="Object">預選縣市編號</param>
41:
42: $(cityDropDownListID).empty().append($('<option></option>').val('').text('請選擇'));
43: $.ajax({
44: url: TaiwanZipCode.ActionUrls.GetCityDropDownlist,
45: data: { selectedCity: selectedCityCode },
46: type: 'post',
47: cache: false,
48: async: false,
49: dataType: 'html',
50: success: function (data) {
51: if (data.length > 0) {
52: $(cityDropDownListID).append(data);
53: }
54: }
55: });
56: },
57:
58: SetCountyDropDownlist: function (cityDropDownListID, countyDropDownListID, selectedPostalCode) {
59: /// <summary>設定鄉鎮市區下拉選單</summary>
60: /// <param name="cityDropDownListID" type="Object">縣市下拉選單ID</param>
61: /// <param name="countyDropDownListID" type="Object">鄉鎮市區下拉選單ID</param>
62: /// <param name="selectedPostalCode" type="Object">預選鄉鎮市區號</param>
63:
64: var selectedCity = $.trim($(cityDropDownListID + ' option:selected').val());
65: $(countyDropDownListID).empty().append($('<option></option>').val('').text('請選擇'));
66: $.ajax({
67: url: TaiwanZipCode.ActionUrls.GetCountyDropDownlist,
68: data: { cityName: selectedCity, selectedCounty: selectedPostalCode },
69: type: 'post',
70: cache: false,
71: async: false,
72: dataType: 'html',
73: success: function (data) {
74: if (data.length > 0) {
75: $(countyDropDownListID).append(data);
76: }
77: }
78: });
79: }
80:
81: });
82: })
83: (window);
Step.7
在 ~/Views/Shared 目錄下建立 Partial View「_TanwanZipCode.cshtml」
1: <script src="~/Scripts/TaiwanZipCode.js"></script>
2: <script type="text/javascript">
3: $(function () {
4: var ActionUrls =
5: {
6: GetCityDropDownlist: '@Url.Action("GetCityDropDownlist", "ZipCode", new { Area = "" })',
7: GetCountyDropDownlist: '@Url.Action("GetCountyDropDownlist", "ZipCode", new { Area = "" })'
8: };
9: TaiwanZipCode.Initialize(ActionUrls);
10: });
11: </script>
使用一
前面的步驟完成了縣市、鄉鎮市區下拉選單的後端程式、前端程式,而接下來要實際使用在頁面上,先建立一個 SampleController,並且加入一個檢視,在這個檢視頁面上放置兩個下拉選單,分別為縣市與鄉鎮市區,
接著就是在這個檢視頁面上使用剛才所建立的 TaiwanZipCode,Partial View「_TaiwanZipCode.cshtml」的內容其實是用來設定取得縣市與鄉鎮市區資料的連結位置,所以放置的地方會在 scripts 這個 setcion 裡,也就是載入 jQuery 之後的地方,
執行結果:
使用二
在頁面上如果有兩組資料都需要使用到縣市鄉鎮市區連動下拉選單,則兩組資料所使用的 Select Tag 必須要有所區隔,除此之外,使用上的方式與上面是相同的,
1: @{
2: ViewBag.Title = "Index2";
3: }
4:
5: <h2>Sample - 兩組資料</h2>
6:
7: <form class="form-horizontal well">
8: <fieldset>
9: <legend>Personal</legend>
10: <div class="control-group">
11: <label class="control-label" for="PersonalCityDDL">縣市</label>
12: <div class="controls">
13: <select id="PersonalCityDDL"></select>
14: </div>
15: </div>
16: <div class="control-group">
17: <label class="control-label" for="PersonalCountyDDL">鄉鎮市區</label>
18: <div class="controls">
19: <select id="PersonalCountyDDL"></select>
20: </div>
21: </div>
22: </fieldset>
23: </form>
24:
25: <form class="form-horizontal well">
26: <fieldset>
27: <legend>Company</legend>
28: <div class="control-group">
29: <label class="control-label" for="CompanyCityDDL">縣市</label>
30: <div class="controls">
31: <select id="CompanyCityDDL"></select>
32: </div>
33: </div>
34: <div class="control-group">
35: <label class="control-label" for="CompanyCountyDDL">鄉鎮市區</label>
36: <div class="controls">
37: <select id="CompanyCountyDDL"></select>
38: </div>
39: </div>
40: </fieldset>
41: </form>
執行結果:
使用三
在編輯的時候會先把原本的資料給帶出來,那麼在這個連動下拉選單也可以設定已選擇的資料,無論是在後端使用 ViewBag 或 Model 還是在前端程式裡設定都可以,
建立檢視頁面所需要用到的 Model,
Controller 的設定
檢視頁面的 Html 內容
1: @model ReuseableCascadeDropdownlist.ViewModels.SampleViewModel
2: @{
3: ViewBag.Title = "Index3";
4: }
5:
6: <h2>Sample - 設定已選擇項目</h2>
7:
8: <form class="form-horizontal well">
9: <fieldset>
10: <legend>Normal</legend>
11: <div class="control-group">
12: <label class="control-label" for="CityDDL">縣市</label>
13: <div class="controls">
14: <select id="CityDDL"></select>
15: </div>
16: </div>
17: <div class="control-group">
18: <label class="control-label" for="CountyDDL">鄉鎮市區</label>
19: <div class="controls">
20: <select id="CountyDDL"></select>
21: </div>
22: </div>
23: </fieldset>
24: </form>
25:
26: <form class="form-horizontal well">
27: <fieldset>
28: <legend>Personal</legend>
29: <div class="control-group">
30: <label class="control-label" for="PersonalCityDDL">縣市</label>
31: <div class="controls">
32: <select id="PersonalCityDDL"></select>
33: </div>
34: </div>
35: <div class="control-group">
36: <label class="control-label" for="PersonalCountyDDL">鄉鎮市區</label>
37: <div class="controls">
38: <select id="PersonalCountyDDL"></select>
39: </div>
40: </div>
41: </fieldset>
42: </form>
43:
44: <form class="form-horizontal well">
45: <fieldset>
46: <legend>Company</legend>
47: <div class="control-group">
48: <label class="control-label" for="CompanyCityDDL">縣市</label>
49: <div class="controls">
50: <select id="CompanyCityDDL"></select>
51: </div>
52: </div>
53: <div class="control-group">
54: <label class="control-label" for="CompanyCountyDDL">鄉鎮市區</label>
55: <div class="controls">
56: <select id="CompanyCountyDDL"></select>
57: </div>
58: </div>
59: </fieldset>
60: </form>
Javascript 設定內容
執行結果
GitHub
已經把此次實作的專案發佈到 GitHub 上,Repository 位置如下:
https://github.com/kevintsengtw/MVC_Reuseable_Cascade_DropDownList
這次算是做個簡單的應用,利用 ASP.NET MVC Partial View 的特性以及前端程式的應用,前端程式的部分並沒有做得很彈性,而只限於縣市與鄉鎮市區的資料連動,不是只有縣市鄉鎮市區連動下拉選單能用這樣的處理,一些常常會用到的連動下拉選單也可以使用相同的方式來做。
以上
ZipCodeController的code放成ZipCodeService 的Code了
回覆刪除Hello, 你好
刪除感謝你的告知,已經將內容做更正了,謝謝。