LoginSignup
20
19

More than 5 years have passed since last update.

Titanium Mobile + Alloy における Backbone.js の扱われ方

Posted at

前置き

Titanium Mobile + Alloy では、ModelをBackbone.jsを使って書きます。
などと言われているようだが、実際にはBackbone.jsそのままというわけではないようだ。
そんなわけで、ちょっと調べてみた。

ちなみにここでは、Todoリストアプリっぽいものを書いていると思ってください。

モデルの書き方

Titanium Mobile + Alloy の新しいプロジェクトを作成すると、appフォルダ以下に、controllers, models, views など、いかにもMVCなんです的なフォルダ構成になっている。

スクリーンショット 0025-07-30 22.58.24.png

この中の、modelsフォルダに「なんちゃら.js」というファイル名でモデルを書いてやると、
Titanium Mobileが自動的にBackbone.jsを使ってくれる。
強制である。
使わないという選択肢は(おそらく)存在しない。

ためしに、ネットでサンプルを読みつつ、Todoリストを扱うモデルを書いてみた。

app/models/todo.js
exports.definition = {
    config: {
        "columns": {
            "description": "TEXT",
            "limitTime": "TEXT",
            "done": "INTEGER"
        },
        "defaults": {
            "description": "-",
            "limitTime": "",
            "done": "false"
        },
        "adapter": {
            "type": "sql",
            "collection_name": "todos",
            "idAttribute": "id"
        }
    },
    extendModel: function(Model) {      
        _.extend(Model.prototype, {
            // Extend Backbone.Model
        });
        return Model;
    },
    extendCollection: function(Collection) {        
        _.extend(Collection.prototype, {
            // extended functions and properties go here
        });
        return Collection;
    }
}

とりあえず、Backbone.jsとUnderscore.jsは、とくにrequire()とかしなくても自動的に読み込んでくれる。

モデルはどのように変換されているのか?

ここで疑問。
exports.definition って何?
Backbone.jsにはないよね?

気になったので、どんな風に変換されるのか追ってみた。
iPhoneシミュレータ用に"Debug"あるいは"Run"すると、

(ホームディレクトリ)/Library/Application Support/iPhone Simulator/(バージョン)/Applications/

ディレクトリ以下に、数字とアルファベットが羅列された名前のディレクトリが出来る。

スクリーンショット 0025-07-30 23.13.01.png

その中のどこかのフォルダに(この場合は)Todo.appというファイルが見つかる。
で、Todo.appを右クリック->「パッケージの内容を表示」で、中身を見てみる。

スクリーンショット 0025-07-30 23.16.48.png

パッケージの中で重要なのは、alloyフォルダっぽい。
alloyフォルダ以下には、controllers, models フォルダがある。

スクリーンショット 0025-07-30 23.20.56.png

どうやら、alloy/modelsフォルダにあるTodo.jsというのが、上で書いたtodo.jsが変換されたもののようだ。

その中身は以下。

alloy/models/Todo.js
exports.definition = {
    config: {
        columns: {
            description: "TEXT",
            limitTime: "TEXT",
            done: "INTEGER"
        },
        defaults: {
            description: "-",
            limitTime: "-",
            done: "false"
        },
        adapter: {
            type: "sql",
            collection_name: "todos",
            idAttribute: "id"
        }
    },
    extendModel: function(Model) {
        _.extend(Model.prototype, {});
        return Model;
    },
    extendCollection: function(Collection) {
        _.extend(Collection.prototype, {});
        return Collection;
    }
};

var Alloy = require("alloy"), _ = require("alloy/underscore")._, model, collection;
model = Alloy.M("todo", exports.definition, []);
collection = Alloy.C("todo", exports.definition, model);
exports.Model = model;
exports.Collection = collection;

最後の5行以外は書いたままなので、最後だけ見ていく。

alloy/models/Todo.js
var Alloy = require("alloy"), _ = require("alloy/underscore")._, model, collection;
model = Alloy.M("todo", exports.definition, []);
collection = Alloy.C("todo", exports.definition, model);
exports.Model = model;
exports.Collection = collection;

1行目は、alloyやunderscoreを読み込んでいるだけなので無視。
4〜5行目は、exportsしてるだけなので無視。
2〜3行目で、exports.definitionで定義したものを、Alloy.M()やらAlloy.C()という名前の関数に渡しているのが怪しい。

Alloyなんちゃらは、パッケージを開いたルートディレクトリにあるalloy.jsで定義されている。
M()とC()の定義だけ抜き出してみる。

どうでもいいが、MはModel、CはCollectionの頭文字と思われる。

