2011年10月25日 星期二

不可不知的按鈕屬性「OnClientClick」與「UseSubmitBehavior」:以購物車結帳按鈕為例

◎以上程式範例Aspnet.aspx,如在頁框下不能操作,請開新視窗操作
◎如果有問題歡迎您提出,dnowba很需要有人和我一起討論

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        ' ************************************************************
        ' ********** 第一部分:按鈕與OnClientClick介紹*******************************
        '  ***********************************************************

        Me.Button1.Text = "確定輸出文字"
        Me.LinkButton1.Text = "確定輸出文字"
        Me.ImageButton1.ImageUrl = "~/Images/checkmark.png"
        Me.ImageButton1.ToolTip = "輸出文字"
        Me.ImageButton1.Width = "25"

        ' 可以利用按鈕的OnClientClick屬性,附帶簡單的JavaScript程式
        ' 這個屬性輸出 HTML 碼後,也就是html碼的 onclick = "xxxx"
        ' 也可以直接寫在設計頁中屬性視窗
        Me.Button1.OnClientClick = "window.alert('警告視窗:即將輸出文字');"
        Me.LinkButton1.OnClientClick = "return confirm('確定要繼續輸出文字?');"
        Me.ImageButton1.OnClientClick = "if (confirm('你確定要繼續輸出文字?')==false) {return false;};"


        ' ************************************************************
        ' ********* 第二部分:購物車系統設計-結帳按鈕設計*************
        ' ************************************************************
        ' ===== 方法一 =====
        Me.Button2.Text = "第一種購物車系統-結帳按鈕"
        Me.Button2.OnClientClick = "return confirm('確定要繼續輸出文字?');"

        ' 當button2被按下後,跳出JavaScript確定視窗
        ' 按「是」,JavaScript回傳true,引發Button2_Click事件,按「否」則傳false取消按鈕動作
        ' 在Button2_Click事件我們把button給disable

        ' ===== 方法二 =====
        Me.Button3.Text = "第二種購物車系統-結帳按鈕"
        Me.Button3.UseSubmitBehavior = False
        ' 在按下按鈕後用JavaScript把在client端的按鈕disable
        Me.Button3.OnClientClick = "if (confirm('你確定要繼續輸出文字?')==false) {return false;} else {this.disabled = true; this.value = '資料處理中...請稍候';}"

    End Sub

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        Response.Write(Me.TextBox1.Text)
    End Sub

    Protected Sub LinkButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles LinkButton1.Click
        Response.Write(Me.TextBox1.Text)
    End Sub

    Protected Sub ImageButton1_Click(ByVal sender As Object, ByVal e As System.Web.UI.ImageClickEventArgs) Handles ImageButton1.Click
        Response.Write(Me.TextBox1.Text)
    End Sub

    Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click
        Me.Button2.Text = "資料處理中…請稍候"
        Me.Button2.Enabled = False
        Response.Write(Me.TextBox1.Text)
    End Sub

    Protected Sub Button3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button3.Click
        Response.Write(Me.TextBox1.Text)
    End Sub

第一部分:按鈕與OnClientClick介紹


畫面上我們放了三種「按鈕」控制項,在功能上與用法如出一轍,主要是在「畫面呈現」上不同。
如果你是要設計跨語言的平台,用文字呈現的方式,
可以在未來主題theme的地方統一編輯頁面上哪些文字的字串。
如果是用圖案ImageButton的話,可以在圖上又加中文字,會很漂亮…
但是若是服務多語言的網頁,按鈕是不是又得重製?(也許圖案就用一些常用、中外皆熟悉的符號來製作)

在 ASP.NET 中 Button 是唯一預設會 PostBack 的控制項,所以我們檢視一下下面三種按鈕的屬性視窗,並沒有像DropDownList 那種AutoPostBack的屬性,因為三種按鈕的設計按下去就要PostBack好像是天經地義的事情。

不過三個PostBack的方式不大一樣,我們檢視一下HTML的原始碼,如下圖
image

Button:就是當作一般網頁的「submit」,這個行之有年就不多著墨
LinkButton:是用連結的,而且連到一個名為「_doPostBack」的JavaScript來PostBack。
ImageButton:這個很奇妙,input type 是image…還可以PostBack…我還在努力的找應用範例。

先補充LinkButton上的JavaScript函式「_doPostBack」,是網頁render後自已會加上去的,我們從HTML原始檔可以找的到以下的函式:

<script type="text/javascript">
//<![CDATA[
var theForm = document.forms['form1'];
if (!theForm) {
    theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}
//]]>
</script>

為什麼要看PostBack機制,是和第二部分有關聯所以才簡單看一下(甚至以後介紹大型控制項GridView之類的,使用CommandField都會和這部分有關係)。ASP.NET就是要把三種功能一樣的物件寫不同的PostBack方法,我也莫可奈何
(後來翻了一下舊書,ASP.NET 1.x 的時候所有Button實現類都是為<input type=button onclick="__doPostBack(....)" /> ,是ASP.NET 2.0 以後才改的 )

再來是我們這三個身上都可以找到的屬性,OnClientClick,這個利用這個屬性,附帶簡單的JavaScript程式 (這邊寫在vb code上,也可以透過屬性欄位直接寫死在 HTML 碼上)

底下觀察OnClientClick 附加的JavaScript在render到網頁後會變成怎樣

◎vb code 上寫成
Button1.OnClientClick = "window.alert('警告視窗:即將輸出文字');"
LinkButton1.OnClientClick = "return confirm('確定要繼續輸出文字?');"

