[修練營 ASP.NET & jQuery]將DualList設計成UserControl

  • 10995
  • 0
  • 2009-11-22

[修練營 ASP.NET & jQuery]將DualList設計成UserControl

前言

先來定義啥叫做DualList,請看畫面

DualList

就是有兩個ListBox(可多選),中間有按鈕可以在兩個ListBox之間搬移item。

因為應該還蠻多地方用的到這樣的需求,所以就動手把這樣的動作包成User control,
希望下次或大家有需要用的時候,直接把檔案貼過去就可以用。


Survey

這樣的需求看起來簡單,不過實作起來要考慮的東西還真是不少。

需考慮的issue:

  1. 按鈕的動作應該採用javascript,來提高效率(不考慮效率的話,當然就是UpdatePanel+Button.Click最好寫)
  2. postback後畫面上的資料不能改變(javascript的操作server端記不住)
  3. 頁面有沒有ScriptManager,UC外有沒被UpdatePanel框住,都要能正常運作
  4. 能輕易的取到兩個ListBox的值,也能輕易的binding相關DataSource
  5. 多個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]));
            }
        }        
    }

最後來看我們的畫面:

demo


結論

sample code裡是測試兩個UserControl放在UpdatePanel裡是否能正常運作,不互相影響,有興趣的人可以下載回去,

In 91 it!!

補充:2009/11/22,黑大的[CODE-用jQuery實作<select>選項上下移動(複選版)]



Sample Code: DualList.rar


blog 與課程更新內容,請前往新站位置:http://tdd.best/