alloy.js
exports.M = function(name, modelDesc, migrations) {
    var config = modelDesc.config;
    var type = (config.adapter ? config.adapter.type : null) || "localDefault";
    "localDefault" === type && (type = "sql");
    var adapter = require("alloy/sync/" + type);
    var extendObj = {
        defaults: config.defaults,
        sync: function(method, model, opts) {
            var config = model.config || {};
            var type = (config.adapter ? config.adapter.type : null) || "localDefault";
            "localDefault" === type && (type = "sql");
            require("alloy/sync/" + type).sync(method, model, opts);
        }
    };
    var extendClass = {};
    migrations && (extendClass.migrations = migrations);
    _.isFunction(adapter.beforeModelCreate) && (config = adapter.beforeModelCreate(config, name) || config);
    var Model = Backbone.Model.extend(extendObj, extendClass);
    Model.prototype.config = config;
    _.isFunction(modelDesc.extendModel) && (Model = modelDesc.extendModel(Model) || Model);
    _.isFunction(adapter.afterModelCreate) && adapter.afterModelCreate(Model, name);
    return Model;
};

exports.C = function(name, modelDesc, model) {
    var extendObj = {
        model: model,
        sync: function(method, model, opts) {
            var config = model.config || {};
            var type = (config.adapter ? config.adapter.type : null) || "localDefault";
            "localDefault" === type && (type = "sql");
            require("alloy/sync/" + type).sync(method, model, opts);
        }
    };
    var Collection = Backbone.Collection.extend(extendObj);
    var config = Collection.prototype.config = model.prototype.config;
    var type = (config.adapter ? config.adapter.type : null) || "localDefault";
    var adapter = require("alloy/sync/" + type);
    _.isFunction(adapter.afterCollectionCreate) && adapter.afterCollectionCreate(Collection);
    _.isFunction(modelDesc.extendCollection) && (Collection = modelDesc.extendCollection(Collection) || Collection);
    return Collection;
};

さきに、M()のほうを見ていく。
2番目の引数modelDescに、exports.definitionが入っているのを頭に入れておく。
そうすると、2〜3行目の

    var config = modelDesc.config;
    var type = (config.adapter ? config.adapter.type : null) || "localDefault";

は、変数configにexports.definition.configを代入し、
変数typeにexports.definition.adapterを元に求めた値を代入していることになる。

次。
alloy.jsの7行目あたり。

    var extendObj = {
        defaults: config.defaults,

として、extendObj.defaultsにconfig.defaults(=exports.definition.config.defaults)を代入している。
結構バラバラにされているようです < exports.definition

その後も紆余曲折を経ながら最終的に、18〜19行目

    var Model = Backbone.Model.extend(extendObj, extendClass);
    Model.prototype.config = config;

で、Backbone.Modelに結びつけられている。
結局、extendObj.defautlsに結びつけられたexports.definition.defaultsが
Backbone.Model.extend()に渡され、また、
exports.definition.configが、Model.prototype.configに代入されている。
さらに、extendObj.sync()の中では、config.adapterから求めたtypeが使用されている(typeはシリアライズのとき"sql"を使うのか、"localStorage"を使うのか、みたいなことを表している)。

ということになる。

でこれから何がわかるかというと…Backbone.jsを勉強するべき、ということになる(?)

C()のほうは以下同様で略。

おまけ

それだけでは何なので、ちょっとだけ。

config.defaults

exports.definitionで定義されたconfig.defaultsが、Backbone.Model.Extend()に渡されるので、

exports.definition = {
    config: {
        "defaults": {
            "description": "-",
            "limitTime": "",
            "done": "false"
        }
    }
}

と書いておけば、

var Model = Backbone.Model.extend({
    "defaults": {
        "description": "-",
        "limitTime": "",
        "done": "false"
    }
});

と書いたのと同じことになる。

extendModel

extendModelプロパティに、以下のような記述をすることでモデルを拡張出来る。

exports.definition = {
    extendModel: function(Model) {      
        _.extend(Model.prototype, {
            // Implement the validate method
            validate: function (attrs) {
                for (var key in attrs) {
                    var value = attrs[key];
                    if (key === "description") {
                        if (value.length <= 0) {
                            return "Error: No description!";
                        }   
                    }   
                }
            }
        });
        return Model;
    }
}

extendCollection

Backbone.jsを使う場合、Backbone.Modelを拡張したオブジェクトを、Backbone.Collectionを拡張したオブジェクトにいくつもいれておくことがある(ようだ)が、
exports.definitionに、extendModelとextendCollectionを同時に記述することで、
ModelとCollectionを一度に記述できる(ような気がする…)。

すみません。Backbone.jsは勉強不足なので…。

exports.definition = {
    extendModel: function(Model) {      
        _.extend(Model.prototype, {
            // こっちはBackbone.Model関係
        });
    },
    extendCollection: function(Collection) {        
        _.extend(Collection.prototype, {
            // こっちはBackbone.Collection関係
        });
        return Collection;
    }
}
20
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
19