IE 都更筆記 - 汰換 showModalDialog()
7 |
這個系列是「將 IE-Only 網站翻修到 Chrome/Edge 相容」過程的瑣碎筆記,有參與古蹟維護的朋友請進。
今天說說網頁裡若出現只剩 IE 支援的 showModalDialog() 要怎麼修改?從 Chrome 43 跟 Firefox 56 之後,showModalDialog API 已被主流瀏覽器移除,但它有些獨家特性,無法輕易用 window.open() 另開新視窗取代,包含:
- 會阻擋呼叫端原有網頁操作,直到使用者關閉 showModalDialog 開啟網頁
- 呼叫程式碼需等待 showModalDialog() 視窗關閉才會繼續往下執行
前者的替代方案不少,主流網頁元件程式庫幾乎都支援強制對話框,像是 jQueryUI dialog、Kendo UI Dialog、Bootstrap Modal,更不用說三大前端框架 Vue.js、Angular、React.js (VAR) 也都有自己的解決方案,但如果你已上手 VAR,應該不需要參考這篇筆記就知如何動手。這篇採行的策略是 - 避免砍掉重練,以最小幅度修改 IE-Only 網頁使其相容 Chrome/Edge 等主流瀏覽器。所以先不考慮 VAR,而 jQueryUI、Kendo UI、Bootstrap 三者相比,Bootstrap Dialog 有點陽春;Kendo UI Dialog 包含在免費版 Kendo UI Core的範圍,但程式庫較龐大,如果網頁還有用到其他 Kendo UI 元件時才是我的首選,這篇會用 jQuery UI Dialog 示範,但要抽換元件不是難事。
至於阻擋程式直到對話視窗結束再執行,這是瀏覽器特權,想用 JavaScript 實現基本上無解,建議比照 jQuery.ajax Promise 做法,改寫成 .done(function() { ...後續執行程式碼... })。
我寫了一個簡單 showModalDialog 範例。
index.html
<!DOCTYPE html>
<html>
<body>
<input type="text" id="name" value="Jeffrey" size="16" />
<input type="button" onclick="testModalDialog()" value="TEST" />
<script>
function testModalDialog() {
var result = window.showModalDialog(
'select.html', //url
document.all('name').value, //dialogArguments
'dialogWidth=300px;dialogHeight=80px');
alert('Team=' + result);
}
</script>
</body>
</html>
select.html
<!DOCTYPE html>
<html>
<body style="padding:12px">
<span id='name'></span>, please select team:
<select id='team'>
<option selected>Blue</option>
<option>Red</option>
</select>
<input type="button" onclick="sendResult()" value="OK" />
<script>
document.getElementById('name').innerText = window.dialogArguments;
function sendResult() {
var team = document.getElementById('team');
window.returnValue = team.options[team.selectedIndex].innerText;
window.close();
}
</script>
</body>
</html>
執行結果如下,index.html 以 showModalDialog() 帶出,使用者姓名以 window.dialogArguments 傳入,在 select.html 按 OK 會設定 window.returnValue 並 window.close() 傳回結果。index.html 由 showModalDialog() 回傳值取得使用者選取結果,若直接關閉視窗,showModalDialog() 則傳回 undefined。
(原程式有個低級錯誤沒正確抓到下拉選取值,導致選 Red 傳 Blue,請大家當作沒看到... orz)
評估之後,我打算寫一個 jQuery.showModalDialog 取代 window.showModalDialog,傳入參數包含 url、原本的 dialogArguments 參數、視窗標題、視窗寬度、視窗高度,背後呼叫 jQuery UI dialog 顯示(若要抽換 Dialog 元件,改這裡就好)。程式寫成 jquery.showModalDialog.js 方便引用,由於網站原本不一定有引用 jQuery UI,我加入主動偵測並自動從 CDN 載入 jquery-ui.css 及 jquery-ui.min.js 的貼心小功能。(企業內部應用則建議將 jQuery UI css/js 放在同網站,Intranet 比 CDN 下載速度更快)
jQuery.showModalDialog 邏輯不複雜,動態加入 iframe 包成 jQuery UI Dialog,用 $.dialogArguments、$.dialogReturnValue 與對話框溝通,開啟及縮放事件時調整 iframe 大小,關閉事件時從 DOM 清除 Dialog 元件並呼叫 Deferred.resolve() 觸發 Promise .done() 事件:
if (window.jQuery && !jQuery.showModalDialog) {
let $ = jQuery;
// Load jQuery UI dynamically if not ready
if (!$.fn.dialog) {
$('<link />').attr({
type: 'text/css',
rel: 'stylesheet',
href: 'https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css'
}).appendTo('head');
$.getScript('https://code.jquery.com/ui/1.12.1/jquery-ui.min.js');
}
$.dialogArguments = undefined;
$.dialogReturnValue = undefined;
$.dialogInstance = null;
$.showModalDialog = function (url, diagArgs, title, width, height) {
$.dialogReturnValue = undefined;
$.dialogArguments = diagArgs;
let dfd = $.Deferred();
$.dialogInstance = $('<iframe></iframe>')
.attr('src', url)
.css({ width: width, height: height, border: 'none', padding: 0 })
.dialog({
title: title || '',
width: width,
height: height + 50,
modal: true,
resize: function(e, ui) { $(e.target).width(ui.size.width); },
open: function(e, ui) { $(e.target).width(width); },
close: function () {
$(this).dialog('destroy').remove();
setTimeout(function() { dfd.resolve(); }, 0); //after closed
}
});
return dfd.promise();
}
$.closeModalDialog = function() {
$.dialogInstance && $.dialogInstance.dialog('close');
}
}
index.html 進行小幅改寫,引用 jQuery、jquery.showModalDialog.js,改呼叫 $.showModalDialog(),接受回應結果寫在 done() 函式,由 $.dialogReturnValue 取值:
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="jquery.showModalDialog.js"></script>
</head>
<body>
<input type="text" id="name" value="Jeffrey" size="16" />
<input type="button" onclick="testModalDialog()" value="TEST" />
<script>
function testModalDialog() {
$.showModalDialog(
'select.html', //url
document.getElementById('name').value, //dialogArguments
'Select Team', //title
350, //width
80 //height
).done(function() {
alert('Team=' + $.dialogReturnValue);
});
}
</script>
</body>
</html>
select.html 的修改更單純,window.dialogArguments 改 parent.jQuery.dialogArguments,window.returnValue 改 parent.jQuery.dialogReturnValue,window.close() 改 parent.jQuery.closeModalDialog():
//document.getElementById('name').innerText = window.dialogArguments;
document.getElementById('name').innerText = parent.jQuery.dialogArguments;
function sendResult() {
var team = document.getElementById('team');
//window.returnValue = team.options[team.selectedIndex].innerText;
parent.jQuery.dialogReturnValue = team.options[team.selectedIndex].innerText;
//window.close();
parent.jQuery.closeModalDialog();
}
實測成功!
Exmaple of replacing showModalDialog() of IE-only page for Chrom/Edge.
Comments
# by Alex
選 red 卻顯示 blue?
# by 小海
黑哥最高~
# by Jeffrey
to Alex, 啊啊啊(抱頭),好低級的錯誤,已修正程式,謝謝提醒。
# by Alex hsu
黑大,這種情況是在單一視窗下的頁面可以正常回饋訊息 如果是一個頁面有兩個iframe,其中一個iframe的頁面用此功能 會彈到最上層嗎?以及return value會回到該Iframe嗎?
# by Jeffrey
to Alex hsu, 在 IFrame 呼叫 $.showMadalDialog(),其顯示範圍最大只到該 IFrame 的大小,你說的情境我傾向從最上層視窗呼叫,用 top.jQuery.showModalDialog() 開啟,傳回值改透過 top.jQuery.dialogReturnValue 傳遞。
# by joe
請問黑大,我改用伺服器控制項執行 ScriptManager.RegisterStartupScript(Page, Page.GetType(), "onclick", "testModalDialog();", True) 會發生 JavaScript 執行階段錯誤: 物件沒有支援這個屬性或方法 'dialog' ~ 是否寫法錯誤~~謝謝指教
# by Jeffrey
to joe, 感覺是動態載入 jQuery UI 部分不成功,建議在網頁載入 https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css 跟 https://code.jquery.com/ui/1.12.1/jquery-ui.min.js 看看是否能排除問題。 另外,推薦更方便的升級寫法:https://blog.darkthread.net/blog/showmodaldialog-polyfill/