ブックマークレット実行時に外部ファイルをロードして使う

ブックマークレットの書き方の段階的な発展の仕方の続き。

基本

javascript:
(function(d,s){
  s=d.createElement('script');s.src='//dl.dropboxusercontent.com/u/XXXXXXX/bookmarklet.js';d.body.appendChild(s);
})(document)
  • Webページのbodyタグに以下のようなscriptタグを追加して、
  • シンプルに、たった一つの外部ファイルをロードしているのだ。
<script src='//dl.dropboxusercontent.com/u/XXXXXXX/bookmarklet.js'></script>

複数の外部ファイルをロードする

  • では、外部ファイルを二つ以上ロードしたいときはどうするべきか?
  • 方法としては、ロードしたい外部ファイルの数だけscriptタグを追加すればいいはず。
  • 外部ファイル二つなら、コピペして、srcのURLだけ変更すればいいのかもしれない。
javascript:
(function(d,s){
  s=d.createElement('script');s.src='//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js';d.body.appendChild(s);
  s=d.createElement('script');s.src='//dl.dropboxusercontent.com/u/XXXXXXX/bookmarklet.js';d.body.appendChild(s);
})(document)
  • では、外部ファイルを三つ以上ロードしたい時も、コピペを繰り返すべきなのか?
  • いや、やめておこう。URLだけ指定すればロードしてくれる、そんな関数が欲しい。
javascript:
(function(d,urls,i,s){
  for(i=0;i<urls.length;i++){s=d.createElement('script');s.src=urls[i];d.body.appendChild(s)};
})(document,['//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js','//dl.dropboxusercontent.com/u/XXXXXXX/bookmarklet.js'])


これで外部ファイルがいくつ増えても、URLを追加するだけでロードしてくれる!

  • ここまで、外部ファイルをひたすらロードするだけのブックマークレットである。
  • ひたすらロードするだけであっても、何でも出来るはず。
  • 目的のコードを外部ファイルに保存しておけばいいのだ。
  • 外部ファイルの保存場所としては、DropboxのPublicディレクトリなどが使える。

外部ファイルをロードしてからブックマークレット内部のコードを実行する

  • 上記までのように、すべてのコードを外部ファイルにしてしまうのも一つの方法である。
  • でも通常は、ライブラリのみ外部からロードして、目的のコードはブックマークレット内部に書いておきたい。
はじめの一歩
javascript:
(function(d,s){
  s=d.createElement('script');s.src='//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js';d.body.appendChild(s);

  /* 内部コード */
  alert(typeof CryptoJS);
  alert(CryptoJS.AES.encrypt('hello','1234'));
})(document)
  • しかし、上記のコードを実行してみると、正常には動かない...。
    • 事前準備として、どこかのページを新規に開いておく。
    • キャッシュを空にして、ページを再読み込みしておく。

  • 気を取り直して、もう一度、同じページで実行するとちゃんと動く。


  • 最初に動かなかったのは、気のせいだった訳ではない。
  • CryptoJSが、しっかり"undefined"になっているのだ。
  • おそらく、crypto-jsライブラリのロードが完了する前に、次のalert文の実行が始まってしまったのだ。
setTimeoutを使う
  • 外部ファイルがロードされるタイミングと、内部コードが実行されるタイミングに気を付けて書き直し。
javascript:
(function(d,s){
  s=d.createElement('script');s.src='//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js';d.body.appendChild(s);

  setTimeout(function(){
    /* 内部コード */
    alert(typeof CryptoJS);
    alert(CryptoJS.AES.encrypt('hello','1234'));
  },1000);
})(document)
  • キャッシュを空にして、ページを再読み込みして、事前準備完了。


今度は、初回から正常に動作した!

  • setTimeoutで、内部コードの実行を1秒遅らせてみたのだ。
  • その間に、crypto-jsのロードが完了して、CryptoJSオブジェクトが利用可能になるのだ。
onloadを使う
  • しかし、必ず1秒待機するのは無駄が多い。
  • あるいは、1秒待機してもロードは未完かもしれない。
  • 本来は、ロードが完了したタイミングで、すぐに内部コードを実行するのがベスト。
  • onloadイベントハンドラを使うと、ロードが完了ししたタイミングで実行できる。
javascript:
(function(d,f,s){
  s=d.createElement('script');
  s.src='//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js';
  s.onload=function(){f()};/* <---onload属性を追加 */
  d.body.appendChild(s);

  f=function(){
    /* 内部コード */
    alert(typeof CryptoJS);
    alert(CryptoJS.AES.encrypt('hello','1234'));
  };
})(document)


これで、無駄に待機せずに素早く実行できるようになった!

onloadするのは最後だけ
  • 複数のライブラリを読み込む時は、最後のロードだけonloadする。
  • 毎回onloadすると、内部コードの実行が繰り返えされてしまう...。
