14

IE 都更筆記 - 汰換 showModalDialog()

 2 years ago
source link: https://blog.darkthread.net/blog/replace-showmodaldialog/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
汰換 showModalDialog()-黑暗執行緒

這個系列是「將 IE-Only 網站翻修到 Chrome/Edge 相容」過程的瑣碎筆記,有參與古蹟維護的朋友請進。

今天說說網頁裡若出現只剩 IE 支援的 showModalDialog() 要怎麼修改?從 Chrome 43 跟 Firefox 56 之後,showModalDialog API 已被主流瀏覽器移除,但它有些獨家特性,無法輕易用 window.open() 另開新視窗取代,包含:

  1. 會阻擋呼叫端原有網頁操作,直到使用者關閉 showModalDialog 開啟網頁
  2. 呼叫程式碼需等待 showModalDialog() 視窗關閉才會繼續往下執行

前者的替代方案不少,主流網頁元件程式庫幾乎都支援強制對話框,像是 jQueryUI dialogKendo UI DialogBootstrap 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();
}

實測成功!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK