後からロードした外部JSファイルでdocument.writeする(2)
※1157010937の回答の4/4
今まで挙げた問題点をふまえ、以下の要望を満たす方法を考えます。
- HTMLファイルのロード後に外部JSファイルを読み込む
- 読み込む外部JSファイルの<script>タグを含んだHTMLのソースが、おおもとのネタ
- 外部JSファイルは、読み込まれたときにdocument.writeする
- 外部JSファイルのdocument.writeは、その<script>タグの位置に出したい
けっこう厳しい要求ですが、以下サンプルです。(前回のwriter.jsも使います)
<html>
<head>
<script type="text/javascript"><!--
// 擬似応答電文
var text = '<div>JSファイルの直前<script type="text/javascript" src="./writer.js" charset="Shift_JIS"></' + 'script>JSファイルの直後</div>';
// 以前の方法
function AjaxWriter_Old(str) {
var targetObj = document.getElementById("target");
targetObj.innerHTML = str;
}
// JSファイルをHTMLロード後に読み込む方法(IE△)
function ActiveScriptLoad(str) {
var targetObj = document.getElementById("target");
var dummyObj = document.createElement("div");
dummyObj.innerHTML = str;
targetObj.appendChild(dummyObj);
}
// 解決方法
function AjaxWriter_New(str) {
var dummyObj = document.createElement("div");
dummyObj.innerHTML = str;
var scripts = [];
(function(node) {
var children = node.childNodes;
for(var i = 0; i < children.length; i++) {
var child = children[i];
if(child.nodeName.toLowerCase() == "script") {
scripts[scripts.length] = {src : child.src, type : child.type, charset : child.charset, language : child.language, defer : child.defer};
} else {
arguments.callee(child);
}
}
})(dummyObj);
var name = "ScriptFilePoint";
var tags = str.replace(/<script.*?<\/script>/ig, '<span name="' + name + '"></span>');
var targetObj = document.getElementById("target");
targetObj.innerHTML = tags;
var spans;
if(document.all) {
// spans = document.getElementsByTagName("span");
// for(var i = spans.length - 1; i >= 0; i--) {
// if(spans[i].name != name) {
// spans = spans.slice(0, i).concat(spans.slice)((i + 1), spans.length))(;
// }
// }
spans = [];
var temp = document.getElementsByTagName("span");
for(var i = 0; i < temp.length; i++) {
if(temp[i].name == name) {
spans[spans.length] = temp[i];
}
}
delete temp;
} else {
spans = document.getElementsByName(name);
}
for(var i = 0; i < scripts.length; i++) {
ScriptLoadWithWrite(scripts[i].type, scripts[i].src, scripts[i].charset, spans[i]);
}
}
// スクリプトのロード(IE○)
function ScriptLoadWithWrite(type, src, charset, baseNode) {
// writeメソッドの隠蔽
var nativeWriter = document.write;
var nativeWriteln = document.writeln;
var stringBuffer = "";
document.write = function(str) {
clearTimeout(recover.timer);
recover.timer = setTimeout(recover, 0);
if(str) {
stringBuffer += str;
}
};
document.writeln = function(str) {
clearTimeout(recover.timer);
recover.timer = setTimeout(recover, 0);
if(str) {
stringBuffer += str;
}
stringBuffer += "\r\n";
};
// scriptタグの生成
var node = document.createElement("script");
node.src = src;
node.type = type;
node.charset = charset;
node.onload = recover;
var next = baseNode.nextSibling;
if(next) {
baseNode.parentNode.insertBefore(node, next);
} else {
baseNode.parentNode.appendChild(node);
}
// writeメソッドの戻し
function recover() {
clearTimeout(recover.timer);
node.onload = null;
document.write = nativeWriter;
document.writeln = nativeWriteln;
if(stringBuffer) {
var obj = document.createElement("span");
obj.innerHTML = stringBuffer;
var baseNode = node;
var next = baseNode.nextSibling;
if(next) {
baseNode.parentNode.insertBefore(obj, next);
} else {
baseNode.parentNode.appendChild(obj);
}
}
}
recover.timer = setTimeout(recover, 3000);
}
//--></script>
</head>
<body>
<div onclick="ActiveScriptLoad(text);">クリックで動的ロード</div>
<div onclick="AjaxWriter_New(text);">クリックで解決!</div>
<div onclick="AjaxWriter_Old(text);">クリックで比較(以前のもの)</div>
<div id="target" style="border:solid 1px black;"></div>
</body>
</html>
※「クリックで解決」以外は前回までのおさらいなので、説明省略します。AjaxWriter_Newメソッド
まず、AjaxWriter_Newメソッドでは、渡されたソースの中の<script>タグを調査します。
ダミーのエレメントを作成し、そのinnerHTMLにソースを流し込み、そこから<script>タグとその情報を抜き出します。
次に、ソースの<script>タグを別のものに置き換えて、目的の場所のinnerHTMLに挿入します。
スクリプトのロードをするためにScriptLoadWithWriteメソッドを呼んで終了です。
ScriptLoadWithWriteメソッド
まず、外部JSファイルをロードする前に、document.writeを隠蔽します。
外部JSファイルがロードされ、そこでdocument.writeされたらその文字列をバッファとして蓄積します。
次に、外部JSファイルをロードします。
最後に、外部JSファイルのロード完了を待って、recoverメソッドで隠蔽したdocument.writeを戻し、バッファの文字列を任意のオブジェクトの直後に書き出します。
recoverメソッドが呼ばれるまでは、本体側でdocument.writeが使えません。
ちなみに、ソースコードの最後のタイマーですが、外部JSファイルのロードに時間がかかったり、応答なしの場合にrecoverメソッドを実行するためのものです。
タイマーを0にすると、外部JSファイルのロードにちょっとでも時間がかかると先にrecoverメソッドが呼ばれ、うまく動きません。
ブラウザでタイムアウトするくらいの長さか、必ず帰ってくるならばその行自体をコメント化します。
個人的な感想…
なんだか、仕様の穴を抜けていくようなコードを書いた気がしてならないです。
途中で、またまたIEを無条件で強制終了させるコードを見つけてしまうし…。
ここまで書いて最後に設計バグを見つけました。
外部JSファイルが複数種類あってそのロード時間がけっこう違うとき、一番速いファイルでrecoverメソッドが走るので、その後のファイルの実行は失敗する気がする。
う゛〜ん、これは許して。