2011年7月16日 星期六

ASP.NET MVC 擴充HtmlHelper 加入 CheckBoxList 功能 - 1

以前在寫ASP.NET WebForm的時候,

若是遇到要加入一個群組的CheckBox時,直覺反應就是使用CheckBoxList控制項,

但是現在寫ASP.NET MVC後,HtmlHelper有提供CheckBox可以使用,但卻少了CheckBoxList,

以致於有時候遇到要使用一堆CheckBox時,就是在原始碼中敲一對的 Html.CheckBox(……….)。

image

這時候我們就可以對HtmlHelper進行擴充,自己加入CheckBoxList功能。

ASP.NET MVC所提供的CheckBox相當陽春,只能去定義CheckBox的Value,卻無法去定義後面顯示的文字。

<%= Html.CheckBoxFor(model => model.Invoice.Product1)%> <%= Html.LabelFor(model => model.Invoice.Product1)%>

所以我們要加入的CheckBocxList功能也需要讓使用者可以去定義要顯示的文字,

另外也需要可以去讓某些CheckBox可以預設選取。

一開始我所參考的程式是 Jeremiah Clark’s Blog 所發佈的一篇文章:CheckBoxList Helper for MVC

他所提供的CheckBoxList有另外自定義一個類別 CheckBoxListInfo

這就有如 Html.DropDownList 中所指定的類別 SelectListItem 一樣,SelectListItem可以去設定Select Tag中的Option.

而 CheckBoxListInfo 就是可以去設定個別的CheckBox,

此類別的三個屬性分別為:Value, DisplayText, IsChecked,我就不多說明。

使用上,可以先在Controller/Action中將需要放到CheckBoxList的資料整理好後,

放到ViewModel或是ViewData中,再給ViewPage使用。

public ActionResult CheckBoxListTest()
{
	var cities = _cityService.GetCollection();
	List<CheckBoxListInfo> infos = new List<CheckBoxListInfo>();
	foreach (var item in cities)
	{
		infos.Add(new CheckBoxListInfo(item.SORT.ToString(), string.Concat(item.NAME, "-", item.SORT.ToString()), false));
	}
	ViewData["CheckBoxListOfCities"] = infos;
	return View();
}

上面的程式就是先將資料取出後,放到List<CheckBoxListInfo>的集合中,再放到ViewData讓ViewPage可以使用。

而ViewPage的使用就很簡單,如下:

<%= Html.CheckBoxList("CityDDL", (List<CheckBoxListInfo>)ViewData["CheckBoxListOfCities"], null) %>

最後來看看所呈現的畫面結果:

image

所呈現的是一個直行的方式。


如果說我就這樣拿別人的程式來介紹的話,我就真的很不上道了,

因為根據原作者的程式所呈現的CheckBoxList只能夠一個直行的方式呈現,

不能夠像ASP.NET WebForm的CheckBoxList一樣,可以去設定水平或是垂直顯示,每個Repeat的個數為多少。

所以我就在這邊先改變一下程式,先讓我們的CheckBoxList可以去設定每列顯示個數,然後以水平方向呈現,

以下就是程式的內容:

