jquery.js の load 関数でロードしたスクリプトを一部ブラウザで実行できない件
IE6 と mac safari(windows版ではなぜか大丈夫) 、Chrome で、 ajaxの中でロードしたスクリプトを呼び出せない。
具体的には、以下のような場合、 loadした abc.htmlのボタンを押すとhello()を呼び出せるかという話。
簡単な概要
---main.html--------- コンテンツabc.htmlをロードする. $('#nanka').load('abc.html');
---abc.html---- 持ってきたコンテンツのボタンを押すと javascript が実行されますか? <input type="button" onclick="hello()"> function hello() { alert("これが実行されるか?"); }
デモサイト!!
http://rtilabs.net/files/2008_09_16/jquery_load_test_main.html
ソースで語ろうぜ
prototype.js はこの問題をうまく回避している。
jquery.js の load を利用したときに発生します。
jquery.js の load 関数の中を見てみると、こうなっています。
// Request the remote document complete: function(res, status){ // If successful, inject the HTML into all the matched elements if ( status == "success" || status == "notmodified" ) // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("<div/>") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result res.responseText ); self.each( callback, [res.responseText, status, res] ); }
いろいろとごちゃごちゃしているので使わないところを削除して簡略化すると、
complete: function(res, status){ // If successful, inject the HTML into all the matched elements if ( status == "success" || status == "notmodified" ) // See if a selector was specified self.html( res.responseText ); self.each( callback, [res.responseText, status, res] ); }
こんな感じになります。
つまり、受け取ったコンテンツを self.html ようするに、 innerHTML に渡しているということです。
問題はこのときにjavascript関数が含まれていたときにどう動作するかということです。
上記の IE6 、 mac safari 、 Chrome は、関数定義を無視します。
正式な仕様がどうなっているのかは謎ですが、頭が固い専門家気取りの moziila(firefox)で動作するので正しいんでしょうwww
IE7でも動作するしね、IE6のバグフィックス?
んで、こんな困った動作をしてくれるブラウザは、 IE6 と mac safari だけだと思ったんですけど、 Chrome でも同じ問題が起きていたので、忘れないうちに書いときます。
解決法
まー prototype.js だと動くんだから、 prototype.js からソースを持ってきて適当にマージすればok。
ライセンス的にも両方ともMITライセンスを選択できるんだから、主点を明記して流用しますた、さんくすと書いておけば大丈夫でしょう。多分。
//スクリプトの削除 prototype.js のソースより(一部編集) function _stripScripts(scriptsString) { var ScriptFragment = '<script[^>]*>([\\S\\s]*?)<\/script>'; return scriptsString.replace(new RegExp(ScriptFragment, 'img'), ''); } //スクリプトだけを抽出し、配列に格納 prototype.jsのソースより(一部編集) function _extractScripts(scriptsString) { var ScriptFragment = '<script[^>]*>([\\S\\s]*?)<\/script>'; var matchAll = new RegExp(ScriptFragment, 'img'); var matchOne = new RegExp(ScriptFragment, 'im'); return jQuery.map( scriptsString.match(matchAll) || [] , function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; } ); } //スクリプトを実行する. prototype.jsのソースより(一部編集) function _evalScripts(scriptsString) { jQuery.map( _extractScripts(scriptsString) , function(script) { jQuery.globalEval(script); return true; } ); } //IE7? function isIE7() { return ( typeof document.documentElement.style.msInterpolationMode != 'undefined' ); } //MAC function isMac() { var agent = navigator.userAgent.toLowerCase(); return agent.indexOf("macintosh") != -1 || agent.indexOf("mac os") != -1; } //Chrome function isChrome() { var agent = navigator.userAgent.toLowerCase(); return agent.indexOf("chrome") != -1 ; } //めんどうだから、 selector の部分とはばっさり削除! まさに外道 jquery.jsより(一部編集) jQuery.fn.extend({ super_load: function( url, params, callback ) { callback = callback || function(){}; // Default to a GET request var type = "GET"; // If the second parameter was provided if ( params ) // If it's a function if ( jQuery.isFunction( params ) ) { // We assume that it's the callback callback = params; params = null; // Otherwise, build a param string } else { params = jQuery.param( params ); type = "POST"; } var self = this; // Request the remote document jQuery.ajax({ url: url, type: type, dataType: "html", data: params, complete: function(res, status){ // If successful, inject the HTML into all the matched elements if ( status == "success" || status == "notmodified" ) { //IE7未満は javascript を書き込もうとするとエラーになるので、 //prototype.jsのやり方で、javascriptだけを実行する実行してあげる必要がある. //Mac Safari も同様のバグがある。 windows safariは直っているのになんで? //Chrome! お前もか! if ( (jQuery.browser.msie && !isIE7()) || (jQuery.browser.safari && isMac() ) || ( isChrome() ) ) { //HTMLだけを書き込む self.html(_stripScripts(res.responseText)); //javascriptを実行する _evalScripts(res.responseText); } else { //IE7以上または、FFやOperaはjQueryのいつものやり方。 self.html(res.responseText); } } //エラーのときもコールバックしようぜ! コールバック self.each( callback, [res.responseText, status, res] ); } }); return this; } });
こんな感じにして、後は、 load で呼び出していたところを super_load に変えれば問題なく動作するよ。
あと、結局 innerHTMLで持ってきたjavascript の関数を実行できるべきなのかできないべきなのか、仕様を知っている専門家と専門家気取りのエロイ人は教えてください。