Ext JS 4でのクラス定義

ということで概観をつかむためにThe Class System - Sencha Docs - Ext JS 4.0を読んだノート。
というか妥協した和訳。でも(゚ε゚)キニシナイ!!

0. 目次

    1. 概説
    2. 命名規約
    3. ハンズオン
    4. エラーハンドリングとデバッギング

1. 概説

Ext JS 4 :
300以上のクラス、20万人以上のデベロッパ
以下のような共通のコードアーキテクチャを提供しようとしている

  • 使いやすく、学びやすい
  • 迅速な開発、簡単なデバッグ、苦痛のないデプロイ
  • よく組織化され、拡張性がありメンテナンスしやすい


JavaScript :
クラスレス、プロトタイプ指向
→ 柔軟性が最大の特徴
→ 実装がばらけやすい
→ コストが予測不可能 && 理解、保守、再利用がしにくい(統一された構造がなければ)


一方、
クラスベース :

  • OOPで最もポピュラー
  • 強い型付け、カプセル化、標準的なコーディング規約 → 時間がたっても予測可能で、拡張性があり、スケーラブル
  • JavaScriptのような動的な機能はない


Ext JS 4では両者の短所を隠し、長所のみを得ることができる。

2. 命名規約

命名規約に従うことでコードを読みやすい状態で保つことができる

1) クラス
  • クラス名は英数字のみ。専門用語でない限り数字は非推奨。
MyCompany.useful_util.Debug_Toolbar  // だめ
MyCompany.util.Base64  // ok
  • トップレベルの名前空間とクラス名 : UpperCamelCase
  • 頭文字語 : UpperCamelCase
  • その他 : 全て小文字
MyCompany.form.action.Autoload
Ext.data.JsonProxy  // JSONProxyとしない
MyCompany.util.HtmlParser  // HTMLParserとしない
MyCompany.server.Http  // HTTPとしない

※ Senchaが配布したもの以外はトップレベルの名前空間にExtを使わない。

2) ソースファイル

クラス名はファイルパスに紐付けされる
→ 1ファイルに1クラス

Ext.util.Observable  // @path/to/src/Ext/util/Observable.js
Ext.form.action.Submit  // @path/to/src/Ext/form/action/Submit.js
MyCompany.chart.axis.Numeric  // @path/to/src/MyCompany/chart/axis/Numeric.js

// (/path/to/src = そのアプリケーションでクラスを配置しているディレクトリ)

全てのクラスは1つのディレクトリ下に置き、適切な名前空間に分ける

3) メソッドと変数

英数字のみのlowerCamelCase。専門用語でない限り数字は非推奨。頭文字語も同様。

encodingUsingMd5();
getHtml();  // getHTML()としない
getJsonResponse();  // getJSONResponse()としない
parseXmlContent();  //parseXMLContent()としない

var isGoodName;
var base64Encoder;
var xmlReader;
var httpSearver;
4) プロパティ

lowerCamelCase。
ただし定数は全て大文字。

Ext.MessageBox.YES = "YES"
Ext.MessageBox.NO = "No"
MyCompany.alien.Math.PI = "4.13"

3. ハンズオン

1. 宣言

1.1) 今までの方法
今まで : クラスの生成 = Ext.extend

var MyWindow = Ext.extend(Object, { ... });

他のクラスを継承していることは分かりやすい
が、直接継承でない場合、configurationやらstaticsやらmixinをうまいことするAPIがなかった(todo)
(Other than direct inheritance, however, we didn't have a fluent API for other aspects of class creation, such as configuration, statics and mixins.)


また下のようなときは

My.cool.Window = Ext.extend(Ext.Window, { ... });
  1. My.coolが存在していること
  2. Ext.Windowが存在し、読み込まれていること

に注意しなければならない。
1 : Ext.namespace(Ext.ns)で解決 ← 毎回書くのは面倒
(※ Ext.namespace = オブジェクトやプロパティがまだ存在していなければ再帰的に作成するメソッド)

Ext.ns('My.cool');
My.cool.Window = Ext.extend(Ext.Window, { ... });

2 : 簡単ではない
← Ext.Windowの直接/間接の継承において依存関係がある(再帰的に)
→ 以前のver.では一部の機能を使うだけでもライブラリ全体をインクルード


1.2) 新しい方法
Ext JS 4ではExt.defineで全て解決

