ColdFusion Builder 3 with AngularJSを使ってMobileネイティブアプリを体験してみる

2014.05.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Adobe ColdFusion 11が2014年4月30日に正式リリースされ、同時にAdobe ColdFusion Bulider 3もパワーアップしてリリースされたようです。目玉はモバイルアプリ開発がサポートされたこととHTMLからPDFへの変換がパワーアップしたようです。そこで今まで手をつけていなかったネイティブアプリの領域を体験してみたので、その体験してみたことをまとめてみました。やったことは基本的にColdFusion Developer Centerに掲載されています。。。

環境の準備

  1. MacOS X 10.8.5
  2. ColdFusion 11 デベロッパー版(トライアル版はこちらから)
  3. ColdFusion Builder 3(トライアル版はこちらから)
  4. jquery
  5. jquery.mobile
  6. AngularJS

※ColdFusion Builder 3をインストールする際にColdFusion 11も同時にインストールすることができるので、そのオプションで入れた方はColdFusion 11 デベロッパー版は不要だと思われますが当方では確認してません。

署名キーの準備

ColdFusion Builder 3を使ってビルドする際に必要となるAndroid,iOS用の署名キーを用意します。Androidの場合は自己証明書を用意してそれを使ってアプリに署名すればいいだけなのでいいのですが、iOSの場合はiOS Developer Programに登録しないといけないので、プロビジョニングファイルの作成方法等についてはここでは割愛します。

cd /usr/bin
keytool -genkey -v -keystore [appname].keystore -alias [alias] -keyalg rsa -keysize 2048 -validity 10000

[appname][alias]の部分は適宜変更してください。

上記のコマンドを実行すると質問が表示されますので、指示に従って入力します。日本語が化けてしまう場合はターミナルの環境設定を一時的に変更すれば解決します。

文字エンコーディングのところをUnicode(UTF-8)から日本語(Mac OS)に変えます

cfb_1

keystoreの作成が完了したら文字エンコーディングのところをUnicode(UTF-8)に戻しておきます。

ColdFusion Builder 3の設定

まだユーザ登録してない場合はhttps://build.phonegap.comでユーザ登録できます

※無料だとPrivateアプリ1つ、Publicアプリ無制限で利用できます。Buildする際に必要になるので先に登録をすませておきます。

次にモバイルプロジェクトをインポートします。

利用させて頂いた元ソース(Original)はCreating CFMobile Application using AngularJSにあります。

cfb_4

ダウンロードしたzipファイルを展開したフォルダを指定します。(一応webroot配下に置きました)

cfb_6

元ソースでは日付と金額と備考及び画像添付を入れて登録するとDivタグにレコードを追加して表示されるものになっていますがこのデモでは日付と金額と出発駅と到着駅及び画像添付を入れるようにちょっと変更してみました。(下記はindex.cfmファイルです)

<!DOCTYPE html>
<html lang="ja" ng-app="CFAngularMobileSample">
	<head>
		<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<meta charset="utf-8">
		<link rel="stylesheet" href="css/jquery.mobile-1.4.2.min.css" ></link>
		
		<script src="js/jquery-2.1.0.min.js" ></script>
		<script src="js/index_app.js"></script>
		<script src="js/jquery.mobile-1.4.2.min.js" ></script>
		<script src="js/angular.min.js" ></script>
		<script src="js/angular_app.js"></script>
		<style >
			table th {
				text-align:left;
			}
		</style>
	</head>
	
	<body >
		<!--- メインページ --->
		<div data-role="page" id="mainPage">
			<div data-role="header" data-position="fixed" >
				<h2 id="toc-3">電車代備忘録</h2>
				<div style="padding-left:10px">
					<button class="ui-btn ui-btn-inline" id="addBtn">追加</button>
					<button class="ui-btn ui-btn-inline" id="deleteAllBtn">全件消去</button>
				</div>
			</div>
			<div class="ui-content">
				<div ng-controller="ListItemsCtrl" id="angularDiv1">
					<table width="100%" ng-if="lists.length > 0">
						<tr>
							<th>日付</th>
							<th>金額</th>
							<th>出発駅</th>
							<th>到着駅</th>
							<th></th>
						</tr>
						<tr ng-repeat="list in lists" >
							<td>{{dateToStr(list.expenseDate)}}</td>
							<td>{{list.amount | number}}</td>
							<td>{{list.stStation}}</td>
							<td>{{list.edStation}}</td>
							<td>
								<span ng-if="list.receiptPath.length > 0">
									<a href="{{list.receiptPath}}" class="imageReceipt">receipt</a>									
								</span>
								<span ng-if="list.receiptPath.length == 0"></span>
							</td>
						</tr>
					</table>
					<div ng-if="lists.length == 0">
						レコードがありません
					</div>
				</div>
			</div>
			<div data-role="footer" data-position="fixed" >
				<h5>Created with CFBuilder 3 and AngularJS</h5>
			</div>
		</div>
		
		<!--- アイテム追加のダイアログボックス表示 --->
		<div data-role="page" id="addDlg" >
			<div data-role="header">
				<h2 id="toc-4">レコード追加</h2>
			</div>
			<div class="ui-content" ng-controller="addExpDlgCtrl" id="addDlgContent">
      			<table width="100%">
      				<tr>
      					<td>日付:</td>
      					<td><input type="date" id="dateTxt" ng-model="date"></td>
      				</tr>
      				<tr>
      					<td>金額:</td>
      					<td><input type="text" id="amtTxt" ng-model="amount"></td>
      				</tr>
      				<tr>
      					<td>出発駅:</td>
      					<td><input type="text" id="stTxt" ng-model="stStation"></td>
      				</tr>
      				<tr>
      					<td>到着駅:</td>
      					<td><input type="text" id="edTxt" ng-model="edStation"></td>
      				</tr>
      				<tr>
      					<td colspan="2"><button class="ui-btn ui-btn-inline" id="attachRcptBtn">レシート添付</button></td>
      				</tr>
      				<tr>
      					<td colspan="2"><img id="receiptImg" width="50" ng-src="{{imagePath}}" ng-show="imagePath.length > 0"></img></td>
      				</tr>
      			</table>
			</div>
			<div data-role="footer">
				<button class="ui-btn ui-btn-inline" id="dlgOKBtn1">登録</button>
				<button class="ui-btn ui-btn-inline" id="dlgCancel1">キャンセル</button>
			</div>
		</div>
		
		<!--- レシートのリンククリック時に画像を表示させる --->
		<div data-role="dialog" id="receiptDlg" style="overflow:scroll">
			<div data-role="header">
				<h2 id="toc-5">レシート</h2>
				<div>
					<button class="ui-btn ui-btn-inline" id="receiptFitBtn">フィットさせて表示</button>
					<button class="ui-btn ui-btn-inline" id="receiptFullBtn">フルサイズで表示</button>
				</div>
			</div>
			<div class="ui-content" style="overflow:scroll" ng-controller="displayReceiptCtrl">
				<img id="receiptImgLarge" ng-src="{{imagePath}}">
			</div>
			<div data-role="footer">
			</div>
		</div>
	</body>