◎HTML 輸出後變成 (為方便閱讀,底下我把多餘的屬性和字符拿掉了)
<input type="submit" onclick="window.alert('警告視窗:即將輸出文字');"/>
<a onclick="return confirm('確定要繼續輸出文字?');"  href="javascript:__doPostBack(‘LinkButton1’,’')">

Button 或是 LinkButton 的OnClientClick =”一段JavaScript” ,render 到 HTML碼會變成 onclick = "一段JavaScript"

這邊大概提了一下JavaScript,還是無以下的案例做準備
如果您想看更多的JavaScript在ASP.NET的寫法的話,請看我的文章
ASP.NET上使用JavaScript的方法 (一)
ASP.NET上使用JavaScript的方法 (二)


第二部分「購物系統-結帳按鈕」

上面的按鈕和OnClientClick介紹是為這個主要案例做準備的
購物網中若是在結帳時重複按下二次按鈕,那麼有可能會交易二次…
所以我們設計當按鈕按下後,就取消按鈕功能的方式,讓按鈕呈現灰化的效果
如此避免使用者不小心按了二次
另外若是結帳網頁被重新整理、上一頁…設計的不好也容易造成交易bug
所以我們善用button的各種屬性來解決這個問題
(附註:這個程式在IE8 上瀏覽「好像」有些問題,還需要修改)

所以這個案例簡單的來說就是要設計:
按一下按鈕→回傳值→輸出結果→灰化按鈕

回傳值到Server和輸出結果到Brower,就是PostBack,所以第一個方法很好了解

◎第一個方法:
按一下按鈕→PostBack引發Button.Click常式下的程序
我們就在Button的Click事件下寫完「輸出值」然後「灰化按鈕」的程式就好了

不過這個方式在程式上總是有些邏輯不正確的問題
使用者按下按鈕後,就一定會灰化按鈕,完全不知道結果是什麼…
所以就算我在程式上的順序顛倒也沒關係,這是一種障眼法而已
(程式裡,我是先disable按鈕再輸出結果的…沒差)

◎第二個方法:
OnClientClick的屬性來調用Javascript從而實現Disable按鈕。
這原本是一個很簡單的應用範例,但也不知道怎麼會這麼難理解。

我在範例網頁上補充了實現這個功能的歷程,這邊就照著說好了,不然只看結果,很多人改寫的時候都會發生錯誤,原因就在於在

OnClientClick、UseSubmitBehavior合在一起用的時候會有衝突情形

以下描述歷程
◎第一次試驗(Button4):Button4 上設置OnClientClick=this.disabled=true
image

結果:TextBox的值會輸出,但是Button不會灰化
原因:因為Button是submit(就是會PostBack),按一下的時候,Broser端的按鈕是會灰化,但是頁面又重整了一次,Button又被初始化一次,畫面閃了一下太快了,所以感覺好像沒有用。
解決方法:不要讓Button PostBack (但值怎麼傳回Server)

這個就要用UseSubmitBehavior了。

◎第二次試驗(Button5):Button5上除了設置OnClientClick=this.disabled=true,再設置UseSubmitBehavior=false
image

設定UseSubmitBehavior的屬性,Brower端的HTML碼會變化:

當UseSubmitBehavior設為true時
<input type="submit"  onclick="this.disable=true;" id="Button4" />

當UseSubmitBehavior設為false時
<input type="button"   onclick="this.disable=true;__doPostBack(‘Button5’,'')" />

從以上可以觀察到,UseSubmitBehavior=true是預設值,當設為false時,input type變為button,就失去了PostBack的功能,不過微軟竟然在後面又加上onclick屬性,叫用了「__doPostBack」的JavaScript函式,這個函式就是我們前面說的LinkButton 的函式 (不過和LinkButton叫用的方法不一樣,一個是用連結,一個是用onclick),結果就是還是會PostBack,只是方式不同。

所以這個照著這個JavaScript函式順序,當browser端觸發onclick事件,就先把按鈕灰化,然後PostBack。所以畫面上還是可以看到閃一下。

這個案例的第二個方法,就是利用這種巧合,系統生的JavaScript排列在我們自已加的JavaScript之後。剛好完成一個事件序。我們不就是希望能防止二次提交,按鈕會灰化然後資料又可以把資料回傳到server嗎?在測試的時候,請在TextBox上輸入長一點的字串讓他回傳的時間拉長,你會發現按鈕會灰化到事件完成後又再次啟用(啟用的原因是PostBack重整頁面的關係)。

前面提到了三種按鈕的PostBack,以Button來說,html表單元素中是一個<input type=submit>的按鈕,顧名思義,只要你點擊這個就會將這個form提交給action指定的頁面,ASP.NET內部會檢索是否有Request.Form中有對應的name匹配頁面上的WebControl.UnquieID來確定誰提交了改頁面,由此引發註冊的服務器事件。

而LinkButton不是<input type=submit>,而是一個與JavaScript的連結,由JavaScript函式來處理PostBack。

這裡要特別注意的地方,就是JavaScript如果用"return confirm('確定要繼續輸出文字?');",會因為return導致confirm以後的JavaScript函式不運行 (JavaScript 中只要下了 return 指令,就會跳出函式回傳值;也就是說後面的 __doPostBack 永遠不會被執行到,所以無論使用者作什麼回應都無法 PostBack),所以如果要用return的話就要
將 OnClientClick 屬性值修改為 "if (confirm('確定執行嗎?')==false) {return false;}"。

1 則留言:

Related Posts Plugin for WordPress, Blogger...
// Dnow Function