2012年3月21日水曜日

シングルコンテキストでの設計方法(6)

Titanium Mobileを使ったアプリ開発において、シングルコンテキストで作る際の工夫の続き、第6回です。作り方をひととおり説明しましたが、これから主流となりそうな「require」の話を取り上げないわけにはいきません。ということで、「require」を使って作る話です。

 

まず最初に、これまでの経緯を簡単に。Titanium Mobileを使うのが初めてだったので、いろいろ調べました。「Ti.include」を使う方法と、「require」を使う方法の2種類があって、将来的には「require」へ移行するという話のようでした。どちらで作るか悩んだのですが、「Ti.include」を使うほうが簡単そうに見えたのと、「Ti.include」も経験したほうが良いだろうとの考えもあり、最初は「Ti.include」で作って、必要なら後から「require」に書き換えればよいかなと判断しました。

実は、作成した業務アプリ、最初はTitanium Desktpoで作成したものです(こちらも、Titanium Desktpoを使うのが初めてでした)。それをiPadに移植しようと、Titanium Mobile用に修正しながら作りました。予想したよりも短期間でアプリは出来上がり、依頼主に見せたところ、以前見せたデスクトップ版よりも、iPad版を速攻で気に入ってしまいました。短期間だけ試してもらった後、依頼主の要望を反映し、機能を少し追加しました。アプリが非常に安定していたため、すぐに本番で使い始めることに。本番で使い始めたら、安定している業務アプリを修正する気にはなりません。こうして「Ti.include」のまま、アプリは本番で使い続けられています。運が良かったのか、トラブルはまったく出てません。

こんな感じで、「require」に書き換える機会を失ってしまいました。とりあえず「Ti.include」で作るかという、私の直感的な(?)判断が招いた結果ですね。「Ti.include」も分かりやすい形で作れるから、なかなか好きなのですが。でも将来は使わないと言われたら、「require」へ移行できるように試しておかないとダメでしょうね。ちゃんと調べて、作り方も決めてあります。

 

いよいよ、ここからが今回の本題です。「require」を使う場合も、今回の3つのレベル分けは同じです。レベル2とレベル3に1文字ずつ割り当てる考え方も、そのまま生かします。実際にJavaScriptをどう作るのか、順番に見ていきましょう。なお、作り方が何種類から選べるため、私が気に入った作り方を選びました。選んだ理由も、少し加えながら解説します。

まずは、レベル1のJavaScriptです。Ti.include方式では全体を即時関数で作りましたが、これは最初にインクルードされたときに実行して、グローバル変数bbに登録するためでした。require方式でも、グローバル変数に登録しますが、登録方法が異なります。そのため、全体を即時関数にする必要はなくなりました。また、このJavaScriptの即時関数の外で、グローバル変数bbを宣言していましたが、これは別な場所に移動しました。JavaScript自体は、次のような形です。

// base.js (レベル1)
exports.createWinF = function(_title){
    return Ti.UI.createWindow({
        title:_title,
        backgroundColor:'#fff'
    });
}
exports.createLblF = function(_text, _fontSize, _textAlign, _height, _width, _top, _left){
    return Ti.UI.createLabel({
        text:_text,
        font:{fontSize:_fontSize},
        textAlign:_textAlign,
        height:_height,
        width:_width,
        top:_top,
        left:_left 
    });
}
exports.createBtnF = function(_title, _fontSize, _height, _width, _top, _left){
    return Ti.UI.createButton({
        title:_title,
        font:{fontSize:_fontSize},
        height:_height,
        width:_width,
        top:_top,
        left:_left
    });
}

Ti.include方式で「dd.createWinF = 」としていたものが、require方式では「exports.createWinF = 」と変えてあります。require方式で外から呼ばれる関数なので、「exports」が必須となります。それを利用する方法ですが、次のような形にしました。