Ext.define(className, members, onClassCreated);

className : クラス名
members : クラスメンバのコレクション
onClassCreated : オプションの関数。依存関係を解決し、クラスが完全に生成された時にコールバックされる

Ext.define('My.sample.Person', {
    name: 'Unknown',

    constructor: function(name) {
        if (name) {
            this.name = name;
        }

        return this;
    },

    eat: function(foodType) {
        alert(this.name + " is eating: " + foodType);

        return this;
    }
});

var aaron = Ext.create('My.sample.Person', 'Aaron');
    aaron.eat("Salad");  //alert("Aaron is eating: Salad");

注意
newよりもExt.create()でインスタンス生成を推奨
← 動的ローディングが出来る

2. Configuration

Ext JS 4ではconfigプロパティを使うことができる

  • configurationは他のクラスメンバーから完全に隠蔽される
  • configプロパティのgetter, setterはプロトタイプに自動生成される
  • applyメソッドも生成される。自動生成されたsetterは値をセットする前にapplyメソッドを呼び出す → 値をセットする前に何か処理をしたい場合applyメソッドをオーバーライドする(下のapplyTitle, applyBottomBar)
    Ext.define('My.own.Window', {
    /** @readonly */
    isWindow: true,

    config: {
        title: 'Title Here',

        bottomBar: {
            enabled: true,
            height: 50,
            resizable: false
        }
    },

    constructor: function(config) {
        this.initConfig(config);

        return this;
    },

    applyTitle: function(title) {
        if (!Ext.isString(title) || title.length === 0) {
            alert('Error: Title must be a valid non-empty string');
        }
        else {
            return title;
        }
    },

    applyBottomBar: function(bottomBar) {
        if (bottomBar && bottomBar.enabled) {
            if (!this.bottomBar) {
                return Ext.create('My.own.WindowBottomBar', bottomBar);
            }
            else {
                this.bottomBar.setConfig(bottomBar);
            }
        }
    }
});

使用例

var myWindow = Ext.create('My.own.Window', {
    title: 'Hello World',
    bottomBar: {
        height: 60
    }
});

alert(myWindow.getTitle());  // "Hello World"

myWindow.setTitle('Something New');

alert(myWindow.getTitle());  // "Something New"

myWindow.setTitle(null);  // "Error: Title must be a valid non-empty string"

myWindow.setBottomBar({ height: 100 });  // height -> 100
3. Statics

Staticなメンバはstaticsコンフィグを用いて定義する

Ext.define('Computer', {
    statics: {
        instanceCount: 0,
        factory: function(brand) {
            // 'this' in static methods refer to the class itself
            return new this({brand: brand});
        }
    },

    config: {
        brand: null
    },

    constructor: function(config) {
        this.initConfig(config);

        // インスタンスのselfプロパティは自身のクラスを指す
        this.self.instanceCount ++;

        return this;
    }
});

var dellComputer = Computer.factory('Dell');
var appleComputer = Computer.factory('Mac');

alert(appleComputer.getBrand());  // "Mac"  (自動生成されたgetterを使用)

alert(Computer.instanceCount);  // "2"

4. エラーハンドリングとデバッギング

Ext JS 4にはデバッグとエラーハンドリングに役立つ機能がいくつかある

  • Ext.getDisplayName()は任意のメソッドのディスプレイ名?を取得する → 例外生成時にクラス名やメソッド名を記述できて便利
throw new Error('['+ Ext.getDisplayName(arguments.callee) +'] ここにメッセージを記述');
  • Ext.define()で定義したメソッドやクラスで例外が生成された場合、コールスタックでそのメソッド名やクラス名を確認することができる(WebKitベースのブラウザ(Chrome, Safari)のみ)

Chromeの例