Hatena::ブログ(Diary)

ザリガニが見ていた...。 このページをアンテナに追加 RSSフィード

2014-08-11

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

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

基本

  • 前回の外部ファイルをロードして実行するブックマークレットは、こんな感じだった。
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ディレクトリなどが使える。

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

  • 上記までのように、すべてのコードを外部ファイルにしてしまうのも一つの方法である。
  • でも通常は、ライブラリのみ外部からロードして、目的のコードはブックマークレット内部に書いておきたい。
はじめの一歩
  • そして、最初に思いつくのはこんなコード。(alert部分がブックマークレット内部のコード)
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)
  • しかし、上記のコードを実行してみると、正常には動かない...。
    • 事前準備として、どこかのページを新規に開いておく。
    • キャッシュを空にして、ページを再読み込みしておく。

f:id:zariganitosh:20140809080331p:image:w428


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

f:id:zariganitosh:20140809080449p:image:w428

f:id:zariganitosh:20140809080626p:image:w428


  • 最初に動かなかったのは、気のせいだった訳ではない。
  • 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を使う

基本
  • これまでの技を使えば、ブックマークレットで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ライブラリを利用したい場合。
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(){/* 内部コード */})

  • ブックマークレット内でjQueryを利用する。
    • 既存のjQuery環境に、影響を与えない。
    • 既存のグローバルな$変数に、影響を与えない。
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($){/* 内部コード */})

  • ブックマークレット内でjQueryやその他複数のライブラリも利用する。
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($){/* 内部コード */})

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/zariganitosh/20140811/load_script_on_bookmarklet
リンク元