********************************************************************
* 著作權聲明
*
* 本文以Creative Commons的發布,請嚴格遵循該授權協議。
* 本文首發於部落格園, 此聲明為本文章中不可或缺的一部分。
* 作者網名: 浪子
* 作者EMAIL:dayichen (at)163.com
* 作者BLOG: Http://Www.Cnblogs.Com/Walkingboy
*
********************************************************************
[CallbackPlus]Ajax中動態執行返回到innerHTML中的js
-Written by 浪子@cnblogs.com (07-08-20)
摘要:
最近在為CallbackPlus增加一個類似Asp.net ajax的UpdatePanel和CA的Callback一樣的容器無刷更新控制項,利用innerHtml來更新最終產生html代碼,但是其中的js指令檔的更新卻是相當的麻煩。對於asp.net ajax中的scriptmanager的調用方式比較不滿意,所以自己想了個方法進行處理。
動態插入Script:
首先碰到的問題是,產生回來的html(包含html標記和script指令碼)插入到對應的容器的innerHtml中,卻發現script不見了。結果發現是返回來的指令碼是這樣子的<script>...</script>,如果有動態使用document.write輸出指令碼到頁面的人,可能明白這樣子寫是會出出錯的,一定要把</script>拆開寫才可以("</SCRIPT" + ">"),修改過後(代碼如下),html的插入正常了。
<HTML><SCRIPT>function insertScript(){var sHTML="<input type=button onclick=" + "run()" + " value='Click Me'><BR>";var sScript="<SCRIPT>";sScript = sScript + "function run(){ alert('Hello from inserted script.') }";sScript = sScript + "</SCRIPT" + ">";divHtml.innerHTML = sHTML + sScript;}</SCRIPT><BODY onload="insertScript();"><DIV ID="divHtml"></DIV></BODY></HTML>
但是這樣子就存在一個問題了,我需要去替換整個html中的</script>,麻煩。因為容器中的控制項都是我們自己開發的,其中關鍵指令碼的輸出和html的輸出是可以分開寫的,所以想到是否分成兩部分進行一個更新,一個容器放真正的html,一個容器專門放script,修改代碼如下:
<HTML><SCRIPT>function insertScript(){var sHTML="<input type=button onclick=" + "run()" + " value='Click Me'><BR>";var sScript="<SCRIPT>";sScript = sScript + "function run(){ alert('Hello from inserted script.') }";sScript = sScript + "</SCRIPT"+">";divHtml.innerHTML = sHTML;divScript.innerHTML = sScript;}</SCRIPT><BODY onload="insertScript();"><DIV ID="divHtml"></DIV><DIV ID="divScript"></DIV></BODY></HTML>
結果問題又出來,你會發現你的script又丟了,沒有真正插入到divScript容器中。
經過測試,原來如果插入script到innerHTML的話,單單插入script代碼是不行的,一定要在script之前帶上html,好比上例中的input,而且不能放在script後面,即一定要是這樣子的格式:
<html>+<script>,而不能是<script>或者<script>+<html>.
動態執行Script:
經過上面的修改,現在的html和script都已經正確更新到dom中了,但是script中定義的方法卻沒有執行。
要執行字串中的指令碼,我第一步想到的是使用eval。
但是目前html和script是混合一起輸出的,所以需要分離出要執行的script代碼,然後使用eval。
首先寫了個分離指令碼的函數:
function AnalyzeHtml(html,start,end){var regexp=new RegExp(start+"((.|\r\n)*?)"+end,"g");var strings=html.match(regexp);var objs=new Array();var regexp2=new RegExp("(^"+start+"|"+end+"$)","g");var js;if(strings!=null){for(var i=0;i<strings.length;i++){objs[i] = strings[i].replace(regexp2,'');}var strScript = objs.join(";").replace(/\\\"/g,"\"").replace(/\\\'/g,"'");//eval(strScript);js += strScript+";"}html = null;regexp=null;strings=null;objs = null;regexp2 = null;eval(js);return js;}
在Callback返回的函數中調用:
html = html.replace(/<script type=\'text\/javascript\'>/g,"<script>").replace(/<script type=\"text\/javascript\">/g,"<script>");//把不同的script標籤替換為一樣的AnalyzeHtml(html,"<script>","</s"+"cript>") ;
本來以為事情告一段落,結果新的問題又出來了。
通過eval執行的指令碼倒是運行了,但是其中定義類的指令碼卻丟失了,比如:
//丟失var a = "變數";//丟失function run(){//TODO:}//正常運行alert("執行函數");
其中定義類的指令碼(比如變數,比如匿名函數)都無法加到當前的window域內,立即執行的指令碼則正確運行了。看來無法通過eval來執行script的動態輸出了。
搜尋網路上的一些文章之後,得到一個新的方法,是利用ie本身的機制來執行的。即當節點被移除的時候,ie會重新解析節點內部的html,有指令碼則會執行相關的指令碼,不過這裡面的指令碼必須設定defer屬性才可以。新方法如下:
/* * 描述:跨瀏覽器的設定 innerHTML 方法 * 允許插入的 HTML 程式碼中包含 script 和 style * 作者:kenxu <ken@ajaxwing.com> * 日期:2006-03-23 * 參數: * el: 合法的 DOM 樹中的節點 * htmlCode: 合法的 HTML 程式碼 * 經測試的瀏覽器:ie5+, firefox1.5+, opera8.5+ */var setInnerHTML = function (el, htmlCode) {var ua = navigator.userAgent.toLowerCase();if (ua.indexOf('msie') >= 0 && ua.indexOf('opera') < 0) {htmlCode = '<div style="display:none">for IE</div>' + htmlCode;htmlCode = htmlCode.replace(/<script([^>]*)>/gi,'<script$1 defer>');el.innerHTML = htmlCode;el.removeChild(el.firstChild);} else {var el_next = el.nextSibling;var el_parent = el.parentNode;el_parent.removeChild(el);el.innerHTML = htmlCode;if (el_next) {el_parent.insertBefore(el, el_next)} else {el_parent.appendChild(el);}}}
此方法充分利用了瀏覽器自身的特性,執行效率高,相容性好。唯一的缺點就是指令碼中不能包含 document.write。還有另外一個方法如下:
/* innerhtml.js * Copyright Ma Bingyao <andot@ujn.edu.cn> * Version: 1.9 * LastModified: 2006-06-04 * This library is free. You can redistribute it and/or modify it. * http://www.coolcode.cn/?p=117 */var global_html_pool = [];var global_script_pool = [];var global_script_src_pool = [];var global_lock_pool = [];var innerhtml_lock = null;var document_buffer = "";function set_innerHTML(obj_id, html, time) {if (innerhtml_lock == null) {innerhtml_lock = obj_id;}else if (typeof(time) == "undefined") {global_lock_pool[obj_id + "_html"] = html;window.setTimeout("set_innerHTML('" + obj_id + "', global_lock_pool['" + obj_id + "_html']);", 10);return;}else if (innerhtml_lock != obj_id) {global_lock_pool[obj_id + "_html"] = html;window.setTimeout("set_innerHTML('" + obj_id + "', global_lock_pool['" + obj_id + "_html'], " + time + ");", 10);return;}function get_script_id() {return "script_" + (new Date()).getTime().toString(36)+ Math.floor(Math.random() * 100000000).toString(36);}document_buffer = "";document.write = function (str) {document_buffer += str;}document.writeln = function (str) {document_buffer += str + "\n";}global_html_pool = [];var scripts = [];html = html.split(/<\/script>/i);for (var i = 0; i < html.length; i++) {global_html_pool[i] = html[i].replace(/<script[\s\S]*$/ig, "");scripts[i] = {text: '', src: '' };scripts[i].text = html[i].substr(global_html_pool[i].length);scripts[i].src = scripts[i].text.substr(0, scripts[i].text.indexOf('>') + 1);scripts[i].src = scripts[i].src.match(/src\s*=\s*(\"([^\"]*)\"|\'([^\']*)\'|([^\s]*)[\s>])/i);if (scripts[i].src) {if (scripts[i].src[2]) {scripts[i].src = scripts[i].src[2];}else if (scripts[i].src[3]) {scripts[i].src = scripts[i].src[3];}else if (scripts[i].src[4]) {scripts[i].src = scripts[i].src[4];}else {scripts[i].src = "";}scripts[i].text = "";}else {scripts[i].src = "";scripts[i].text = scripts[i].text.substr(scripts[i].text.indexOf('>') + 1);scripts[i].text = scripts[i].text.replace(/^\s*<\!--\s*/g, "");}}var s;if (typeof(time) == "undefined") {s = 0;}else {s = time;}var script, add_script, remove_script;for (var i = 0; i < scripts.length; i++) {var add_html = "document_buffer += global_html_pool[" + i + "];\n";add_html += "document.getElementById('" + obj_id + "').innerHTML = document_buffer;\n";script = document.createElement("script");if (scripts[i].src) {script.src = scripts[i].src;if (typeof(global_script_src_pool[script.src]) == "undefined") {global_script_src_pool[script.src] = true;s += 2000;}else {s += 10;}}else {script.text = scripts[i].text;s += 10;}script.defer = true;script.type = "text/javascript";script.id = get_script_id();global_script_pool[script.id] = script;add_script = add_html;add_script += "document.getElementsByTagName('head').item(0)";add_script += ".appendChild(global_script_pool['" + script.id + "']);\n";window.setTimeout(add_script, s);remove_script = "document.getElementsByTagName('head').item(0)";remove_script += ".removeChild(document.getElementById('" + script.id + "'));\n";remove_script += "delete global_script_pool['" + script.id + "'];\n";window.setTimeout(remove_script, s + 10000);}var end_script = "if (document_buffer.match(/<\\/script>/i)) {\n";end_script += "set_innerHTML('" + obj_id + "', document_buffer, " + s + ");\n";end_script += "}\n";end_script += "else {\n";end_script += "document.getElementById('" + obj_id + "').innerHTML = document_buffer;\n";end_script += "innerhtml_lock = null;\n";end_script += "}";window.setTimeout(end_script, s);}
後記:
做完這個功能,花去了我大半天的時間,中間過程跌宕起伏,此情此景,不禁讓我想到了經典台詞“秋香,真是幾經波折啊!”^_^......