// app.js (メインのJavaScript)
var bb = {}; // グローバル変数
var bbb = {};          // レベル1専用のグローバル変数
bbb = require("base"); // レベル1を使えるようにする
(function() {
    // メインのウィンドウを開く
    bb.win = bbb.createWinF('prod_edit');
    var lblTitle = bbb.createLblF(bb.win, '主メニュー', 36, 'center', 70, 'auto', 20, null);
    var lblMsg = bbb.createLblF(bb.win, ' ', 18, 'left', 30, 500, 700, 200);
    var btnOpenPe = bbb.createBtnF(bb.win, '商品編集', 24, 40, 200, 100, 32);
    btnOpenPe.addEventListener('click', openPeWinF);
    ...
})();

一番の違いは、レベル1専用のグローバル変数を用意したことです。「bb.func_name = 」という形で追加できなくなり、全体を直接「bb」に入れる「bb = require("base");」という形になります。これだと追加するのではなく、置換えになります。一番最初なので置換えでも、後の追加まで正常に動くのでしょうが、気分的にイヤと感じました。そこで別なグローバル変数「bbb」を用意して、そこに入れた(「require」した)わけです。

当然ですが、レベル1をグローバル変数に入れなくても使えます。その場合は、レベル1を使うJavaScript全部で、先頭に「bbb = require("base");」と書かなければなりません。これが面倒なので、グローバル変数に入れたわけです。面倒だと思わなければ、毎回書いても良いでしょうね。

 

続いて、レベル2のJavaScriptです。これも全体を即時関数で囲む必要はありません。また、先頭で入れ物「bb.c」を用意することも不要です。その結果、JavaScriptは次のようになります。

// c_customer.js (レベル2)
// 継続保持させるデータ用の変数
var customar = [];
var custType = [];

// 外部からデータにアクセスするための関数
exports.initDataF = function(id) { ... } // 初期処理
exports.getNameF = function(id) { ... }
exports.addCustomerF = function(name, ... ) { ... }
exports.checkCstmIdF = function(id) { ... }
// 外部からアクセスされない関数
function calcYearF(date) { ... }
// 上記2種類の関数は、別々に分けるのではなく、関係の深いものを近付ける形で混在させる

値を継続的に保持したい変数を宣言して、後に関数を続けます。外部からアクセスする関数だけ、「exports.func_name = 」という形にして、残りは普通の関数として書きます。

Ti.include方式とは違い、使う側で準備が必要です。複数のJavaScriptから使われるため、毎回宣言するのも面倒と思い、メイン処理でグローバル変数に登録してしまいました。

// app.js (メインのJavaScript)
var bbb = {};          // レベル1専用のグローバル変数
bbb = require("base"); // レベル1を使えるようにする
var bb = {};                   // グローバル変数
bb.c = require("c_customer"); // レベル2を使えるようにする
bb.c.initDataF();              // まずは初期化
bb.p = require("p_products");  // 別なレベル2を使えるようにする
bb.p.initDataF();              // 同じく初期化
(function() {
    // メインのウィンドウを開く
    bb.win = bbb.createWinF('prod_edit');
    var lblTitle = bbb.createLblF(bb.win, '主メニュー', 36, 'center', 70, 'auto', 20, null);
    var lblMsg = bbb.createLblF(bb.win, ' ', 18, 'left', 30, 500, 700, 200);
    var btnOpenPe = bbb.createBtnF(bb.win, '商品編集', 24, 40, 200, 100, 32);
    btnOpenPe.addEventListener('click', openPeWinF);
    ...
})();

レベル2の数だけ、グローバル変数「bb」に登録します。これでレベル3のJavaScriptでは、宣言なしで使えるようになりました。使い方もTi.include方式と同じで、「bb.c.func_name()」という形になります。

 

