IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Prototype.js を使った JavaScript OOP 講座 #02

社内の勉強会の資料をここに公開していきます。社内の人も社外の人も読んでください。


※ターゲットは JavaScript は書いたことない、オブジェクト指向言語プログラマ
※信念は「教わるのではなく、必死に着いていきませう」


今週はかなりハードボイルドで全身タイツのような忙しさなので、みんなが春を分けて楽しんでいたり、香港に行って楽しんでいる間にせこせこ資料を作っています。
はぁ。

Section 00 Prototype.js の前に JavaScript と DOM とイベントの概要

HTML は読み込まれた後、すべての情報が JavaScript のオブジェクトに変換される。
イメージ的にはこんな感じ

<html>
    <head>
        <title>タイトル</title>
    </head>
    <body>
    :
    :
    </body>
</html>

↓↓↓

var document = {
    documentElement: {
        tagName: 'HTML',
        childNodes: {
            0: {
                tagName: 'HEAD',
                childNodes: {
                    0: {
                        tagName: 'TITLE',
                        childNodes: {
                            0: {
                                nodeValue: 'タイトル'
                            }
                        }
                    }
                }
            },
            1: {
                tagName: 'BODY',
                childNodes:{
        :
        :
                }
            }
        }
    }
};

この仕組みを DOM と言い。W3C という標準化団体によって標準化されている。

たとえば html 要素に対応するオブジェクトは document.documentElement で取得できる。
以下のようにすると html 要素の中に含まれるソースを文字列として見ることが出来る。

<html><head><script>

    alert(document.documentElement.innerHTML);

</script><body>Sample Sample Sample</body></html>

しかし、上の JavaScript を実行すると、不完全な( body がない) HTML が表示される。
これは、script 要素が読み込まれた時点でスクリプトが実行され、以後のソースがオブジェクト化されていないからである。

DOM の状態が不完全ではコンテンツを自由に操ることが出来ないため、 DOM が構築されたタイミングで処理を開始しなければならない。

// 一番古い方法(イベントリスナはひとつしか登録できない)
window.onload = function() {
    alert('ロードされた');
};
// W3C によって標準化された方法。 IE では未対応
window.addEventListener('load', function() {
    alert('ロードされた');
}, false);
// IE のみ
window.attachEvent('onload', function() {
    alert('ロードされた');
});

上のソースでやってることを「 window オブジェクトで発生する 'load' イベントをイベントリスナ(実はただの関数オブジェクト)によって監視する」と表現する。
イベントは DOM の全てのオブジェクトに発生するもので 'load' 以外にもたくさんある。

Section 01 Event.observe

Source 01 通常のイベントリスナ登録
var ColorfulBlock = Class.create();

ColorfulBlock.prototype = {
    element: null,
    r: 0, g: 0, b: 0,
    initialize: function(id) {
        this.element = document.getElementById(id);
        if(this.element.addEventListener) {
            this.element.addEventListener('mouseover', this.onMouseOver.bind(this), false);
            this.element.addEventListener('mouseout', this.onMouseOut.bind(this), false);
            this.element.addEventListener('mousemove', this.onMouseMove.bind(this), false);
        }
        else if(this.element.attachEvent) {
            this.element.attachEvent('onmouseover', this.onMouseOver.bind(this));
            this.element.attachEvent('onmouseout', this.onMouseOut.bind(this));
            this.element.attachEvent('onmousemove', this.onMouseMove.bind(this));
        }
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    },
    setColor: function(color) {
        Object.extend(this, color); 
        var array = ['rgb(', 100 - this.r * 20, '%,', 100 - this.g * 20, '%,', 100 - this.b * 20, '%)'];
        this.element.style.backgroundColor = array.join('');
    },
    onMouseMove: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400});
    },
    onMouseOver: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400, b: 0});
    },
    onMouseOut: function(event) {
        event = event || window.event;
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    }
};

var main = function() {
    new ColorfulBlock('element');
};

if(window.addEventListener) {
    window.addEventListener('load', main, false);
}
else if(window.attachEvent) {
    window.attachEvent('onload', main);
}

上の例を実行

Source 02 Event.observe を使ってイベントリスナを登録する。
var ColorfulBlock = Class.create();

ColorfulBlock.prototype = {
    element: null,
    r: 0, g: 0, b: 0,
    initialize: function(id) {
        this.element = document.getElementById(id);
        Event.observe(this.element, 'mouseover', this.onMouseOver.bind(this));
        Event.observe(this.element, 'mouseout', this.onMouseOut.bind(this));
        Event.observe(this.element, 'mousemove', this.onMouseMove.bind(this));
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    },
    setColor: function(color) {
        Object.extend(this, color);
        var array = ['rgb(', 100 - this.r * 20, '%,', 100 - this.g * 20, '%,', 100 - this.b * 20, '%)'];
        this.element.style.backgroundColor = array.join('');
    },
    onMouseMove: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400});
    },
    onMouseOver: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400, b: 0});
    },
    onMouseOut: function(event) {
        event = event || window.event;
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    }
};

Event.observe(window, 'load', function() {
    new ColorfulBlock('element');
});

上の例を実行

Source 03 おまけ。 $H を使ってリファクタリング( Event.observe と関係ないけど)
var ColorfulBlock = Class.create();

