CODE-滲透式jQuery.live()

手上有個需求,要掌握網頁裡所有連結被點擊的狀況。

對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的狀況。

歡迎推文分享:
Published 04 January 2010 08:40 PM 由 Jeffrey
Filed under: , ,
Views: 27,871



意見

# jaceju said on 04 January, 2010 04:30 AM

太強了~ (拜)

# Ammon said on 04 January, 2010 09:08 AM

我還沒有實際測試過,用 jQuery(expression, [context]) 也許可以不用在 iframe 中動態載入 jQuery?

另外其實偵測的程式我是喜歡簡化成

(function(){

   if (!window.jQuery) return setTimeout(arguments.callee, 100);

   //Do Somthing...

}

)();

# Jeffrey said on 04 January, 2010 06:57 PM

to Ammon, 一開始我也想過jQuery("a", iframeObj.contentWindow.document)的做法,但測試發現這樣還是在查主網頁的範圍,造成click事件重複bind了。簡潔版的偵測寫法實在是太酷了,請受小弟一拜~~~

# Ammon said on 04 January, 2010 07:52 PM

測試之後的確是會重複 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);

# Jeffrey said on 05 January, 2010 02:32 AM

to Ammon, 好神!! 測試結果,使用_live()就不用在子網頁中加掛jQuery了! 原本預期$(win).load的加入可以一併解決IFrame裡有IFrame的遞迴,但實測發現在IE以外的瀏覽器不管用,我推測是$(contentWindow).load沒被觸發。

# Ark said on 06 January, 2010 09:47 AM

$("iframe").each( ....

1) $(window).load()可以確保所有IFrame都載入後才開始動作,相對地掛好事件前的空窗期會變長

==>

         $(function() {

             $('iframe')._load(function() {

                 alert('this_iframe_finish_load');

             });

         });

誰先好誰先來

# Ammon said on 08 January, 2010 12:44 PM

測試結果對 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) { }

          });

      });

# Ark said on 09 January, 2010 04:29 PM

那乾脆把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層測過

# Billy said on 14 January, 2010 08:30 PM

jQuery 剛剛出了v1.4,要去嚐鮮。

# Ammon said on 18 January, 2010 08:51 AM

1.4 可以直接用 live, 不需要自己搞一個

# carl said on 24 January, 2010 07:11 PM

小弟使用Ammon方法測試的結果,

在iframe.html可以正常alert出訊息,

但在www.google.com.tw卻無法正常alert出訊息

不知是那兒出錯了,

請大大幫忙指教一下,謝謝

程式碼如下:

<html xmlns="www.w3.org/.../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>

# Jeffrey said on 24 January, 2010 11:25 PM

to carl, 這裡討論去操縱iframe內DOM的做法,不能違背Same Origin Policy的限制。

簡單來說,iframe裡嵌入的若是來自其他網站的網頁(例如: 你所測試的www.google.com.tw),Browser會禁止Javascript程式去存取或更動其中的內容,以防範形成資安漏洞。這是各家瀏覽器都會遵循的規範,在應用時要留意。

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<January 2010>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication