昨天剛在公司解完案例,今天又在日常生活遇到實例,老天爺這暗示明顯無比,趕緊來篇筆記,以防出門被雷劈!
最近迷上蝦皮拍賣。能跟 LINE 一樣跟賣家溝通超方便,尤其問完問題馬上接到賣家的實體照片真令人感動,不用出門與人面對面又保有臨櫃交談的即時性,真是阿宅的救星。遇到一個賣家很妙,凌晨四點多發訊息通知我補寄商品今天會到(不知對方有沒有早起的拎杯秒回「謝謝」嚇到? XD)。
在中華郵政網站輸入包裹號碼,按下「運輸資料」查詢鈕… 登楞!
JavaScript 試圖彈出視窗被 Chrome 瀏覽器封鎖了!
簡單來說,這是踩中「瀏覽器會封鎖不是使用者點擊直接觸發的 window.open」地雷。(詳細說明可參考: showModalDialog與IE快顯封鎖)
無聊追進郵局包裹查詢程式幫忙 Debug 順便練功。網頁是用 Angular 寫的:
<a class="css_btn_class_gray ng-scope" ng-click="showInfo('Base64編碼');">運輸資料</a>
按鈕時會觸發 API 查詢
$scope.showInfo = function(b64Str){ $scope.queryAPI(b64Str); };
$scope.queryAPI 呼叫 AJAX 查詢,接收結果後呼叫 setTimeout(function() { $scope.createTRSView(); }, 500); 開啟結果視窗:
$scope.queryAPI = function(b64Str){
$scope.sendRecv("EB500100", "queryAPI", API_Url, vo,
function(tota, isError) {
//查詢處理(省略)
//延遲0.5秒
setTimeout(function() {
$scope.createTRSView();
}, 500);
}
});
};
$scope.createTRSView = function(){
var template = "<html><head><title>郵袋籃車查詢</title>…略…";
var mw = window.open("", "_blank");
$scope.mw = mw;
if(mw) {
mw.document.write(template.format($("#trs_template").html()));
} else {
alert("無法開啟查詢視窗");
}
};
由以上邏輯可發現,window.open 與 onclick 事件間隔了兩層非同步,第一層是呼叫 API 回傳結果(背後使用的是 $http Promise 機制),之後透過 setTimeout 又是另一層非同步,window.open 動作怎麼都不可能算成 onclick 的直接觸發行為,註定要被瀏覽器攔截!
遇到這種情境要怎麼處理呢?我想到幾種做法:
- 改為直接操作 XmlHttpRequest 以同步方式呼叫(網頁卡住等待伺服器傳回結果再繼續執行),收到結果後直接 window.open,避開 XHR 非同步執行、$http Promise 與 setTimeout 三層非同步,以符合 window.open 被包在 onclick 事件中的條件。 不過,這做法與 AJAX 精神背道而馳,我歸類為餿主意。
- 在 onclick 事件就 window.open 將結果視窗先開好(得先顯示「查詢中…」之類的動畫,不然會很乾),待 AJAX 呼叫完成後再將結果填入,但無法視 AJAX 呼叫結果決定要不要開視窗是一大缺點。
- 避用 window.open(),改以 IFrame 內嵌或直接在 DOM 建立 div 放入結果。
我個人偏好第 3 種做法,練功完畢。