</html>

<!--- デバイスAPIの利用宣言 --->
<cfclientsettings enabledeviceapi="true" >

<cfclient>
	<cfinclude template="index_include.cfm" >
</cfclient>

次にAngularJSのコントローラを定義します。(下記はangular_app.jsファイルです)

CFAngularMobileSampleApp = angular.module("CFAngularMobileSample",[]);

CFAngularMobileSampleApp.controller(
	"ListItemsCtrl", 
	[
		"$scope",
		function ($scope) {
			$scope.lists = [];
			$scope.addList = function (list)
			{
				$scope.lists.push(list);
			}
			
			$scope.dateToStr = function (dateNum)
			{
				var tmpDate = new Date(dateNum);
				return dateFormat(tmpDate,"yyyy/mm/dd"); //this is cfclient function
			}
		}
	]
).controller (
	"addExpDlgCtrl",
	[
		"$scope",
		function ($scope) {
			$scope.resetExpense = function ()
			{
				$scope.amount = 0;
				$scope.stStation = "";
				$scope.edStation = "";
				$scope.date = new Date(); //デフォルト表示
				$scope.imagePath = "";
			}
			
			$scope.resetExpense();
		}
	]
).controller (
	"displayReceiptCtrl",
	[
		"$scope",
		function ($scope) 
		{
			$scope.resetData = function()
			{
				$scope.imagePath = "";
			}
			$scope.resetData();			
		}
	]
)

あとはボタンクリック時の処理をindex_app.jsに記述して、saveList()などのfunctionを呼び出し、index_include.cfm内に<cffunction name="saveList">〜〜〜</cffunction>として処理を記述しておきます。あとはcffunction内からcfcを呼び出してデータベースの更新処理を行います。下記がindex_include.cfmを一部抜粋したソースコードです

<cfclient>
	<!--- レシートの画像を保存するフォルダ名 --->
	<cfset variables.appFolderName = "CFMobileSample">

	<cftry>
		<!--- Create an instance of ListManager.cfc and get all expenses from it --->
		<cfset expMgr = new cfc.ListManager()>
		<cfset lists = expMgr.getExpenses()>
		<cfscript>
			angular.element("##angularDiv1").scope().$apply(function($scope){
				$scope.lists = lists;
			});
		</cfscript>
	
		<cfcatch type="any" name="e">
			<cfset alert(e.message)>
		</cfcatch>
	</cftry>

	<cffunction name="saveList">
		<cfscript>
			var scope = angular.element("##addDlgContent").scope();
			
			var dateStr = scope.date;
			var amtStr = trim(scope.amount);
			
			if (dateStr == "" || amtStr == "")
			{
				alert("Date and amount are required");
				return;
			}
			
			if (!isNumeric(amtStr))
			{
				alert("Invalid amount");
				return;
			}
			
			var amt = Number(amtStr);
			var tmpDate = new Date(dateStr);
			var stStation = trim(scope.stStation);
			var edStation = trim(scope.edStation);
			
			var receiptPath = "";
			
			if (isDefined("_tmpImagePath"))
				receiptPath = _tmpImagePath;
			
			var expVO = new cfc.ListVO(tmpDate.getTime(),amt,stStation,edStation,receiptPath);
			var expAdded = false;
			
			try
			{ 
				expMgr.addList(expVO);
				expAdded = true;
			} 
			catch (any e)
			{
				alert("Error : " + e.message);
				return;
			}
		</cfscript>
		
		<cfset $("##addDlg").dialog("close") >
		
		<cfif expAdded eq true>
			<cfscript>
				angular.element("##angularDiv1").scope().$apply(function($scope){
					$scope.addList(expVO);
				});
			</cfscript>
		</cfif>
	
	</cffunction>