javascript:
(function(d,f,s){
  s=d.createElement('script');
  s.src='//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha256.js';
  d.body.appendChild(s);

  s=d.createElement('script');
  s.src='//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js';
  s.onload=function(){f()};/* <---onload属性を追加はここだけ */
  d.body.appendChild(s);

  f=function(){
    /* 内部コード */
    alert(CryptoJS.SHA256('1234'));
    alert(CryptoJS.AES.encrypt('hello','1234'));
  };
})(document)
for+onload
  • 複数の外部ファイルをロードするforを使った技と組み合わせてみた。
javascript:
(function(urls,i,s,f){
  for(i=0;i<urls.length;i++){
    s=document.createElement("script");
    s.src=urls[i];
    if(i==urls.length-1){s.onload=function(){f()}};
    document.body.appendChild(s);
  };

  f=function(){
    /* 内部コード */
    alert(CryptoJS.SHA256('1234'));
    alert(CryptoJS.AES.encrypt('hello','1234'));
  };
})(["//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha256.js","//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"])
内部コードの関数を引数渡し
  • 外部ファイルのロード内部コードを書く部分が明確に分かれる所が好き。
javascript:
(function(f,urls,i,s){
  urls=["//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha256.js","//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"];
  for(i=0;i<urls.length;i++){
    s=document.createElement("script");
    s.src=urls[i];
    if(i==urls.length-1){s.onload=function(){f()}};
    document.body.appendChild(s);
  };
})(function(){
  /* 内部コード */
  alert(CryptoJS.SHA256('1234'));
  alert(CryptoJS.AES.encrypt('hello','1234'));
})

jQueryを使う

基本
javascript:
(function(f,urls,i,s){
  urls=["//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"];
  for(i=0;i<urls.length;i++){
    s= document.createElement("script");
    s.src=urls[i];
    if(i==urls.length-1){s.onload=function(){f()}};
    document.body.appendChild(s);
  };
})(function(){
  /* 内部コード */
  alert($().jquery);
})
コンフリクト防止
  • jQueryはよく使われるライブラリである。
  • 様々なバージョンがあって、中には互換性のないバージョンもあったりする。
  • また、jQueryやprototypeなどはシンプルに書くため、$変数にそのオブジェクトを代入する。
  • jQueryやprototypeなどを使っているページで上記ブックマークレットを実行するとどうなるか?
  • 困ったことに、現在のページが正常に動作しなくなってしまう恐れがあるのだ...。
  • ブックマークレットがロードしたjQueryによって、$変数が書き換えられてしまうため。
jQuery.noConflict();
    • $変数にjQueryオブジェクトを代入しない。
    • jQuery変数にはjQueryオブジェクトを代入する。
var myQuery=jQuery.noConflict(true);
    • $変数にjQueryオブジェクトを代入しない。
    • jQuery変数にもjQueryオブジェクトを代入しない。(より正確には、jQuery変数を以前の状態に戻す)
    • myQuery変数にjQueryオブジェクトを代入する。
  • jQuery.noConflict(true)を使って、書き直してみる。
javascript:
(function(f,urls,i,s){
  urls=["//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"];
  for(i=0;i<urls.length;i++){
    s= document.createElement("script");
    s.src=urls[i];
    if(i==urls.length-1){s.onload=function(){f(jQuery.noConflict(true))}};
    document.body.appendChild(s);
  };
})(function($){
  /* 内部コード */
  alert($().jquery);
})
  • 内部コードの仮引数として$を指定して、内部コードではローカルな$変数が利用される。
  • ローカルな$変数には、jQuery.noConflict(true)が返すjQueryオブジェクトが代入されるのだ。
    • onload=function(){f(jQuery.noConflict(true))}由来。


これで、既存のjQuery環境や$変数に一切影響を与えず、ブックマークレットjQueryを利用できるようになる!

すぐに使えるワンライナーの雛形

      • URL = ロードする外部ファイルのURL。
      • 内部コード = ブックマークレット内部に書くコード。(ロードしたライブラリを活用できる)
  • 指定したURLの外部ファイルをロードする。(ひたすらロードするのみ)
javascript:(function(urls,i,s){for(i=0;i<urls.length;i++){s=document.createElement('script');s.src=urls[i];document.body.appendChild(s)};})([/* "URL","URL",... */])
  • 指定したURLの外部ファイルをロードしてから、内部コードを実行する。
javascript:(function(f,urls,i,s){urls=[/* "URL","URL",... */];for(i=0;i<urls.length;i++){s=document.createElement("script");s.src=urls[i];if(i==urls.length-1)s.onload=function(){f()};document.body.appendChild(s)}})(function(){/* 内部コード */})
javascript:(function(f,s){s=document.createElement("script");s.src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js";s.onload=function(){f(jQuery.noConflict(true))};document.body.appendChild(s)})(function($){/* 内部コード */})
javascript:(function(f,urls,i,s){urls=["//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"/* ,"URL","URL",... */];for(i=0;i<urls.length;i++){s=document.createElement("script");s.src=urls[i];if(i==urls.length-1)s.onload=function(){f(jQuery.noConflict(true))};document.body.appendChild(s)}})(function($){/* 内部コード */})