[修練營 ASP.NET & jQuery]將DualList設計成UserControl
前言
先來定義啥叫做DualList,請看畫面
就是有兩個ListBox(可多選),中間有按鈕可以在兩個ListBox之間搬移item。
因為應該還蠻多地方用的到這樣的需求,所以就動手把這樣的動作包成User control,
希望下次或大家有需要用的時候,直接把檔案貼過去就可以用。
Survey
這樣的需求看起來簡單,不過實作起來要考慮的東西還真是不少。
需考慮的issue:
- 按鈕的動作應該採用javascript,來提高效率(不考慮效率的話,當然就是UpdatePanel+Button.Click最好寫)
- postback後畫面上的資料不能改變(javascript的操作server端記不住)
- 頁面有沒有ScriptManager,UC外有沒被UpdatePanel框住,都要能正常運作
- 能輕易的取到兩個ListBox的值,也能輕易的binding相關DataSource
- 多個UserControl也要能正常運作
因為上面這些限制,讓我從一般頁面操作OK,一般頁面PostBack OK,UserControl OK,ScriptManager OK,UpdatePanel PostBack OK,多User Control OK。
一路改了很多次,最後動態註冊js的部分實在是很醜…不過功能倒是該有的都有了,
效率跟需求上也比前一篇[修練營 jQuery]plug-in “MultiSelect”簡介 更OK,(因為我這邊對操作後的item順序很care)
接下來,讓我們看下去…
Play it
廢話不多說,先來看我們的User Control畫面上的Code長怎樣
<table style="width: 800px; table-layout: fixed;">
<tr>
<td style="width: 380px;">
<asp:ListBox ID="LeftList" runat="server" Width="380px" Rows="10" SelectionMode="Multiple"
EnableViewState="false"></asp:ListBox>
</td>
<td style="width: 40px; text-align: center;">
<input id="toLeft" type="button" value=" < " runat="server" style="width: 40px;" /><br />
<input id="allLeft" type="button" value=" << " runat="server" style="width: 40px;" /><br />
<input id="toRight" type="button" value=" > " runat="server" style="width: 40px;" /><br />
<input id="allRight" type="button" value=" >> " runat="server" style="width: 40px;" /><br />
</td>
<td style="width: 380px;">
<asp:ListBox ID="RightList" runat="server" Width="380px" Rows="10" SelectionMode="Multiple"
EnableViewState="false"></asp:ListBox>
</td>
</tr>
</table>
<asp:HiddenField ID="hidLeftListValue" runat="server" />
<asp:HiddenField ID="hidRightListValue" runat="server" />
我這邊的設計是透過兩個Hidden來記錄左右ListBox的值,當PostBack的時候,再把這些item塞回去。
既然每次postback都會重塞,那麼ListBox的EnableViewState就順手把它關掉,能省則省一向是我的優點。
.ascx上就只有這樣,你沒看錯,那堆該死的js都要搬到server端動態去註冊…
為什麼不註冊在.ascx上?我一開始的確是這樣,而且還把ascx上的javascript都掛上<%= Control.ClientID%>,
結果碰到ScriptManager跟UpdatePanel,PostBack後竟然按鈕都沒反應了…
被逼的還是得到後端用ScriptManager把所有東西動態註冊。
所以,server端註冊javascript的code,相當然爾就很醜,大家就請多包涵。(長的醜就算了,debug也不是多容易啊...)
不過請放心,長的漂亮的code,我有放在ascx上註解起來,方便大家閱讀。
.ascx.cs
public ListBox LeftListBox
{
get { return this.LeftList; }
set { LeftList = value; }
}
public ListBox RightListBox
{
get { return this.RightList; }
set { RightList = value; }
}
/// <summary>
/// LeftListBox的Items
/// 以value,text;value,text的型態呈現
/// </summary>
public string LeftListBoxValue
{
get { return this.hidLeftListValue.Value; }
set { this.hidLeftListValue.Value = value; }
}
/// <summary>
/// RightListBox的Items
/// 以value,text;value,text的型態呈現
/// </summary>
public string RightListBoxValue
{
get { return this.hidRightListValue.Value; }
set { this.hidRightListValue.Value = value; }
}
屬性的部分,我們分別開出LeftListBox跟RightListBox,還有兩個hidden的值,方便我們直接取串好的字串來parse。
/// <summary>
/// 動態註冊javascript,如果頁面上有ScriptManager要透過Scriptmanager,每次Page重Load都要執行
/// 由於畫面上可能會有多個相同的UserControl,要避免執行的function相衝突,所以function Name也都設計成unique的方式
/// </summary>
private void RegisterJS()
{
string myJS=@"$(document).ready(function() {
$('#" + toLeft.ClientID + @"').click(toLeft_" + this.ClientID + @");
$('#" + RightList.ClientID + @"').dblclick(toLeft_" + this.ClientID + @");
$('#" + toRight.ClientID + @"').click(toRight_" + this.ClientID + @");
$('#" + LeftList.ClientID + @"').dblclick(toRight_" + this.ClientID + @");
$('#" + allLeft.ClientID + @"').click(AllLeft_" + this.ClientID + @");
$('#" + allRight.ClientID + @"').click(AllRight_" + this.ClientID + @");
RememberItems_" + this.ClientID + @"();
});
function toLeft_" +this.ClientID+@"() {
$('#" + RightList.ClientID + @" option:selected').remove().appendTo('#"+LeftList.ClientID+@"').removeAttr(""selected"");
RememberItems_" + this.ClientID + @"();
}
function toRight_" + this.ClientID + @"() {
$('#" + LeftList.ClientID + @" option:selected').remove().appendTo('#" + RightList.ClientID + @"').removeAttr(""selected"");
RememberItems_" + this.ClientID + @"();
}
function AllLeft_" + this.ClientID + @"() {
$('#" + RightList.ClientID + @" option').remove().appendTo('#" + LeftList.ClientID + @"').removeAttr(""selected"");
RememberItems_" + this.ClientID + @"();
}
function AllRight_" + this.ClientID + @"() {
$('#" + LeftList.ClientID + @" option').remove().appendTo('#" + RightList.ClientID + @"').removeAttr(""selected"");
RememberItems_" + this.ClientID + @"();
}
function RememberItems_" + this.ClientID + @"() {
var leftAllItem = '';
var rightAllItem = '';
$('#" + LeftList.ClientID + @"').find('option').each(function() { leftAllItem += $(this).text() + ',' + $(this).val() + ';'; });
$('#" + hidLeftListValue.ClientID + @"')[0].value = leftAllItem;
$('#"+RightList.ClientID+@"').find('option').each(function() { rightAllItem += $(this).text() + ',' + $(this).val() + ';'; });
$('#" + hidRightListValue.ClientID + @"')[0].value = rightAllItem;
}";
//請自行修改jQuery.js的參考路徑
if (ScriptManager.GetCurrent(this.Page) != null)
{
ScriptManager.RegisterClientScriptInclude(this.Page, this.Page.GetType(), "jQuery", "JavaScript/jquery-1.3.2-vsdoc2.js");
ScriptManager.RegisterClientScriptBlock(this.Page, this.Page.GetType(), this.ClientID + "dualList", myJS, true);
}
else
{
this.Page.ClientScript.RegisterClientScriptInclude("jQuery", "JavaScript/jquery-1.3.2-vsdoc2.js");
this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), this.ClientID + "dualList", myJS, true); }
}
動態組javascript的部分,不只註冊js要唯一,控制項ID要唯一,連function name都要唯一,否則畫面上有多個User Control時,
會出現雖然Render了兩段不一樣的function內容,但是由於function Name一樣,所以只有最後一個function會有作用。
其實client端的操作透過jQuery相當簡單,
嚴格來說精髓只有一行:
$('#<%=RightList.ClientID %> option:selected').remove().appendTo('#<%=LeftList.ClientID %>').removeAttr("selected");
把某邊的ListBox裡面選到的item,移走,加到另一邊的ListBox上,然後把選取的狀態清掉。
(這代表什麼....我上面多了好幾倍的code都是因為該死的WebForm、PostBack、UpdatePanel、還有MasterPage和UserControl害的...)
額外要練習到的部分,就是each跟this的用法,在RememberItems()裡我們把ListBox裡面每一個option用each來選取,
這時候each裡面的委派function,裡面用到的this,就等同於server端foreach var objectItems in collection裡面的objectItem一樣。
透過text()跟val()可以輕鬆的得到我們要的資訊。
剩下來的Code就只是在對的事件上,把item還原到ListBox裡,還有parse的規則。
protected void Page_Load(object sender, EventArgs e)
{
//註冊相關js
RegisterJS();
//每次postback上畫面上的list items還原至剛剛client端操作的狀態
if (IsPostBack)
{
string leftList = this.hidLeftListValue.Value;
string rightList = this.hidRightListValue.Value;
SplitToListItem(leftList, this.LeftList, ';', ',');
SplitToListItem(rightList, this.RightList, ';', ',');
}
}
//protected void Page_PreRender(object sender, EventArgs e)
//{
//}
/// <summary>
/// 將存放ListItemCollection字串binding至listbox上,區分item的符號為';',區分value與text的符號為','
/// </summary>
/// <param name="listString"></param>
/// <param name="bindingList"></param>
private void SplitToListItem(string listString, ListBox bindingList)
{
string List = listString.TrimEnd(';');
string[] eachItem = List.Split(';');
for (int i = 0; i < eachItem.Length; i++)
{
string[] item = eachItem[i].Split(',');
if (item.Length > 1)
{
bindingList.Items.Add(new ListItem(item[0], item[1]));
}
}
}
/// <summary>
/// 將存放ListItemCollection字串binding至listbox上
/// </summary>
/// <param name="listString"></param>
/// <param name="bindingList"></param>
/// <param name="itemSplit">用來區分item之間的符號</param>
/// <param name="valueSplit">用來區分value與text之間的符號</param>
private void SplitToListItem(string listString, ListBox bindingList, char itemSplit, char valueSplit)
{
string List = listString.TrimEnd(itemSplit);
string[] eachItem = List.Split(itemSplit);
for (int i = 0; i < eachItem.Length; i++)
{
string[] item = eachItem[i].Split(valueSplit);
if (item.Length > 1)
{
bindingList.Items.Add(new ListItem(item[0], item[1]));
}
}
}
最後來看我們的畫面:
結論
sample code裡是測試兩個UserControl放在UpdatePanel裡是否能正常運作,不互相影響,有興趣的人可以下載回去,
In 91 it!!
補充:2009/11/22,黑大的[CODE-用jQuery實作<select>選項上下移動(複選版)]
Sample Code: DualList.rar
blog 與課程更新內容,請前往新站位置:http://tdd.best/