40歳からのキャリアチェンジ

20代はエンジニア・PM、30代はWeb系エンジニア向けのキャリアアドバイザー。40代の今はフリーランスで開発含めて色々やってます。技術ネタとしてはRuby/RailsとJavaScript関連あたり

Alloy本格的にはじめました

現在、作業が比較的落ち着いてるのと、先々考えても、Alloyはひとまず抑えておいたほうがよいかと思って、やや食わず嫌い気味だったAlloyを最近本格的にいじりはじめました。

過去何度かトライはしていたのですが、自分が欲しいサンプルアプリ(まずは画面遷移系のもの)の情報が見つからなかったことも有り、本格的な利用は躊躇してたこともあり、似たような人がいるかもしれないと思い、ひとまず画面遷移系のアプリについて以下に簡単にまとめておこうと思います

プロジェクト作成までの流れ

自分の実行環境

Titanium の新規プロジェクト作成する

CLIベースで最近作業してるので、ひとまずその流れを以下に示します。

※気が向いたら、Titanium Studioでの流れもキャプチャ付きでまとめておきたい

ホームディレクトリ配下に、titaniumというディレクトリがある前提での説明になってますので、適宜読み替えていただければと思います。

ti create --name=alloysample1 --id=info.h5y1m141.alloysample1--platform=ios

上記実行すると、オプション設定間違ってるからか、ターゲットのOSを聞かれるので、その後に、任意のOS(ひとまずiOS)と入力する。しばらくすると

Project 'alloysample1' created successfully in 90ms

という感じでプロジェクト作成完了します

Alloyを利用するための手順

Titanium の新規プロジェクト作成完了したら、今度はAlloyが利用できるようにします。

~/titanium
% cd alloysample1
~/titanium/alloysample1
% alloy new
       .__  .__
_____  |  | |  |   ____ ___.__.
\__  \ |  | |  |  /  _ <   |  |
 / __ \|  |_|  |_(  <_> )___  |