続いて、最後のレベル3です。レベル3は関数が外部から呼ばれない点で、レベル1やレベル2とは異なります。レベル3は、レベル1やレベル2を利用する側だからですね。「Ti.include」で呼ばれるため全体を即時関数にしていましたが、require方式では「exports」で外から呼ばれる方式に変わります。全体が即時関数の形に似せて、全体を1つの「exports」関数に作りました。即時関数と同様に、全部の変数が関数の内側に入る点が好きだからです。これとは違って、普通に関数を分割して書き、オープンする関数だけ外から見えるようにしても構いません。

// pe_prod_edit.js (レベル3)
exports.openWin = function() {
    // pe用の入れ物を用意する(ウィンドウ用)
    bb.pe = {};

    // 画面に表示するためのUI部品
    bb.pe.win = bbb.createWinF('prod_edit');
    var lblMnTitle = cc.createLblF(bb.pe.win, '商品情報編集', 36, 'center', 70, 'auto', 20, null);
    var lblMsg = bb.createLblF(bb.pe.win, ' ', 18, 'left', 30, 500, 700, 200);
    var btnClose = bb.createBtnF(bb.pe.win, '終了', 24, 40, 200, 100, 32);
    btnSave.addEventListener('click', closeWinF);

    // データを内部的に保持するための変数
    var prodScrn = [];
    var idxProd = null;
    var userId = null;

    // イベントを実行したり、データを加工する関数
    function openWinF() { ... }
    function saveDataF() { ... }

    // ウィンドウを閉じる処理
    function closeWinF(){
        bb.pe.win.close(); // ウィンドウを閉じて、
        bb.pe = null;      // UI部品をウィンドウごと解放する
    }

    // 最後に、生成済みのウィンドウを開く
    bb.pe.win.open();
}

関数は呼ばれないので考慮する必要はないのですが、ウィンドウを開く処理や閉じる処理をどのように配置するのか、決めなければなりません。この例の作り方では、JavaScript全体がオープン用関数になっていて、そのなかでウィンドウの生成なども行います。同時に、ウィンドウ上のUI部品へイベント処理を加え、その処理から呼ばれる関数も用意します。それらが終わった最後に、生成したウィンドウを開いています。

ウィンドウを終了するときのために、画面を終了するボタンとして付けてありますが、「戻る」ボタンであったり、何かの処理が終わった直後に閉じる場合もあるでしょう。とにかく、ウィンドウを閉じる機能が必要です。ウィンドウを閉じる関数では、ウィンドウを閉じた後に入れ物「bb.pe」を空にして、作成したウィンドウごと消えるようにしています。メモリー解放のためです。

このように作った画面用JavaScriptは、次のような形で呼び出します。

menu.js (呼び出しボタンの付いた画面のJavaScript)
(function() {
    ...
    var btnOpenPe = bbb.createBtnF(bb.xx.win, '商品編集', 24, 40, 200, 100, 32);
    btnOpenPe.addEventListener('click', openPeWinF);
    ...
    // 商品編集の画面を呼び出す関数
    function openPeWinF(){
        require("pe_prod_edit").openWin();
    }
})();

ウィンドウを呼び出すための関数では、JavaScript「pe_prod_edit.js」を「require」して、ウィンドウを作って開く関数を呼び出します。分かりやすいように別関数としましたが、たった1行のJavaScriptです。

 

以上のように、require方式でも、3つのレベルに分けた設計方法が使えますし、Ti.include方式と同じメリットがあります。世の流れとしてrequire方式に動いている以上、これから作るならrequire方式でしょう。Ti.include方式は、意外にスッキリした構造で好きなんですけどね。というわけで、新しく作る場合は、require方式で作ってください。私も次のアプリを、require方式で作り進めています。将来を約束されている方式で作っておかないと、やっぱり多少は不安がありますからね。

今回で、設計方法の話はいったん終りです。私は凄く気に入っている方法ですが、感じ方は人それぞれだと思います。部分的にでも気に入った点があったら、そこだけでも利用してください。全体の設計以外にも、iPadアプリを作っていて気付いた点がありますから、ぼちぼち書き進めます。

0 件のコメント:

コメントを投稿