ColorfulBlock.prototype = {
    element: null,
    r: 0, g: 0, b: 0,
    eventLinteners: {
        mouseover: 'onMouseOver', mouseout: 'onMouseOut', mousemove: 'onMouseMove'
    },
    initialize: function(id) {
        this.element = document.getElementById(id);
        $H(this.eventLinteners).each(function(set) {
            Event.observe(this.element, set.key, this[set.value].bind(this));
        }.bind(this));
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    },
    setColor: function(color) {
        Object.extend(this, color);
        var array = ['rgb(', 100 - this.r * 20, '%,', 100 - this.g * 20, '%,', 100 - this.b * 20, '%)'];
        this.element.style.backgroundColor = array.join('');
    },
    onMouseMove: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400});
    },
    onMouseOver: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400, b: 0});
    },
    onMouseOut: function(event) {
        event = event || window.event;
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    }
};

Event.observe(window, 'load', function() {
    new ColorfulBlock('element');
});

上の例を実行

Section 02 $ 関数

Source 01 通常の要素取得( Section 01 の Source 03 と同じ)
var ColorfulBlock = Class.create();

ColorfulBlock.prototype = {
    element: null,
    r: 0, g: 0, b: 0,
    eventLinteners: {
        mouseover: 'onMouseOver', mouseout: 'onMouseOut', mousemove: 'onMouseMove'
    },
    initialize: function(id) {
        this.element = document.getElementById(id);
        $H(this.eventLinteners).each(function(set) {
            Event.observe(this.element, set.key, this[set.value].bind(this));
        }.bind(this));
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    },
    setColor: function(color) {
        Object.extend(this, color);
        var array = ['rgb(', 100 - this.r * 20, '%,', 100 - this.g * 20, '%,', 100 - this.b * 20, '%)'];
        this.element.style.backgroundColor = array.join('');
    },
    onMouseMove: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400});
    },
    onMouseOver: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400, b: 0});
    },
    onMouseOut: function(event) {
        event = event || window.event;
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    }
};

Event.observe(window, 'load', function() {
    new ColorfulBlock('element');
});

上の例を実行

Source 02 $ 関数を使った要素取得。document.getElementById より簡単。
var ColorfulBlock = Class.create();

ColorfulBlock.prototype = {
    element: null,
    r: 0, g: 0, b: 0,
    eventLinteners: {
        mouseover: 'onMouseOver', mouseout: 'onMouseOut', mousemove: 'onMouseMove'
    },
    initialize: function(element) {
        this.element = $(element);
        $H(this.eventLinteners).each(function(set) {
            Event.observe(this.element, set.key, this[set.value].bind(this));
        }.bind(this));
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    },
    setColor: function(color) {
        Object.extend(this, color);
        var array = ['rgb(', 100 - this.r * 20, '%,', 100 - this.g * 20, '%,', 100 - this.b * 20, '%)'];
        this.element.style.backgroundColor = array.join('');
    },
    onMouseMove: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400});
    },
    onMouseOver: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400, b: 0});
    },
    onMouseOut: function(event) {
        event = event || window.event;
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    }
};

Event.observe(window, 'load', function() {
    new ColorfulBlock('element');
});

上の例を実行
さらに、 $ 関数を使うことによって、コンストラクタに与えられる引数が文字列でも要素オブジェクトでも動作するようになる。

Section 03 Function.prototype.bindAsEventListener

Source 01 通常のインスタンスメソッドによるイベントリスナ( Section 02 の Source 02 と同じ)
var ColorfulBlock = Class.create();

ColorfulBlock.prototype = {
    element: null,
    r: 0, g: 0, b: 0,
    eventLinteners: {
        mouseover: 'onMouseOver', mouseout: 'onMouseOut', mousemove: 'onMouseMove'
    },
    initialize: function(element) {
        this.element = $(element);
        $H(this.eventLinteners).each(function(set) {
            Event.observe(this.element, set.key, this[set.value].bind(this));
        }.bind(this));
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    },
    setColor: function(color) {
        Object.extend(this, color);
        var array = ['rgb(', 100 - this.r * 20, '%,', 100 - this.g * 20, '%,', 100 - this.b * 20, '%)'];
        this.element.style.backgroundColor = array.join('');
    },
    onMouseMove: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400});
    },
    onMouseOver: function(event) {
        event = event || window.event;
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400, b: 0});
    },
    onMouseOut: function(event) {
        event = event || window.event;
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    }
};

Event.observe(window, 'load', function() {
    new ColorfulBlock('element');
});

上の例を実行

Source 02 Function.prototype.bindAsEventListener を使ったイベントリスナ
var ColorfulBlock = Class.create();

ColorfulBlock.prototype = {
    element: null,
    r: 0, g: 0, b: 0,
    eventLinteners: {
        mouseover: 'onMouseOver', mouseout: 'onMouseOut', mousemove: 'onMouseMove'
    },
    initialize: function(element) {
        this.element = $(element);
        $H(this.eventLinteners).each(function(set) {
            Event.observe(this.element, set.key, this[set.value].bindAsEventListener(this));
        }.bind(this));
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    },
    setColor: function(color) {
        Object.extend(this, color);
        var array = ['rgb(', 100 - this.r * 20, '%,', 100 - this.g * 20, '%,', 100 - this.b * 20, '%)'];
        this.element.style.backgroundColor = array.join('');
    },
    onMouseMove: function(event) {
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400});
    },
    onMouseOver: function(event) {
        this.setColor({r: (event.offsetX || event.layerX || 0) / 400, g: (event.offsetY || event.layerY || 0) / 400, b: 0});
    },
    onMouseOut: function(event) {
        this.setColor({r: 0.5, g: 0.5, b: 0.5});
    }
};

Event.observe(window, 'load', function() {
    new ColorfulBlock('element');
});

上の例を実行