Apache Cordova(PhoneGap)開発におけるTips

#はじめに
Cordova(PhoneGap)を使って、[smartFX Virtual Trade](http://smartfx.minkabu.jp/s/virtual-trade.html)をネイティブアプリとしてリリースしました。
前回[Cordova(PhoneGap)を使ったネイティブアプリ事例](http://qiita.com/takeshy/items/18a902289c05de044d50)は概要でしたが、今回は実装のTipsを紹介します。

# js、cssを動的に差し替える

WebViewを使う利点として、バイナリを更新せずにアプリの動作を変更できるという点があります。
ただ、単にリンク先のjsの内容を書き換えても以前のファイルのcacheを参照する可能性があったり、逆に毎回最新のjsを取得するのはパフォーマンスが落ちるという問題があります。
これを回避するために、最新のJS、CSSのパスが記述されている小さなjsを毎回取得し、そこに記載されているjs,cssをロードするという方法を考えました。

```javascript
//var JS_PATH="http://192.168.1.77:3000/assets/demo.js"
//var CSS_PATH="http://192.168.1.77:3000/assets/demo_application.css"
var JS_PATH=null
var CSS_PATH=null

document.addEventListener("deviceready", function(){
  loadResources();
});

function loadResources(){
  if(!JS_PATH || !CSS_PATH){
    var d = document
    var s = "script"
    var fjs=d.getElementsByTagName(s)[0]
    var js=d.createElement(s)
    js.src = "https://hostname/current.js?t=" + Math.floor(new Date().getTime()/10000)
    fjs.parentNode.insertBefore(js,fjs)
  }else{
    loadCSS(CSS_PATH);
    loadJS(JS_PATH);
  }
}
function loadCSS(path){
  var link = document.createElement('link');  
  link.href = path;
  link.type = 'text/css';  
  link.rel = 'stylesheet';  
  var head = document.getElementsByTagName('head');  
  head.item(0).appendChild(link);  
}
function loadJS(path){
  var d = document
  var s = "script"
  var fjs=d.getElementsByTagName(s)[0]
  var js=d.createElement(s);
  js.src = path;
  fjs.parentNode.insertBefore(js,fjs)
  //開始処理内でjsを読み込まれたかをtimerをつかってcheckして
  //読み込みが終わっていたら処理を行う
  開始処理();
}
```

```javascript:current.js
CSS_PATH="http://hostname/assets/demo_application-f19817820c9a6d9d9a05f52f352a80bd.css"
JS_PATH="http://hostname/assets/demo-2821df320a6a0d4b9197f873e5206f6f.js"
loadCSS(CSS_PATH);
loadJS(JS_PATH);
```

上記のcurrent.jsが最新のcssとJSのパスを呼び出すファイルになっています。
また、ローカルの開発時は予めCSS_PATHとJS_PATHを定義することで、current.jsを経由せずに読みこみます。
JS,CSSともひとつしか指定できませんが、Railsの場合は、config.assets.debug = falseの設定することで、dynamic loadingでもjsやcssがconcatされそれぞれひとつのファイルになるので問題ありません。

# ソーシャルログインを行う

Cordovaでは原則外部のWebサイトを読み込んでしまうと、戻れなくなるため、読み込んではいけません。
ただ、smartFXのログインはソーシャルログインのため、ログイン時はどうしてもTwitterなどの外部のサイトを読み込む必要があります。
そこでsmartFXでは、ログイン画面はInAppBrowserというアプリ内ブラウザ機能を使い、アプリとは別の画面としました。
強引にTwitterのログイン画面内のリンクからTwitter画面に飛ぶなどの想定外の動作をして戻れなくなっても、閉じるボタンでInAppBrowserを閉じれば、アプリに戻ってこれます。
また、InAppBrowser内のページがログイン完了画面に来たかを画面遷移ごとにチェックを行い、ログイン完了画面だった場合は自動的にcloseするように対応することで、外部サイトを使っても違和感なくログインを行えるようにしました。

```javascript
//ログインのリンクをclickした際のcallback
//clearcache=noでアプリのWebViewとセッション共有
loginProc = function(){
  var ref = window.open(host + '/demos/login', '_blank', 'location=yes,clearcache=no');
  ref.addEventListener("loadstop", function(e){
    if(e.url.match(/ログイン完了画面のPATH/)){
      ref.close();
    }
  });
}
```

# ネットワークが接続できない場合に対応する

起動時にネットワークが繋っていない環境だった場合、外部JS,CSSが読み込まれないため、真っ白な画面になります。
また、そのままではネットワークが繋がる場所に移っても読み込み直さないため、アプリを落とすまで真っ白なままになってしまいます。
そのためアプリの起動時に、ネットワークがつながっているかどうかを確認し、ネットワークが繋っていなかった場合は、繋った際に外部JSやCSSを読み込むイベントハンドラを登録し、接続されるまで"接続待ち"のダイアログを出すようにしました。

```javascript
document.addEventListener("deviceready", function(){
  if(navigator.connection.type == Connection.NONE){
    document.addEventListener("online", startLoadResources,false);
    ActivityIndicator.show("waiting for connection.");
  }else{
    loadResources();
  }
});

function startLoadResources(){
  ActivityIndicator.hide();
  loadResources();
}
```

# backgroundの場合は処理を行わないようにする

他のアプリに切り替えbackgroundになった際にAndroidの場合、デフォルトのままではJavaScriptの処理が動いています。
そのため、もしTimerを使ってAjaxのpollingを行っていた場合、バッテリの消費量が大変なことになります。
下記のように設定でBackgroundの場合は処理を行わないようにします。

```xml:config.xml
<preference name="keepRunning" value="false" />
```

#まとめ

Cordova(PhoneGap)特有の処理をTipsにあげました。
ご参考になれば。