</cfclient>

DB更新している部分を一部抜粋(下記はListManager.cfcファイルです。)

component client="true"   
{
	this.dsn = "sample_db";

	function addList (expVO)
	{
		queryExecute(
			"insert into trainfare (expense_date,amount,stStation,edStation,receipt_path) values(?,?,?,?,?)",
			[expVO.expenseDate,expVO.amount,expVO.stStation,expVO.edStation,expVO.receiptPath],
			{"datasource":this.dsn}
		);
		
		//get auto generated id
		queryExecute(
			"select max(id) maxId from trainfare",
			[],
			{"datasource":this.dsn, "name":"rs"}
		);
		
		expVO.id = rs.maxId[1];
	}
}

ビルドしてみる

ソースの変更が完了したので、実際にビルドをしてみます。

ウィンドウ/設定で設定ウィンドウを開きます。

  1. PhoneGap Build サービスへのログイン情報を入力します
  2. iOSキーを登録します。(設定しない場合はipaファイルが作成されないだけで特に問題ありません)
  3. Androidキーを登録します。(こちらは署名キーの準備のところで作った自己証明書を指定します)

cfb_8

次にプロジェクトのプロパティを設定します。

ColdFusion サーバーの設定でサーバーを指定します。

※ColdFusion Builder 3と一緒にCF11を入れた場合はdefaultLocalになっているかと思います。

cfb_9

ColdFusion モバイルプロジェクトを設定します。パッケージビルドする際にはApplication.cfmのチェックは外しましょう。

cfb_10

最後にリソースの選択の右隣にあるPhoneGapタブをクリックしてアプリケーションで利用するデバイスネイティブ機能にチェックを付けます。これに最初気づかないで何回がビルドしてアプリがエラーになりました。。。

このアプリではFileCameraにチェックを付けます。

cfb_11

PhoneGap Buildサービスにログインした直後の画面

cfb_12

それではプロジェクトのところで右クリックしてPhoneGap Buildを生成を選択します。

cfb_13

ビルドを実行するとビルドステータスのところがPendingに変わり、暫くするとダウンロードに変わります

cfb_14

再度PhoneGap Buildサービスの画面を見てみると下記のようにアプリケーションが出来上がってることが確認できます。

cfb_15

デフォルトではPrivateアプリケーションとなっていますので、一般公開したい場合にはPublicにする必要があります。私は無料版ユーザなのでプライベートアプリは1つしかもてません。この状態で2つ目のアプリケーションをビルドしてもプライベートアプリが既に1つあるので何も変わらないです。複数作成する場合は先に一般公開にするかアプリそのものを削除しましょう。

ダウンロード&インストールして実行してみる

ようやくパッケージ化ができたので、実際にダウンロードして実機で動作させてみました。

Android4.xで動作させたスクリーンショット

Android版立ち上げ直後 Android版データ登録後
初期画面 サンプルデータ登録後画面
iOS版立ち上げ直後 iOS版データ登録後
IMG_0201 IMG_0202

どちらも無事動作しました。

ただ、、、iOS版の場合で動作確認する際にはPhoneGap Build サービス経由でインストールしないとインストールできませんでしたので、試される方はその点ご注意下さい。
※PhoneGap Build サービス経由からインストールする為にPrivateアプリになっているのを一旦Publicアプリにしました。下の画像はプライベートアプリの状態です。

cfb_15

設定のところをクリックして Public Application にチェックをつけて保存すると再ビルドが走ります。

cfb_16

下記の様に再ビルド時にエラーが出た場合はキーのロックを一度解除してあげます。(パスワードはkeystore作成時のものです)解除後に再ビルドをクリックするとapkファイルが出来上がってます。

cfb_17

最終的には下記の様になればOKです。

cfb_18

まとめ

cfclientタグ内のCFMLコードは全てJavascript,HTMLに置き換えられて各モバイル端末向けにパッケージ化されます。この辺りはすべてビルドとやるだけでColdFusion Builder 3が全部自動でやってくれるので非常に楽でした。
またColdFusionサーバはこのサンプルアプリをJavascript、HTMLに変換するだけのエンジンとなっています。当然バックエンドとしてColdFusionを使うこともできますが、まずはMobileアプリを体験するという位置づけの検証なので、機会があったらサーバと通信するあたりも検証してみたいと思います。

試してみようという方々のご参考になればと思います。