public static partial class CheckBoxListExtensions
{
	#region CheckBoxList
	/// <summary>
	/// CheckBoxList.
	/// </summary>
	/// <param name="htmlHelper">The HTML helper.</param>
	/// <param name="name">The name.</param>
	/// <param name="listInfo">CheckBoxListInfo.</param>
	/// <returns></returns>
	public static string CheckBoxList(this HtmlHelper htmlHelper, string name, List<CheckBoxListInfo> listInfo)
	{
		return htmlHelper.CheckBoxList
		(
			name,
			listInfo,
			(IDictionary<string, object>)null,
			0
		);
	}
	/// <summary>
	/// CheckBoxList.
	/// </summary>
	/// <param name="htmlHelper">The HTML helper.</param>
	/// <param name="name">The name.</param>
	/// <param name="listInfo">CheckBoxListInfo.</param>
	/// <param name="htmlAttributes">The HTML attributes.</param>
	/// <returns></returns>
	public static string CheckBoxList(this HtmlHelper htmlHelper, string name, List<CheckBoxListInfo> listInfo, object htmlAttributes)
	{
		return htmlHelper.CheckBoxList
		(
			name,
			listInfo,
			(IDictionary<string, object>)new RouteValueDictionary(htmlAttributes),
			0
		);
	}
	/// <summary>
	/// CheckBoxList.
	/// </summary>
	/// <param name="htmlHelper">The HTML helper.</param>
	/// <param name="name">The name.</param>
	/// <param name="listInfo">The list info.</param>
	/// <param name="htmlAttributes">The HTML attributes.</param>
	/// <returns></returns>
	public static string CheckBoxList(this HtmlHelper htmlHelper, string name, List<CheckBoxListInfo> listInfo, IDictionary<string, object> htmlAttributes)
	{
		if (String.IsNullOrEmpty(name))
		{
			throw new ArgumentException("必須給這些CheckBoxList一個Tag Name", "name");
		}
		if (listInfo == null)
		{
			throw new ArgumentNullException("必須要給List<CheckBoxListInfo> listInfo");
		}
		if (listInfo.Count < 1)
		{
			throw new ArgumentException("List<CheckBoxListInfo> listInfo 至少要有一組資料", "listInfo");
		}
		StringBuilder sb = new StringBuilder();
		int lineNumber = 0;
		foreach (CheckBoxListInfo info in listInfo)
		{
			lineNumber++;
			TagBuilder builder = new TagBuilder("input");
			if (info.IsChecked)
			{
				builder.MergeAttribute("checked", "checked");
			}
			builder.MergeAttributes<string, object>(htmlAttributes);
			builder.MergeAttribute("type", "checkbox");
			builder.MergeAttribute("value", info.Value);
			builder.MergeAttribute("name", name);
			builder.InnerHtml = string.Format(" {0} ", info.DisplayText);
			sb.Append(builder.ToString(TagRenderMode.Normal));
			sb.Append("</br>");
		}
		return sb.ToString();
	}
	/// <summary>
	/// CheckBoxList.
	/// </summary>
	/// <param name="htmlHelper">The HTML helper.</param>
	/// <param name="name">The name.</param>
	/// <param name="listInfo">The list info.</param>
	/// <param name="htmlAttributes">The HTML attributes.</param>
	/// <param name="direction">The direction.</param>
	/// <param name="number">每個Row的顯示個數.</param>
	/// <returns></returns>
	public static string CheckBoxList(this HtmlHelper htmlHelper, string name, List<CheckBoxListInfo> listInfo, IDictionary<string, object> htmlAttributes, int number)
	{
		if (String.IsNullOrEmpty(name))
		{
			throw new ArgumentException("必須給這些CheckBoxList一個Tag Name", "name");
		}
		if (listInfo == null)
		{
			throw new ArgumentNullException("必須要給List<CheckBoxListInfo> listInfo");
		}
		if (listInfo.Count < 1)
		{
			throw new ArgumentException("List<CheckBoxListInfo> listInfo 至少要有一組資料", "listInfo");
		}
		StringBuilder sb = new StringBuilder();
		int lineNumber = 0;
		foreach (CheckBoxListInfo info in listInfo)
		{
			lineNumber++;
			TagBuilder builder = new TagBuilder("input");
			if (info.IsChecked)
			{
				builder.MergeAttribute("checked", "checked");
			}
			builder.MergeAttributes<string, object>(htmlAttributes);
			builder.MergeAttribute("type", "checkbox");
			builder.MergeAttribute("value", info.Value);
			builder.MergeAttribute("name", name);
			builder.InnerHtml = string.Format(" {0} ", info.DisplayText);
			sb.Append(builder.ToString(TagRenderMode.Normal));
			if (number == 0)
			{
				sb.Append("<br />");
			}
			else if (lineNumber % number == 0)
			{
				sb.Append("<br />");
			}
		}
		return sb.ToString();
	}
	#endregion
}
public class CheckBoxListInfo
{
	public string Value { get; private set; }
	public string DisplayText { get; private set; }
	public bool IsChecked { get; private set; }
	public CheckBoxListInfo(string value, string displayText, bool isChecked)
	{
		this.Value = value;
		this.DisplayText = displayText;
		this.IsChecked = isChecked;
	}
}

於原本的Method再去多增加一個多載,多增加一個參數,讓user可以設定每列所呈現的CheckBox個數。

而原本Controller/Action去取得資料、整理成List<CheckBoxListInfo>的內容不變動,而有變動的地方就是ViewPage。

<%= Html.CheckBoxList("CityDDL", (List<CheckBoxListInfo>)ViewData["CheckBoxListOfCities"], null, 5) %>

以下就是呈現的CheckBoxList畫面

image

這裡就先做好水平方向的呈現功能,因為垂直方向的呈現比較麻煩一點,

不像水平方向呈現只要依據顯示個數然後去做折行的動作即可,

垂直方向的呈現,就必須考慮到全部資料數量與呈現個數,

再去計算每個直行或是橫列要顯示哪些CheckBoxListInfo內容>

我之前有嘗試去做,但是做好之後總是會卡在各種顯示個數的不同而讓呈現結果不如我的預期,

有興趣的人可以去參考一下ASP.NET WebForm的原始碼,

其中在System.Web.UI.WebControls.RepeatInfoRenderVerticalRepeater

這個就是WebForm CheckBoxList去呈現一個VerticalRepeater的實作。

如果我有做出來,我再將實作的內容分享給各位。

 

另外提一下,製作這類的Html.Helper時,因為是要在頁面上呈現Html Tag,

所以有很多人都會直接在程式碼中以接字串或是直接把Html Code寫在程式之中,

其實這樣不是很好,並不鼓勵這樣的寫法,雖然同樣達到目的,但是.Net Framework已經有提供更好的方式

TagBuilder

以下是MSDN對於TagBuilder的介紹,多用程式以及寫好的類別來做那些手工的動作總是比較好的。

http://msdn.microsoft.com/zh-tw/library/system.web.mvc.tagbuilder.aspx


參考資料:

CheckBoxList.RepeatDirection 屬性
http://msdn.microsoft.com/zh-tw/library/system.web.ui.webcontrols.checkboxlist.repeatdirection(v=VS.80).aspx

另外也有人做了不同的CheckBoxList,只不過他是將要呈現的Text與Value分別用IEnumerable<string>給CheckBoxList,參考一下不同的做法
http://gerardocontijoch.wordpress.com/2009/07/04/un-checkboxlist-que-funciona-en-asp-net-mvc/



3 則留言:

  1. 隔了很久,終於把Vertical顯示的CheckBoxList給交出來了
    「ASP.NET MVC 擴充HtmlHelper 加入 CheckBoxList 功能 - 2」
    http://kevintsengtw.blogspot.com/2012/02/aspnet-mvc-htmlhelper-checkboxlist-2.html

    回覆刪除
  2. 請問若使用這個產生出來的CheckBox要如何在Controller接勾選了哪些呢?
    使用FormCollection去接會回傳null

    回覆刪除
    回覆
    1. 建議改看這一篇的內容
      http://kevintsengtw.blogspot.com/2014/06/aspnet-mvc-5-with-checkboxlist.html

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。