zorioの日記 このページをアンテナに追加 RSSフィード

2008-06-09

[] IE6のメモリリークを華麗に回避

ajaxian経由:http://ajaxian.com/archives/is-finally-the-answer-to-all-ie6-memory-leak-issues

http://www.hedgerwow.com/360/dhtml/ie6_memory_leak_fix/

try ... finallyを使って循環参照を削除する方法。

windowのunloadとかに頼らないで済む所がかっこいい。

function createButton() 
{
	var obj = document.createElement("button");
	obj.innerHTML = "click me";
	obj.onclick = function() {
		//handle onclick
	}
	//this helps to fix the memory leak issue
	try {
		return obj;
	} finally {
		obj = null;
	}
}
var dButton = document.getElementsById("d1").appendChild(createButton());

finallyが無いと、

  1. buttonのonclickプロパティにセットされたFunctionオブジェクト
  2. Functionオブジェクトの[[Scope]]プロパティにセットされたActivationオブジェクト
  3. Activationオブジェクトのobjプロパティにセットされたbutton

って感じで循環参照するのだが、finallyでobjにnullがセットされるので循環参照が回避される。

これで全部のケースを回避できるのかどうか良く分からないが、TopHatenarの終わりのほうに引っかかっている事に気付いたので久しぶりに書いてみた。

2008-03-11

[] privateとpublicのスコープが指定出来るクラス定義

オレ様用メモ。

こんな感じで、クロージャを使ってprivateな変数関数を定義する。

publicなメンバ同士がアクセスするために、selfって変数も用意しておく。

privateなメンバへの参照にはレシーバは省略するのに、publicなメンバの参照時にはselfってレシーバが必要というのは若干不自然だが、そこはご愛嬌という事でよろしく。

追記:

id:AWAWAさんからご指摘をいただきました。

privateなインスタンス変数はウソでした。この挙動だとクラス変数ですね。

コメント欄参照。

さらに追記:

publicな変数も同じですね。

this.publicVariableってアクセスすればちゃんとインスタンス別の変数にアクセスできるけど、self.publicVariableでアクセスすると、ClassA.prototypeオブジェクトのpublicVariableプロパティに明示的にアクセスしてしまうので、同様にクラス変数として振舞ってしまいます。

var ClassA = function() {
  // constructor
};
ClassA.prototype = (function(){
    //private variables
    var privateVar1 = 0;
    var privateVar2 = 'hoge';

    //private methods
    function privateMethod1()
    {
    }

    function privateMethod2()
    {
    }

    var self = {
        //public variables
        publicVar1: 1,
        publicVar2: 'aaa',

        //public methods
        publicMethod1: function() {
            return privateMethod1(privateVar1);
        }, 
        publicMethod2: function() {
            return self.publicVar2 + privateVar2;
        }
    };
    return self;
})();

2008-03-10

[][] Drag & Drop(1)

silverlightとかにも目移りしつつ、またExt JSをいじる。

D&Dについては、チュートリアルがいっぱいある。

http://extjs.com/learn/Tutorial:Custom_Drag_and_Drop_Part_1

http://extjs.com/learn/Tutorial:Custom_Drag_and_Drop_Part_2

http://extjs.com/learn/Tutorial:Custom_Drag_and_Drop_Part_3

http://extjs.com/learn/Tutorial:Advanced_Custom_Drag_and_Drop_Part_1

http://extjs.com/learn/Tutorial:Advanced_Custom_Drag_and_Drop_Part_2

なんだけど、読んでもあんまり納得感が得られなかった。

クラスの構成

正確にはAPIドキュメント参照って事で。

名前説明
DragDropDrag用、Drop用の両方の基底クラス。
DDDrag用。Dragすると、Dragした要素そのものがマウスに沿って移動するんだと思う
DDProxyDrag用。Dragすると枠だけのElementがカーソルに沿って移動。マウスを放した時にドラッグした要素がマウス位置に移動する
DDTargetDrop用

以下は今回は扱わない。また今度。

DragSource, DropTarget, DragZone, DropZone

ソースを見る

APIリファレンスもいっぱい書いてあるし、チュートリアルもある。

が、仕組みを理解するには、source/dd/DDCore.jsを見るのが一番早いと思う。

特にDragDropMgrがキモ。

マウスのイベントの処理や、DragDrop(とその派生クラス)の各種イベントの発火はここで制御している。

特に、handleMouseDown, handleMouseUp,handleMouseMove,fireEventの辺りを見ておくと良い。

で、マウスの動作に合わせて、Drag元のDragDropオブジェクトに対して以下のイベントが発生する。

名前説明
startDragDrag開始時
onDragOverDrop先にマウスが入った時
onDragOutDrop先からマウスが出た時
onDragDropDropした時
endDragDrag終了時

この辺はリファレンスのExt.dd.DragDropの所に書いてある。

ちなみに、Drag元をDragSource、Drop先をDropTargetとした場合は、これらの上にさらに高機能のイベントを発生させているので、ちょっと話が違う。

それについては次回。

サンプル

Ext.Elementには、自分自身を対象にしたD&D用のメソッドとして、initDD、initDDProxy, initDDTargetが用意されている。

