Hatena::ブログ(Diary)

惰性で・・・ RSSフィード

2008-02-17

[][]JSONPライブラリ作った その2

[javascript][JSONP]JSONPライブラリ作った

http://d.hatena.ne.jp/futa23/20080203/p1

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

これ、今頃気づいたんだけど・・・

prototype.js使っているなら、Function.prototype.bindを使えば

3番目の引数scopeっていらないじゃん。


これでOK。

//最後の引数は、bindの引数に指定。
<input type="button" onclick="new JSONP2('http://del.icio.us/feeds/json/futa23?', grid.build.bind(grid))" value="JSONP呼び出し">

scopeは必須の引数じゃなくてもいいので、使う際は

initialize: function(src, callback, scope){
//を
initialize: function(src, callback){
//に変更するだけ。

2008-02-08

[][]JSONPライブラリ作った・・・けどIE7で動かない?

あれ?IE7でしか動かないorz

Operaは大丈夫だけどIE7だと動かない?

動かないというかHTMLが表示できない。

なんで?

IE7はHTMLのチェックが厳しすぎるイメージ。


追記

OperaだとJSONPは同期処理になってしまうんだね。

その回避法。

TAKESAKO @ Yet another Cybozu Labs: Operaでも非同期リクエストが並列処理できる img-JSONP

http://labs.cybozu.co.jp/blog/takesako/2007/06/opera_img-jsonp.html

非同期リクエストをimgタグで飛ばして、結果をonerrorハンドラで受けとって、ブラウザキャッシュを再利用するという方法。


すんごい回避方法だなぁ。

これも組み込んじゃう?

2008-02-03

[][]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番目はAPIURL。(これだけ必須)

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

2007-10-13

[][][]エロくないやつも作ってみた

f:id:futa23:20071015022228j:image

f:id:futa23:20071015022209j:image

f:id:futa23:20071015022239j:image

mixi日記一覧や日記の個別ページで、サムネイル画像クリックすると

オーバーレイされてオリジナルの大きな画像が表示されます。

おまけでプロフィルページの「ほかの写真を見る」の横にアイコンが出て来るので

クリックするとプロフィール画像が全てオーバーレイ表示されます。

表示された画像クリックすると、オーバーレイが終了します。


mixiImager -- Userscripts.org

http://userscripts.org/scripts/show/12959

あまりソースはきれいじゃないのはご愛敬。


ほんとはLightBox.jshttp://www.huddletogether.com/projects/lightbox/

みたく中央に枠をつけて表示させたかったんだけど

画像のwidthとheightが取得できなかった。

なんでだろう?


急にテキストエリアで改行できなくなったorz

なんで?

2007-10-08

[][][]やっぱり技術革新の源はエロだね

前から書いてみたかったGreasemonkeyを書いてみた。

あと、ついでにXPathも使ってみた。

初めてのことづくしで丸一日かかったorz

よくわかってないので、おかしなソースを書いているかもしれないがキニシナイヽ(`Д´)ノ

download->DMMimager -- Userscripts.org

早速、試したい方はこちら(成人リンク)*1


できること

  1. DMMでの全サムネイルのオートロード
  2. DMMでのジャケット写真の先読み

「DMMでの全サムネイルのオートロード」

DMMでは「全部見る」というリンクをクリックしないと

全てのサムネイルが見れないのを、勝手にajaxでロードさせます。

「DMMでのジャケット写真の先読み」

ジャケット写真にmouseoverさせると、リンク先の拡大写真を表示する。mouseoutで消える。

拡大写真のページはウィンドウサイズを勝手に変更してきやがりますが、それから解放される。


しょーもない技術的な説明

「全部見る」というTextNodeを持つAncherを取るためにXPathを使いました。

$X('//A[contains(string(.),"\u5168\u90E8\u898B\u308B")][1]')
\u5168\u90E8\u898B\u308B

は「全部見る」の16進数表記。こうしないと動きませんでした。


$XはXPathを使うためのユーティリティ関数。

私製版、GreasemonkeyでXPathを楽に使う関数 @蕪浅録奏から頂きました。

ありがとうございます。

$X内で使ってるlog関数はfirebugのコンソールに出すためのログ関数。

無駄に指定しなきゃいけないnullとか無駄に長いsnapshotLengthやsnapshotItemから解放されます。

function $X(xpath, node) {
        var node = node || document
	log('xpath=' + xpath);
        var nodesSnapshot = document.evaluate(xpath, node, null,
            XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
        var data = []
        for (var i = 0; i < nodesSnapshot.snapshotLength; i++) {
            data.push(nodesSnapshot.snapshotItem(i))
        }
        return (data.length >= 1) ? data : null
}

var logEnable = false;
function log(str) {
	if(console.log && logEnable) console.log(str);
}

その他

ロードするHTMLページをさらにXPathするために、

HTMLドキュメント化する必要があったのでLDRizeからソースを拝借しました。

thanks to id:swdyh!

でも実はよくわかってないw

function createDocumentFragmentByString(str) {
	var range = document.createRange()
	range.setStartAfter(document.body)
	return range.createContextualFragment(str)
}
function createHTMLDocumentByString(str) {
	var html = str.replace(/<!DOCTYPE.*?>/, '').replace(/<html.*?>/, '').replace(/<\/html>.*/, '')
        var htmlDoc  = document.implementation.createDocument(null, 'html', null)
        var fragment = createDocumentFragmentByString(html)
        htmlDoc.documentElement.appendChild(fragment)
        return htmlDoc
}

バグ

  1. 一覧画面でもジャケット写真と勘違いして取ってきてしまいます
  2. 商品ページによっては、サムネイルの配置が崩れます

1つ目のバグはジャケット写真を1つ目のJPEG画像で取っているため。

回避方法が浮かばないのと、実害がないので無視。

その他、バグがあったら教えてください。


参考

Dive Into Greasemonkey の日本語訳 (PDF)がよかった。

Greasemonkeyの解説としてもいいですが、JavaScriptの基礎的なことや

XPathについても書かれていて一読の価値ありだと思う。

*1:人気ランキング1位だそうです