JSONPライブラリ作った

Javascriptを触りだして9ヶ月経つ。
昔のコードを見ると恥ずかしくなるなぁ(゚ε゚)キニシナイ!

クロスドメインでアクセスするための非同期通信としてJSONPはすごく便利だけど

ちょっとなぁってところが3つある。

  1. callback関数名を渡すところ
  2. scriptタグのremoveChildを書くのが面倒くさい
  3. callback関数のスコープ


1つ目

<script type="text/javascript" charset="UTF-8" src="http://del.icio.us/feeds/json/futa23?callback=コールバック関数名"/>

初めてJSONPを知ったJSONscriptRequestでもコールバック関数をURL内に文字列として指定してる。


文字列で指定するんじゃなくて関数で指定したい!
例えば、こんな風に指定できたほうが自然だよね。

new JSONP2('http://del.icio.us/feeds/json/futa23?', function(json){alert(json)})


2つの目

function callback(json){
 //描画
 //scriptタグの削除
}

scriptタグをappendChildして関数をコールするのがJSONPだけど
そのscriptタグの削除をcallback関数で書かないといけないよね?
まぁ別に放置しててもいいけどちょっと精神衛生上気持ち悪い。
scriptタグのremoveChildはライブラリ内でうまいことやっといてほしい。
ある関数を実行し終わったら必ず特定の処理をする、
この場合callback関数を実行したらJSONPコールに使ったscriptタグの削除。
つまりAOPみたいなことできないものか。


3つ目
GUIを描画する部品とか作ってJSONPでデータをもらうと
GUI部品をnewして

  //DIV#target=描画ターゲット
 grid = new GridBuilder($('target'));

そのインスタンスの描画メソッドにJSONPのデータをもらって描画って流れになると思う。
以下の例では、gridというインスタンスのbuildメソッドを呼び出してる。

<script type="text/javascript" charset="UTF-8" src="http://del.icio.us/feeds/json/futa23?callback=grid.build"/>

1つ目みたいな書き方をしつつ、スコープがgridになるようにしたい。

ということで作ってみた!JSONP2クラス。

JSONP2ってクラス名になってる。
callbackパラメータ名はcallback固定にしてあるけど、
こういうのもオプションで外から指定できるようにハッシュで引数を用意するのもアリ。

/*******************************************
* JSONP call<br/>
* @auther futa23<br/>
* @version 2008/02/03
********************************************/
JSONP2.counter = 1;
function JSONP2(src){
  this.initialize.apply(this, arguments);
}
JSONP2.prototype = {
  scriptTag: '',
  headTag: '',
  initialize: function(src, callback, scope){
      this.scriptTag = document.createElement("script");
    this.headTag = document.getElementsByTagName("head").item(0);

    this.scriptTag.id = "jsonp" + JSONP2.counter++;
    this.scriptTag.setAttribute('type', 'text/javascript');
    this.scriptTag.setAttribute('charset', 'UTF-8');
    var noCache = '&_noCache=' + new Date().getTime();
    var url = src + noCache;
    
    switch(typeof callback) {
      case 'string':
        url += '&callback=' + callback;
        break;
      case 'function':
        var callbackName = 'callback' + this.scriptTag.id;
        var self = this;
        scope = scope || window;
        window[callbackName] = function(json) {
                    return function() {
                        callback.apply(scope, arguments);
                        window[callbackName] = undefined;
                        try{ delete window[callbackName]; } catch(e){}
                        if (self.headTag) self.remove();
                    }(json);//これで実行される
        }      
        url += '&callback=' + callbackName;
        break;
      default:
        break;
    }
    
    this.scriptTag.setAttribute('src', url);
      this.headTag.appendChild(this.scriptTag);
  },
  remove: function(){
    this.headTag.removeChild(this.scriptTag);
  }
}


使い方。
1番目はAPIのURL。(これだけ必須)
2番目はメソッドの指定。
3番目はスコープの指定。protoype.jsのFunction.prototype.bindみたいなイメージ。

//URLにcallbackまで指定する場合(windowにcallbackとしてhundlerという関数を定義している場合)
<input type="button" onclick="new JSONP2('http://del.icio.us/feeds/json/futa23?callback=hundler')" value="JSONP呼び出し">

//上記例だとcallbackがわかりにくいので、callback関数名を指定
//switch文の分岐のstringの場合
<input type="button" onclick="new JSONP2('http://del.icio.us/feeds/json/futa23?', 'hundler')" value="JSONP呼び出し">

//関数を指定しつつ、スコープのバインド付き(UI部品をcallbackとして指定する場合)
<input type="button" onclick="new JSONP2('http://del.icio.us/feeds/json/futa23?', grid.build, grid)" value="JSONP呼び出し">


コールバック関数名を自動生成して、window[自動生成したコールバック関数名]としてURLに指定してる。
自動生成した関数では、引数でもらったコールバック関数をラップした関数を返して実行してる。
ラップした関数内で、引数でもらったコールバック関数の呼び出しとscriptタグの削除などを順にやってる。
その他、callback関数名を文字列で指定しても動くように引数のcallbackをtypeofで評価して
型がfunctionのときだけ上記処理をしてる。
文字列のときは、そのままURLに追加だけ。
あとはブラウザのキャッシュ抑制のための変数とか勝手に入れてる。


って、そういやjQueryJSONPコールがサポートされてるんだった。
jQuery で JSONP 2通り - てっく煮ブログ
これを参考にすればよかったorz
まさに車輪の再発明

$.ajax({
    url : "http://www.example.com/jsonp.cgi",
    dataType : "jsonp",
    data : {
        param1 : "value1"
    },
    success : function(json){
        // ロード完了時にここが呼ばれる
    },
    error : function(){
        alert('error');
    }
});

errorとか参考にさせてもらおっと。

コピーライト

しょーもないソースなんで自由に使ってください。
prototype.jsとか使ってないので単体で動きます。

感想

自分で作ったやつをリファクタリングしたくなってきた(´・ω・`)
カリー化ってのが初めてちゃんとわかったかも。

追記

第1引数だけだと、callback関数がわからないので
さすがにscriptタグの削除はできないです。
setTimeoutで10秒とか長めな遅延実行させてもいいけど、
そこまでして消さなくてもいいかなぁと。

追記2

[javascript][JSONP]JSONPライブラリ作った その2
http://d.hatena.ne.jp/futa23/20080217/p1