せっかくなので、これを使ってみた。

サンプル:http://zoriolab.info/sample/extjs/dd/proxy_and_target.html

ソースはこれだけ。

Ext.onReady(function(){
    var group = 'ddGroup';
    var drags = Ext.getDom('dd1-ct').childNodes;
    for (var i = 0, im = drags.length; i < im; i++) {
        if (drags[i].nodeType === 1) {
            Ext.get(drags[i].id).initDDProxy(group, {}, {
                isTarget: false,//ドロップ出来ないようにする
                onDragDrop: function(e, id){//Drop時のイベント
                    var target = Ext.get(id);
                    if (!target.last()) {//空のときだけ移動
                        target.appendChild(Ext.get(this.getEl()));
                    }
                },
                //DDProxyがドロップ位置に移動させてしまうのを抑制
                endDrag: Ext.emptyFn
            });
        }
    }
    var drops = Ext.getDom('dd2-ct').childNodes;
    for (var i = 0, im = drops.length; i < im; i++) {
        if (drops[i].nodeType === 1) {
            var d = Ext.get(drops[i].id);
            if (d) {
                d.initDDTarget(group);
            }
        }
    }
});

ElementのinitDDProxyを呼べばDrag出来るようになるし、initDDTargetではDrop出来るようになる。

2008-02-16

[][] クラス定義とサブクラス化

詳しくは、公式のチュートリアルを参照。

http://extjs.com/learn/Tutorial:Extending_Ext_Class

http://extjs.com/learn/Tutorial:Extending_Ext2_Class

実例

こんな風に書く。

//親クラスのコンストラクタ
var SuperClass = function(){
    Ext.log('SuperClass is newly instantiated');
}
//親クラスのコンストラクタ以外の定義
SuperClass.prototype = {
    a: function(){
        Ext.log('method a in Superclass is called.')
    }
};
//子クラスのコンストラクタ
var SubClass = function(){
    SubClass.superclass.constructor.call(this);//親クラスのコンストラクタの呼び出し
    Ext.log('SubClass is newly instantiated');
}
//子クラスのコンストラクタ以外の定義
Ext.extend(SubClass, SuperClass, {//子クラスで再定義する分をObjectで渡す
    a: function(){
        SubClass.superclass.a.call(this);//親クラスのメソッドの呼び出し
        Ext.log('method a in Subclass is called.')
    },
    b: function(){
        Ext.log('method b in Subclass is called.')
    }
});

サンプル:http://zoriolab.info/sample/extjs/classdef/normalclass.html

中を調べてみる

上のソースだが、superclassとconstructorっていう属性が気になる。意味は分かるけど。

調べたら、Ext.extendの呼び出しで定義されるようになっていた。

以下、source/core/Ext.jsより抜粋したソース

extend : function(){
    var io = function(o){
        for(var m in o){
            this[m] = o[m];
        }
    };
    return function(sb, sp, overrides){
        //後述
        if(typeof sp == 'object'){
            overrides = sp;
            sp = sb;
            sb = function(){sp.apply(this, arguments);};
        }
        var F = function(){}, sbp, spp = sp.prototype;
        //SubClassからprototype chainでSuperClassのメソッドが呼び出せるようにする
        F.prototype = spp;
        sbp = sb.prototype = new F();
        //SubClass.prototype.constructorでSubClassを呼び出せるようにする
        sbp.constructor=sb;
        sb.superclass=spp;//SubClass.superclassでSuperClassのprototypeを参照できるようにする
        //SuperClass.prototype.constructorが未定義の場合
        if(spp.constructor == Object.prototype.constructor){
            //Subclass.superclass.constructorで親クラスのFunctionオブジェクトを参照できるようにする
            spp.constructor=sp;
        }
        sb.override = function(o){
            Ext.override(sb, o);
        };
        sbp.override = io;
        Ext.override(sb, overrides);
        return sb;
    };
}(),

Ext.extendの最初に

if(typeof sp == 'object'){
    overrides = sp;
    sp = sb;
    sb = function(){sp.apply(this, arguments);};
}

っていう部分がある。

コンストラクタ部分は親クラスと同じで良いなら、以下のようにSubClass部分の定義を省略出来る。

var SubClass = Ext.extend(SuperClass, {
    a: function(){
        SubClass.superclass.a.call();
        Ext.log('method a in Subclass is called.')
    },
    b: function(){
        Ext.log('method b in Subclass is called.')
    }
});

20080311追記:

もう一個味わい深いのが、この部分。

    var F = function(){}, sbp, spp = sp.prototype;
    F.prototype = spp;
    sbp = sb.prototype = new F();

考えなしに書くと、

sbp = sb.prototype = new sp();

って感じで、与えられたSuperClassの関数定義をそのまま使ってしまう。

このnew sp()が何も副作用を持たないなら良いんだけど、対応したDOM elementが存在する前提だったり、インスタンスの個数を別途管理してたり、困る場合だって色々ある。

そこで、prototypeだけ安全に繋ぐ為に、副作用の無い空の関数を用意している。

賢い。

2008-02-12

[][] Ext.dataの辺りについて

