風邪

風邪で寝ていた。

風邪で寝ていた間にJavaScriptをいじった(寝てろよ)。



JQueryをさわってみて、以下のように思った。

JavaScriptではイベント関係の関数などがブラウザ依存なのでその辺の扱いはライブラリによって様々。

prototype.jsだと、

Event.observe(document,'click',function(){alert('hoge')});

みたいな感じ。

JQueryはすごいので

$('div#sidebar').click(function(){alert('hoge')});
$(document).click(function(){alert('hoge')});

または

$(document).bind('click',function(){alert('hoge')});

などと書ける。寝てる間に、以前つくった前のページをロードするやつをJQueryで書き直してみたが(寝てろよ)、適当に書いても結構すぐ動いたのですごいと思った。

しかしJQueryほどがんばらなくても、ブラウザ依存を解消する際に、

$html(document).event('click',function(){alert('hoge')});

などと書けるようにすると良いのではないか。$html()とやるとラッパー用のオブジェクトを返すの。

そう思い、はじめは"HTML = function()"などとやっていたが、見づらいのでラッパー用のクラスを抽象化したものをつくった。

//util
Object.extend = function(destination, source){
    for(var key in source){
        destination[key] = source[key];
    }
    return destination;
};
Object.inherit = function(destination, source){
    for(var key in source){
        if(destination[key]) continue;
        try{
            destination[key] = source[key];
        }catch(e){};
    }
};
//wrapperClass
var wrapperClass = function(){
    var klass = function(){
        this.initialize.apply(this,arguments);
    };
    klass.copy = false;
    klass.wrap = function(source){
        var wrapping = new this();
        if(this.copy){
            Object.inherit(wrapping,source);
        }
        wrapping.original = source;
        return wrapping;
    };
    klass.sugar = function(bind){
        var _this = this;
        var _wrap = function(){
            return _this.wrap.apply(_this,arguments)
        };
        return _wrap;
    };
    klass.prototype = {};
    klass.prototype.initialize = function(){};
    return klass;
};

使い方の例。

//HTML Element
HTML = new wrapperClass();
HTML.copy = true;
Object.extend(HTML.prototype,{
    event: function(type,handler){
        var element = this.original;
        if(element.addEventListener)
            element.addEventListener(type,handler,false);
        else if(element.attachEvent)
            element.attachEvent('on'+type,handler);
    },
    removeEvent: function(type,handler){
        var element = this.original;
        if(element.removeEventListener)
            element.removeEventListener(type,handler);
        else if(element.detachEvent)
            element.detachEvent('on'+type,handler);
    }
});
var $html = HTML.sugar();
var handler = function(){alert('hoge')};
$html(document).event('click',handler);

new wrapperClass()とやると、wrapおよびsugarというメソッドをもったクラスを作成する。

その後、HTML.wrap(document)とか、HTML.wrap(document.getElementById('hoge'))などとやると、HTMLクラスのインタンスを作成する。

小文字のhtmlがインスタンスの名前だとするとhtml.originalに元のエレメントが格納される。htmlはHTMLクラスのインスタンスなので、prototypeにブラウザ依存解消用のラッパー関数を登録しておけばよい。上の例ではhtml.eventがクロスブラウザで動くイベントハンドラ登録用の関数となっている。

HTML.copyがtrueになってる場合、htmlにはoriginalのプロパティがコピーされる。これはもちろんただのコピーなのでインスタンスであるhtmlを書き換えてもドキュメントそのものを変更することはできないが、リードのみならば役に立つこともあるだろう。

また、HTML.sugar()とやるとシンタックスシュガー用の関数を作成する。単に$html = HTML.wrapとやるとコンテキストが変わってしまいうまく動かないため、こちらを利用する。$html=HTML.sugar()とすると、$html(document).eventなどと書けるようになる。


個人的にはklass.wrapの中のnew this()というところが気持ち悪くて良いと思う。



追記:

wrapの部分を以下のように書けば、オリジナルのメソッドを、あたかもラッパーオブジェクトのメソッドであるかのように実行できることに気づいた。

klass.wrap = function(source){
    var wrapping = new this(source);
    if(this.copy){
        for(var key in source){
            if(wrapping[key])continue;
            try{
                if(typeof source[key] == 'function'){
                    wrapping[key] = function(){ 
                        return source[key].apply(source,arguments)
                    };
                }
                else wrapping[key] = source[key]; 
            }catch(e){}
        }
    }
    wrapping.original = source;
    return wrapping;
};

これなら、$html(document).createElement('div')などと書くこともできる。

個人的にはすごい発見のような気がしたのだが、書いているうちにだんだん自信がなくなってきた。

さらに追記:

上の書き方だと動かなかった。

