Titanium
Alloy
Jade

iOS/Android両対応時でNavigationWindowを使いつつAlloy Data Bindingも使う(Jade版とxp.ui)

More than 1 year has passed since last update.

Titanium/Alloyでマルチプラットフォーム向けにアプリを作る際、以下のような問題があります。

Windowの中身を二度書くのがいやなのと、中身を共通化してrequireしようとするとdataCollectionがちゃんと動かないんですよねえ。

- Titanium - NavigationWindowでもAlloyのCollectionを使いたいしAndroidとコードを共有したい - Qiita

iPhoneでナビゲーションバーを使うにはNavigationWindowが必要ですが、Androidにはないコンポーネントなので、ビューを作るのに一工夫必要になってしまうのですが、そうするとデータバインディングと相性が悪いらしいという悩ましい問題です。

上の記事では、コントローラ内でiOSかAndroidを判断して、Androidの場合はWindowをそのまま使い、iOSの場合はNavigationWindowを生成してWindowを挿入するような形で対応する方法を紹介されていました。

正攻法で行くなら、冗長になってしまいますが、上記記事のコメントにあるようにiOSとAndroidそれぞれにWindowを書く形、

<Alloy>
  <Collection src="entry" />
  <NavigatonWindow platfrom="ios">
  </NavigationWindow>
  <Window platfrom="android">
  </Window>
</Alloy>

もしくは app/views/ 以下にプラットフォームごとのフォルダを用意して、それぞれのViewを実装する形でしょうか。

データバインディングを使わないのであれば、 require を使ってわかりやすく共通化できるのですが。

参考: iOSとAndroidのナビゲーションサンプル | garicchi.com

そこで、別のアプローチとして、私が普段Titanium開発をする際に使っているViewをJadeで書く(&TSSをStylusで書く)方式での解決方法を紹介したいと思います。

参考: Titanium Alloy with StylusでFontを設定 - Umi Uyuraのブログ

XP.UI.js(Jadeを使わない方法)

と言いつつ、先にJadeを使わない場合の解決のひとつを紹介。

Titanium/Alloy向けの便利 FokkeZB/UTiL に含まれている xp.ui.js を使う方法です。

詳細は リポジトリのドキュメント に詳しく書かれていますが、iOS以外のプラットフォーム用にダミーのNavigationWindowを提供しているので、以下のように module="xp.ui" を設定することで、iOS/Android両方に対応できます。

<Alloy>
  <NavigationWindow module="xp.ui">
    <Window>
      <Label>Hello World</Label>
    </Window>
  </NavigationWindow>
</Alloy>

先日Alloy Modelの勉強していたときに使ってみたので、データバインディングと組み合わせも問題なさそうです。

Alloy Modelの学習 - Umi Uyuraのブログ

Jade版

Jadeでviewを拡張する方法としては、 include extend mixin などがありますが、このうち mixin を使うことにしました。

Mixins - Jade

mixin は、再利用可能なコードブロックを作って、任意の箇所で何度も生成できる機能です。関数のように引数を取ることもできます。

それを利用して、まずは mixin で基本となる画面をWindowベースで作りました。引数でプラットフォームの指定を受け取るようにしたので、生成される <Window> タグは各プラットフォーム専用になります。

それを、iOS用のNavigationWindow以下と、Android用にAlloyタグ配下の2箇所で呼び出すようにしました。

app/views/index.jade

mixin mainWindow(platform)
  Window.container(platform=platform, title="Alloy Project Demo", onOpen="doOpen", onClose="doClose")
    ListView(onItemclick="clickItem")
      ListSection(dataCollection="member")
        ListItem(title="{name}", itemId="{id}")/

Alloy
  Collection(src="member")/
  NavigationWindow(platform="ios")
    +mainWindow("ios")
  +mainWindow("android")

これをJadeコンパイルすると、以下のようなView XMLが生成されます。

app/views/index.xml

<Alloy>
  <Collection src="member"/>
  <NavigationWindow platform="ios">
    <Window platform="ios" title="Alloy Project Demo" onOpen="doOpen" onClose="doClose" class="container">
      <ListView onItemclick="clickItem">
        <ListSection dataCollection="member">
          <ListItem title="{name}" itemId="{id}"/>
        </ListSection>
      </ListView>
    </Window>
  </NavigationWindow>
  <Window platform="android" title="Alloy Project Demo" onOpen="doOpen" onClose="doClose" class="container">
    <ListView onItemclick="clickItem">
      <ListSection dataCollection="member">
        <ListItem title="{name}" itemId="{id}"/>
      </ListSection>
    </ListView>
  </Window>
</Alloy>

冒頭に正攻法として紹介した、iOSとAndroidそれぞれに向けて二重にWindow以下を書いた形で出力されました。当然データバインディングも問題なく動きます。

まとめ

Jadeを使うことで、Windowの中身を1箇所で書くことができ、煩雑さは多少減らせるのではないかな、と思います。

Jade版でのサンプルをGitHubにあげてあります。

umi-uyura/AlloyModelWithJadeNavWin

ちなみに、サンプルはJadeおよびStylusから生成したView XMLとTSSも含んでいるので、Jade & Stylusがない環境の人でも、alloy.jmkを削除して (appc) ti build を実行してもらえば動きます。