引き続きExt JSについて色々サンプルソースを書いてみるテスト。

Ext.dataのパッケージ辺りを重点的に調べてみる。

概要

出てくるのはこのくらい。

クラス名説明
Recordデータの組。RDBで言うとTableのrow
StoreRecordの集合。RDBで言うとTable
DataProxyデータを外部から取得するクラス
DataReaderProxyからデータを受け取ってRecordに変換するクラス

残りはサブクラス。いくつかのサンプルを書いてみる。

共通部分

各サンプルの相違点はDataProxyとDataReaderを使っていかにStoreを作るかの所だけ。

Storeから画面を作成する部分は共通化した。

Ext.onReady(function(){
    //各Record毎にリンクを作成する
    var tpl = new Ext.XTemplate(
       '<ul>', 
       '<tpl for=".">', '<li class="item-wrap"><a href="{link}">{title}</a></li>', '</tpl>',
       '</ul>');
    //Recordはtitleとlinkを持つ
    var record = [{ name: 'title' }, { name: 'link' }];
    //Storeの取得
    var store = getStore(record);
    if (store.getCount()==0) store.load({});
    //Windowの中に表示する
    new Ext.Window({
        width: 300,
        height: 200,
        autoScroll: true,
        title: 'DataView Sample',
        layout: 'border',
        items: [{
            xtype: 'dataview',
            store: store,
            tpl: tpl,
            itemSelector: 'item-wrap',
            autoHeight: true,
            emptyText: 'No images to display',
            region: 'center',
            margins: '10 10 10 10'
        }]
    }).show();
})

ブラウザで完結するような場合

サーバとデータ交換したりせず、メモリ上の配列からRecordを作ってStoreに格納するような例。

MemoryProxyとArrayReaderを使う。

サンプル:http://zoriolab.info/sample/extjs/data/simple.html

function getStore(record){
    var data = [['name1', 'http://www.example.com/'], ['name2', 'http://www.example.com/']];
    var proxy = new Ext.data.MemoryProxy(data);
    var reader = new Ext.data.ArrayReader({}, record);
    var store = new Ext.data.Store({
        proxy: proxy,
        reader: reader
    });
    return store;
}

別ドメインからJSON形式でデータを取得する

前も一度似たような例を挙げたが、別ドメインのYahoo!PipesからJSON形式でデータを取得する例。

ScriptTagProxyとJsonReaderを使う。

サンプル:http://zoriolab.info/sample/extjs/data/jsonreader.html

function getStore(record){
    var proxy = new Ext.data.ScriptTagProxy({
        callbackParam: '_callback',
        url: 'http://pipes.yahoo.com/pipes/pipe.run'
    });
    var reader = new Ext.data.JsonReader({
        root: 'value.items'
    }, record);
    var store = new Ext.data.Store({
        proxy: proxy,
        reader: reader,
        baseParams: {
            SearchIndex: 'Blended',
            AssociateTag: 'dhatennejpzor-22',
            keyword: '田村ゆかり',
            _id: '682640003b289aada399b03f0a23cce3',
            _render: 'json'
        }
    });
    return store;
}

同じドメインからXMLデータを取得する

同一ドメインの場合はHttpProxyを使う。Xml形式のデータはXmlReaderで読み込む。

サンプル:http://zoriolab.info/sample/extjs/data/xmlreader.html

function getStore(record){
    var proxy = new Ext.data.HttpProxy({
        url: './piperesult.xml' //Yahoo Pipesが返してきたRSSを保存した静的なファイル
    });
    var reader = new Ext.data.XmlReader({
        record: 'channel/item'
    }, record);
    var store = new Ext.data.Store({
        proxy: proxy,
        reader: reader
    });
    return store;
}

ちなみに、XmlReaderのソースをちょっと眺めてみたが、xhr.responseXmlを参照していたりして、HttpProxyを使ってxhr経由でアクセスされることを前提としてるようで、ScriptTagProxyとの組み合わせでは使えないっぽい。


分かりやすさを重視して、あえて明示的にインスタンス化するとこんな感じ。

SimpleStoreを使う場合

ArrayReaderを使うようなケースでは、Storeを継承したSimpleStoreを使うと、ちょっと簡潔になる。

サンプル:http://zoriolab.info/sample/extjs/data/simplestore.html

function getStore(record){
    var data = [['name1', 'http://www.example.com/'], ['name2', 'http://www.example.com/']];
    var store = new Ext.data.SimpleStore({
        data: data,
        fields: record
    });
    return store;
}

JsonStoreを使う場合

同じく、JsonStoreを使うとJsonReaderのインスタンス化を省略できる。

また、StoreのInitialConfigにurlパラメータをセットするとHttpProxyの作成を省略できる。

サンプル:http://zoriolab.info/sample/extjs/data/jsonstore.html

function getStore(record){
    var store = new Ext.data.JsonStore({
        fields: record,
        root: 'value.items',
        url: './piperesult.json' //Yahoo Pipesが返してきたJSONを保存した静的なファイル
    });
    return store;
}

DataSourceの扱いについてはこれくらい。

次はDrag&Dropに行こうか、Gridをもう少し調べようか。