var makeWrapper = function(wrapper,source){
    for(var key in source){
        if(wrapper[key])continue;
            if(typeof source[key] == 'function'){
                var methods = function(){
                    return source[key].apply(source, arguments);
                }
                wrapper[key]=methods;
            }
            else wrapper[key] = source[key];
    }
};
var o = {};
makeWrapper(o,document);
o.createElement('div');

これだと動かない。argumentsの中身は空になる。

var fixContext = function(func, bind){
    return function(){
        return func.apply(bind,arguments);
    }
}
makeWrapper = function(wrapper,source){
    for(var key in source){
        if(wrapper[key])continue;
            if(typeof source[key] == 'function')
                wrapper[key] = fixContext(source[key],source);
            else wrapper[key] = source[key];
    }
};
var o = {};
makeWrapper(o,document);
o.createElement('div');

これだと動く。

oからdocumentのメソッドが呼び出せるようになる。ただしIEだとダメ(documentのメソッドに対してapplyを使えない)。




eventListenerに引数をわたす

普通にaddEventListenerやattachEventを使ってイベントリスナを登録すると、リスナに引数をわたすことができない。

またイベントリスナはグローバルな関数として実行されるのでthisの参照先が変わってしまい困ることがある。

Hoge = function(value){this.value = value}
Hoge.prototype.plus = function(){
    this.value++;
};
Hoge.prototype.minus = function(){
    this.value--;
};
Hoge.prototype.setVal = function(value){
    this.value = value;
};
Hoge.prototype.setListener = function(){
    $html(document).event('click',this.plus);
    $html(document).event('keydown',this.setVal(0));
}
var h = new Hoge(0);
h.setListener();

これだとイベントハンドラとして呼びだされたときにh.minusとh.plusの中のthisの参照先がインスタンスであるところのhじゃなくてwindowになっちゃうのでダメなんだよ。あとsetVal(0)もちゃんと動かないよ。

じゃあどうすればよいか。

Hoge.prototype.setListener = function(){
    var self = this;
    $html(document).event('click',
        function(){self.plus()}
    );
    $html(document).event('keydown',
        function(){self.setVal(0)}
    );
}

わたしは今までこうやってやっていた。こうすると、変数selfにthis==hが格納され、selfはイベントハンドラにわたした無名関数からでも参照できるのでうまくいく。

しかしprototype.jsを見ていたら以下のような方法が使われていて、こっちの方がかっこいいと思った(内容はちょっと変えてある)。

//イベントハンドラ作成用の関数。
//bindしたいコンテキストとハンドラにわたしたい引数をわたす。
Function.prototype.toHandler = function(){
    var __method = this,
        args = [];
    //argumentsをコピー
    for(var i=0,leng=arguments.length;i<leng;i++)
        args[i]=arguments[i];
    //bindは関数にわたしたいコンテキスト
    var bind = args.shift();
    // ハンドラを作成し返す。
    /// ハンドラは元の関数をbindのコンテキストで実行し、
    /// 引数にはtoHandlerにわたした引数とeventが渡される。
    return function(event){
        __method.apply(bind,args.concat([event || window.event]))
    }
}

Functionコンストラクタのプロトタイプ関数にハンドラ作成用の関数を登録してしまう。

これをやると、

Hoge.prototype.setListener = function(){
    $html(document).event('click', this.plus.toHandler(this));
    $html(document).event('keydown', this.setVal.toHandler(this,0));
}

と書けてすっきり。

コメント(7)

# contractio

「追記:」のところのタグが閉じていないように思われる。

(2007/12/12 4:02)
# atakada

ご指摘ありがとうございます。直しました。

(2007/12/12 9:07)
# contractio

なおってなくね?

(2007/12/13 23:52)
# atakada

あれ? 線がびゃーと入ってるのは全体がinsタグのなかに入っているからですよ(これはわざと)。それともそこのことじゃない?

(2007/12/14 0:22)
# contractio

了解した。

(2007/12/14 5:28)
# mdb212

Seems different from your previous posts. Did YOU write this post, or someone else did? Anyway, I think your readers really enjoyed reading it.

(2008/04/ 7 2:07)
# Gdog

This website is for people who have nothing to do, for those who spend all time fooling around in internet

(2008/04/ 9 20:03)

コメントする

トラックバック(0)

このブログ記事を参照しているブログ一覧: 雑記2007年12月2日(日)

このブログ記事に対するトラックバックURL: http://www.at-akada.org/mt/mt-tb.cgi/854

著者について

赤田敦

nightly[at]at-akada.org

紹介: about

ホーム: at-akada.org

-> 携帯電話用

なかなか更新されないときは...

-> 赤田ブログ生成器

2009年1月

        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31