CODE-滲透式jQuery.live()
12 |
手上有個需求,要掌握網頁裡所有連結被點擊的狀況。
對jQuery來說這是小菜一碟,利用$("a").live("click", function() { ... });就可在使用者點擊連結時加入自訂邏輯。不過,有挑戰性的部分在於網頁中可能穿插IFrame內嵌其他網頁,原本這個手腳只想動在MasterPage,就打算一口氣將網站所有網頁一網打盡,但$("a")的範圍只限於jQuery所在的window物件範圍,如果連內嵌網頁都要涵蓋,感覺上得在內嵌網頁裡也加上jQuery,也跑一次$("a").live("click", ...)。
"要手動修改一大堆網頁"這件事徹底挑動了【懶人】最敏感的神經,逼我又動起歪腦筋--是否可以找出主網頁裡所有的內嵌網頁,再滲透進去一一加掛$("a").live("click", ...)? 理論上只要不違背Same Origin Policy,主網頁可以透過contentWindow屬性存取到內嵌網頁的DOM,技術上是可行的。
於是,我試出了以下的Prototype,在此野人現曝一番,歡迎大家回饋與指教。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="../js/jquery-1.3.2.js" type="text/javascript"></script>
<script type="text/javascript">
//用load而不是ready,會等所有IFrame都載入後才觸發
$(window).load(function () {
//將設定邏輯寫成function,稍後可再度利用
var hookProc = function () {
$("a").live("click", function () {
alert("clicked!");
});
};
hookProc();
//針對所有IFrame進行操作
$("iframe").each(function () {
//透過IFrame.contentWindow存取其中的網頁
//cross-domain時會失敗,故加上try
var win, doc;
try {
win = this.contentWindow;
doc = win.document;
} catch(err) {}
//無法取得IFrame的window或document就放棄
if (!win || !doc) return;
//檢查IFrame中是否已載入jQuery?
if (!win.jQuery) {
//動態載入
var jqInject = doc.createElement("script");
jqInject.src = "../js/jquery-1.3.2.js";
jqInject.type = "text/javascript";
doc.getElementsByTagName("head")[0].appendChild(jqInject);
}
//等待jQuery載入
function waitjQueryLoaded() {
if (typeof win.jQuery == "undefined") setTimeout(waitjQueryLoaded, 100);
else {
//將前述的邏輯對IFrame的window也做一次
win.eval("(" + hookProc.toString() + ")();");
}
}
waitjQueryLoaded();
});
});
</script>
</head>
<body>
<a href="#">Link in Parent</a>
<br />
<iframe src="Child.htm" id="frm1"></iframe><br />
<iframe src="http://www.google.com.tw"></iframe><br />
<iframe src="Child.htm" id="Iframe1"></iframe><br />
</body>
</html>
PS: 以上的做法我想到兩個缺點(但對我的應用來說不是大問題) 1) $(window).load()可以確保所有IFrame都載入後才開始動作,相對地掛好事件前的空窗期會變長 2) 只支援單層結構,無法涵蓋IFrame裡又有IFrame的狀況。
Comments
# by jaceju
太強了~ (拜)
# by Ammon
我還沒有實際測試過,用 jQuery(expression, [context]) 也許可以不用在 iframe 中動態載入 jQuery? 另外其實偵測的程式我是喜歡簡化成 (function(){ if (!window.jQuery) return setTimeout(arguments.callee, 100); //Do Somthing... } )();
# by Jeffrey
to Ammon, 一開始我也想過jQuery("a", iframeObj.contentWindow.document)的做法,但測試發現這樣還是在查主網頁的範圍,造成click事件重複bind了。簡潔版的偵測寫法實在是太酷了,請受小弟一拜~~~
# by Ammon
測試之後的確是會重複 bind, 查看 jQuery source code 發現是因為 2978行使用 jQuery(document).bind 去處理,若是直接用 click 就不會有問題,但是這樣動態產生的 a 就沒辦法,只好抄襲jQuery來做修改 $.fn._live = function(type, fn) { function liveConvert(type, selector){ return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join("."); } var proxy = jQuery.event.proxy( fn ); proxy.guid += this.selector + type; jQuery(this.context).bind( liveConvert(type, this.selector), this.selector, proxy ); }; (function(win) { var fn = arguments.callee; $(win).load(function(){ var doc = win.document; $('a',doc)._live('click', function(){ return !!alert(this.href); }); // 不用 live 的可以忽略 $.fn._live及上方三行, 直接使用 click // $('a',doc).click(function(){ // return !!alert(this.href); // }); $('iframe',doc).each(function(){ try{ fn(this.contentWindow); }catch(e){} }); }); })(window);
# by Jeffrey
to Ammon, 好神!! 測試結果,使用_live()就不用在子網頁中加掛jQuery了! 原本預期$(win).load的加入可以一併解決IFrame裡有IFrame的遞迴,但實測發現在IE以外的瀏覽器不管用,我推測是$(contentWindow).load沒被觸發。
# by Ark
$("iframe").each( .... 1) $(window).load()可以確保所有IFrame都載入後才開始動作,相對地掛好事件前的空窗期會變長 ==> $(function() { $('iframe')._load(function() { alert('this_iframe_finish_load'); }); }); 誰先好誰先來
# by Ammon
測試結果對 iframe 使用 document ready, 主視窗用 window onload, 就可以解決 iframe中有iframe的問題。當然,缺點還是必須等到所有頁面載入完成才會有效用 (function(win) { var fn = arguments.callee; var w = win == window; $(w ? win : win.document)[w ? 'load' : 'ready'](function() { var doc = win.document; $('a', doc)._live('click', function() { return !!alert(this.href); }); $('iframe', doc).each(function() { try { fn(this.contentWindow); } catch (e) { } }); }); })(window); 本來想用下方更優雅的方式寫,無奈 iframe 的 ready 裡的this指的並不是iframe的document本身,而是最上層的document :( 所以注意下方的 code 會造成無窮迴圈 $(window).load(function() { var fn = arguments.callee; var doc = this.document||document; $('a', doc)._live('click', function() { return !!alert(this.href); }); $('iframe', doc).each(function() { try { $(this.contentWindow.document).ready(fn); } catch (e) { } }); });
# by Ark
那乾脆把jquery 匿名載入改成具名的 開啟jquery-1.3.2.min.js 最前方(function() { var l = this, 改成為var initjq = function(mywin) {var l = mywin, 最後面typeof K === "string" ? K : K + "px") } }) })(); 改成為typeof K === "string" ? K : K + "px") } }) }initjq(window); 改名另存initjq-1.3.2.min.js <script src="js/initjq-1.3.2.min.js" type="text/javascript"></script> 載入到head var hookProc = function(i$) { i$("a").live("click", function() { alert("clicked!"); }); }; var liveiframe = function() { $('iframe').each(function() { var execlive = function(win) { win.hookProc = parent.hookProc; if (!win.$) { if ($.browser.mozilla) { win.execScript = win.eval; }//這裡搞不清楚為何IE8 eval 不給過~改成execScript就又可以 win.execScript("var initjq=" + parent.initjq.toString()); win.eval("initjq(window);"); } hookProc(win.$); if (win.$('iframe').length > 0) { win.eval("var liveiframe=" + liveiframe.toString() + " ;liveiframe();"); } } var win; try { win = this.contentWindow; setTimeout(function() { execlive(win); }, 15);//等待doc readyState complete; } catch (err) { $(this)._load(function() { win = this.contentWindow; execlive(win); }); } }); } $(function() { liveiframe(); }); 以上IE FF 3層測過
# by Billy
jQuery 剛剛出了v1.4,要去嚐鮮。
# by Ammon
1.4 可以直接用 live, 不需要自己搞一個
# by carl
小弟使用Ammon方法測試的結果, 在iframe.html可以正常alert出訊息, 但在http://www.google.com.tw卻無法正常alert出訊息, 不知是那兒出錯了, 請大大幫忙指教一下,謝謝 程式碼如下: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script src="jquery-1.3.2.js" type="text/javascript"></script> <script type="text/javascript"> $.fn._live = function(type, fn) { function liveConvert(type, selector){ return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join("."); } var proxy = jQuery.event.proxy( fn ); proxy.guid += this.selector + type; jQuery(this.context).bind( liveConvert(type, this.selector), this.selector, proxy ); }; (function(win) { var fn = arguments.callee; var w = win == window; $(w ? win : win.document)[w ? 'load' : 'ready'](function() { var doc = win.document; $('a', doc)._live('click', function() { return !!alert(this.href); }); $('iframe', doc).each(function() { try { fn(this.contentWindow); } catch (e) { } }); }); })(window); </script> </head> <body> <a href="#">Link in Parent</a> <br /> <iframe src="iframe.html" id="frm1"></iframe><br /> <iframe src="http://www.google.com.tw" id="frm2"></iframe><br /> <iframe src="iframe.html" id="Iframe1"></iframe><br /> </body> </html>
# by Jeffrey
to carl, 這裡討論去操縱iframe內DOM的做法,不能違背Same Origin Policy的限制。 簡單來說,iframe裡嵌入的若是來自其他網站的網頁(例如: 你所測試的www.google.com.tw),Browser會禁止Javascript程式去存取或更動其中的內容,以防範形成資安漏洞。這是各家瀏覽器都會遵循的規範,在應用時要留意。