(____  /____/____/\____// ____|
     \/                 \/
Alloy 1.3.1 by Appcelerator. The MVC app framework for Titanium.

[INFO] Deployed ti.alloy plugin to plugins/ti.alloy/plugin.py
[INFO] Deployed ti.alloy hook to plugins/ti.alloy/hooks/alloy.js
[INFO] Installed "ti.alloy" plugin to tiapp.xml
[INFO] Generated new project at: app

Alloyが利用できるようになったかと思いますが、念のためこの段階でbuildしてみます

ti build --platform ios

build完了すると、以下の様な画面が表示されるかと思います。

f:id:h5y1m141:20140407212021p:plain

Alloy導入後のプロジェクトのディレクトリ構造は以下のようになります。

├── LICENSE
├── README
├── Resources
│   ├── alloy
│   ├── app.js
│   └── iphone
├── app
│   ├── README
│   ├── alloy.js
│   ├── assets
│   ├── config.json
│   ├── controllers
│   ├── models
│   ├── styles
│   └── views
├── build
│   ├── alloy
│   ├── iphone
│   └── map
├── plugins
│   └── ti.alloy
└── tiapp.xml

JavaScriptではなくCoffeeScriptで書きたいので、設定ファイルを編集する

Titanium×Alloy×CoffeeScript×Jadeでいいんじゃーねの情報を参考に、~/titanium/alloysample1/app/alloy.jmk を以下内容で作成します

task("pre:compile", function(event,logger) {
  var wrench = require("wrench"),
      fs = require("fs"),
      jade = require("jade"),
      view_root = event.dir.views,
      path = require("path"),
      coffee = require("coffee-script");
  
  event.alloyConfig.xml = [];
  event.alloyConfig.coffee = [];
       
  wrench.readdirSyncRecursive(view_root).forEach(function(view) {
    if (view.match(/.jade$/)) {
      event.alloyConfig.xml.push(view.replace(/\.jade$/, ".xml"));
      fs.writeFileSync(
        path.join(view_root,view.replace(/\.jade$/, ".xml")),
        jade.compile(fs.readFileSync(path.join(view_root,view)).toString())(event));
    } 
  });
 
  wrench.readdirSyncRecursive(event.dir.home).forEach(function(target){
    if (target.match(/\.coffee$/)) {
      event.alloyConfig.coffee.push(target.replace(/\.coffee$/, ".js"));
      fs.writeFileSync(
        path.join(event.dir.home,target.replace(/\.coffee$/, ".js")),
        coffee.compile(fs.readFileSync(path.join(event.dir.home + "/" + target)).toString(), { bare: true }));
    }
  });
});
  
task("post:compile",function(event,logger){
  var fs = require("fs"),
      view_root = event.dir.views,
      path = require("path");
  
  event.alloyConfig.xml.forEach(function(view){
    if (!view.match(/index.xml/g)) {
      fs.unlinkSync(path.join(view_root, view));
    }
  });
  event.alloyConfig.coffee.forEach(function(target){
    fs.unlinkSync(event.dir.home + "/" + target);
  });  
});

app/controllers/index.coffee

$.index.open()
$.label.addEventListener 'click', (e) ->
  alert e.source.text

app/views/index.xml

<Alloy>
    <Window class="container">
        <Label id="label">Hello, World</Label>
    </Window>
</Alloy>

XMLでもいいのですが、上記でalloy.jmkにて、Jade利用できるようにしていたので、index.xmlではなく、index.jadeを以下のように準備しました

app/views/index.jade

Alloy
  Window.container
    Label#label Hello, World!!!!!!!

これをベースにして、画面遷移の方法を考えます

画面遷移系のサンプルアプリ

理解してしまうと簡単なのですが、そこに至るまでに意外と時間がかかったのも事実なのでここは少しじっくり解説していこうと思います。

作成するファイル&それぞれの対応関係

まず、現在作成中のプロジェクトのappディレクトリのcontrollers、styles、viewsにそれぞれ以下のようにファイルを作成します

  • controllers
    • index.coffee
    • newWindow.coffee
  • styles
    • index.tss
    • newWindow.tss
  • views
    • index.jade
    • index.xml
      • index.xmlがないとうまくコンパイルされないのでそれは残してあります
    • newWindow.jade

起動時の画面&画面遷移後の画面イメージと、それぞれ対応するファイル群の対応関係を絵にしてみました

f:id:h5y1m141:20140408063502p:plain

f:id:h5y1m141:20140408063432p:plain

キモとなるコードの解説

上記の対応関係の紫色の枠&線でつなげてる部分が画面遷移する上でキモとなる処理になるかと思います。

具体的には

  • 画面遷移先の処理と対応関係にある呼び出し先のコントローラーを 起動時に実行されるindex.coffeeに記述する
    • Alloy.createController('xx')としてる所。xxの方は処理を渡すコントローラーの名前の拡張子を取った名前(今回だとnewWindow)を指定する
    • 処理を渡した後に呼びだすメソッド名を記述する
  • 呼び出される側のコントローラーのファイルの newWindow.coffee
    • 処理を渡した後に呼びだされるメソッドを実装する。
    • 遷移後のWindowは、Window#newWindow.container という形でマークアップしており、id名であるnewWindowを指定してopen()にてWindowを開く

という感じです。

実際のコード

起動時の画面

index.coffee

$.mainWindow.open()

$.label.addEventListener 'click', (e) ->
  newWindow = Alloy.createController('newWindow')
  return newWindow.move()

index.jade

Alloy
  Window#mainWindow.container
    Label#label Hello, World!!!!!!!

index.tss

".container": {
    backgroundColor:"#fff"
},
"#label": {
    width: Ti.UI.SIZE,
    height: 40,
    color: "#000",
  top:50,
  left:10

} 

遷移後の画面

newWindow.coffee

exports.move = () ->
  alert "Moved!"
  return $.newWindow.open()

newWindow.jade

Alloy
  Window#newWindow.container
    Label#label This is a new Window

newWindow.tss

".container":{
    backgroundColor:"#fff"
}

"#label": {
    width: Ti.UI.SIZE,
    color: "#222",
  top:"50%"
} 

最後に最近参考にした情報のまとめ

Alloy関連の情報は結構色々ネット上にあるのですが、これまとまっていたら嬉しいと思う人が結構いそうなので、とりあえず以下に箇条書きでまとめておきました