專案規格有一條機車要求: 對於刪除或覆寫資料前的確認程序,希望以自訂風格的確認對話框取代簡陋的window.confirm()。

舉例來說,按鈕後原本要透過window.confirm()請使用者確認後再執行,現在要改用自訂HTML元素呈現確認文字、按鈕進行確認,就如以下改用Kendo UI Window實作確認對話框的效果:

用HTML打造自訂對話框並在適當時機顯示是小事一椿,較有挑戰性的部分是原本window.confirm()執行為同步式,程式碼會停住等使用者回應再繼續往下走。想依confirm()結果決定不同動作只需寫成:

if (window.confirm("確定嗎?")) {
    //…使用者回答【是】時的動作
} else {
    //…使用者回答【否】時的動作
}

但要在JavaScript做到"卡住流程直到特定條件再繼續"並非易事,曾看過一種做法是跑while無窮迴圈並於特定時機跳出,但因JavaScript不像C#有Thread.Sleep可用,無窮迴圈會莫名吃光CPU很不環保。也有人想出藉由同步式XHR到Server端存取虛設延遲網頁模擬Thread.Sleep的招術,但靠著無謂網路傳輸來節省CPU,還徒增Server負擔,想來也不怎麼高明。最後,還是決定用jQuery的Deferred來處理非同步。

原理是先宣告Deferred物件,當呼叫確認對話框時,傳回Deferred.promise()給呼叫端,呼叫端可透過.done()指定使用者按【是】時要執行的動作、在.fail()指定使用者按【否】時要執行的動作。如此,再依使用者按鈕決定呼叫Deferred.resolve()或Deferred.reject(),就能控制該觸發done()還是fail(),達到依操作結果決定不同執行動作的效果。先不管華麗的UI元素,以下是示範用Deferred依按鈕結果決定動作的簡單範例: 線上展示

<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js"></script>
<meta charset=utf-8 />
<title>使用Deferred建立自訂確認對話框</title>
  <script>
    function myConfirm(msg) {
      var df = $.Deferred(); //建立Deferred物件
      var $div = $("<div id='C'></div>");
      //由樣版複製建立一次性div元素
      $div.html($(".dialog").html())
      //加上按鈕事件
      .on("click", "input", function() {
        $div.remove(); //將對話框移除
        if (this.value == "Yes") 
          df.resolve(); //使用者按下Yes
        else 
          df.reject(); //使用者按下No
      })
      .find(".m").text(msg); //設定顯示訊息
      //將div加入網頁
      $div.appendTo("body");
      return df.promise();
    }
    
    $(function() {
      $("#btnTest").click(function() {
        myConfirm("Are you sure?")
        .done(function() { //按下Yes時
          alert("You are sure");
        })
        .fail(function() { //按下No時
          alert("You are not sure");
        });
      });
    });
  </script>
</head>
<body>
  <input type='button' value='Test' id='btnTest' />
  <div class='dialog' style='display:none'>
    <div style='border: 1px solid blue; padding: 12px;'>
    <span class='m'></span>
    <input type='button' value='Yes' />
    <input type='button' value='No' />
    </div>
  </div>  
</body>
</html>

其操作結果如下:

最後,運用同樣原理再招喚Kendo UI的Window套件上場,就能實現一開始展示的華麗版確認對話框囉~ 完整程式碼如下: 線上展示

<!DOCTYPE html>
<html>
<head>
  <title>使用Deferred建立自訂確認對話框(Kendo UI版)</title>
<link href="http://cdn.kendostatic.com/2013.2.716/styles/kendo.common.min.css" 
 rel="stylesheet" type="text/css" />
<link href="http://cdn.kendostatic.com/2013.2.716/styles/kendo.default.min.css" 
 rel="stylesheet" type="text/css" />
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js"></script>
<script src="http://cdn.kendostatic.com/2013.2.716/js/kendo.web.min.js"></script>
<meta charset=utf-8 />
  
  <style>
    .cnfrm-msg { color: red; padding: 12px; font-size: 12pt; }
    .cnfrm-yes,.cnfrm-no { }
  </style>
  <script>
    //參考: http://jsfiddle.net/gyoshev/HRcKK/
    (function($) {
      var h = [];
      h.push("<div class='cnfrm-block'>");
      h.push("<div class='cnfrm-msg'></div>");
      h.push("<input type='button' class='cnfrm-yes' />");
      h.push("<input type='button' class='cnfrm-no' />");
      h.push("</div>");
      var html = h.join("");
      
      $.kendoConfirm = function(title, msg, yesText, noText) {
        var $div = $(html);
        $div.find(".cnfrm-msg").text(msg);
        $div.find(".cnfrm-yes").val(yesText || "Yes");
        $div.find(".cnfrm-no").val(noText || "No");
        var win = $div.kendoWindow({
          title: title || "Confirmation",
          resizable: false,
          modal: true,
          deactivate: function() {
            this.destroy(); //remove itself after close
          }
        }).data("kendoWindow");
        win.center().open();
        var dfd = $.Deferred();
        $div.find(":button").click(function() {
          win.close();
          if (this.className == "cnfrm-yes")
            dfd.resolve();
          else
            dfd.reject();          
        });
        return dfd.promise();
      };
    })(jQuery);
    
    $(function() {
      $("#btnTest").click(function() {
      var dfd = 
        $.kendoConfirm(
          "Please confirm...",
          "Are you sure to delete it?",
          "Yeeees", "No no no");
        dfd.done(function() { //按下Yes時
          alert("You are sure");
        })
        .fail(function() { //按下No時
          alert("You are not sure");
        });
      });
    });
  </script>
</head>
<body>
  <input type='button' value='Test' id='btnTest' />
  <div class='dialog' style='display:none'>
    <div style='border: 1px solid blue; padding: 12px;'>
    <span class='m'></span>
    <input type='button' value='Yes' />
    <input type='button' value='No' />
    </div>
  </div>  
</body>
</html>

Comments

# by chihwen

黑大您好,也可考慮採用 jQuery BlockUI 套件看看。 http://www.malsup.com/jquery/block/

# by Jeffrey

to chihwen, 呵! blockUI已是我專案的固定班底,本次套件海選未能出線,是因為專案已使用Kendo UI,而kendoWindow提供現成的Window Title、控制位置的API,更符合需求。 但謝謝你的推薦,我會轉告blockUI它又多了一名粉絲(我也是blockUI後援會成員)。

# by 火影

請問黑大,觀察範例中,有個變數使用 var df,有的卻又 var $div ;不知這樣撰寫是為了甚麼?還是有其他不為人知的意義?

# by Jeffrey

to 火影,這算是我個人的撰寫慣例,用來存放jQuery物件的變數,我會加上"$"字首,方便後續快速分辦它是jQuery物件,就能大膽地套用.find(), .data()等方法。

# by Smalle

第一个实例拿下来运行,怎么不是同步的啊

# by Jeffrey

to Smalle, 請問不同步指的是什麼?能放上JSBin或JSFiddle給個演示嗎?

# by JerryH

有法子覆寫window.confirm 嗎?讓confirm 直接用kendoConfirm?

# by Jeffrey

to JerryH, 覆寫 confirm 很容易,但 kendoConfirm 是非同步的,後續程式碼要放在 done()/faile() 裡,無法 100% 模擬 confirm() 卡住程式等回應的行為。即使覆寫,也沒法做到程式完全不改。

Post a comment