#CommandBox+ColdBoxでCRUDアプリを作ってみる
普段仕事ではフレームワークをあまり使わない環境なんですが、モダンな開発環境が構築できそうなColdBoxを勉強してみることにしました。英語アレルギーの体に鞭打ちつつ(Google翻訳に頼りつつ)。
HMVCフレームワークについて右も左もわからないので、とりあえずユーザー一覧をCRUDするアプリを作ってみようと思います。
##ColdBoxってなに?
ColdBoxは、ColdFusion(CFML)用の規約ベースのHMVC開発フレームワークです。
ColdBox公式ドキュメントより引用
##まずCommandBoxをインストール
CommandBoxというCLIを導入します。
https://www.ortussolutions.com/products/commandbox
導入編→CommandBoxを使ってみる
##アプリのベースを作る
適当なフォルダを作って、その中にMyAppというアプリを作成します。
> mkdir study --cd
> install coldbox
> coldbox create app MyApp
> server start --rewritesEnable
「Welcome to ColdBox!」と書かれたアプリがブラウザで立ち上がったと思います。
##ハンドラーを作成
usersハンドラーにアクションindex,list,detail,add,edit,removeを作成します。
> coldbox create handler name="users" actions="index,list,detail,add,edit,remove"
ブラウザを更新すると、「Registered Event Handlers」にusersというリンクができていると思います。クリックしてもまだタイトルしか表示されません。これから作っていきます。
##モデルを作成
UserServiceサービスにメソッドgetAll,getUser,addUser,editUser,removeUserを作成します。
> coldbox create model name="UserService" methods="getAll,getUser,addUser,editUser,removeUser" persistence="singleton"
###UserServiceをハンドラーのプロパティに追加
component{
property name="userService" inject="UserService";
##コンストラクタにデータのモックを作成
おためしなので今回データベースは使いません。
UserService function init(){
variables.data = [
{ id=1, name="Taro Suzuki" },
{ id=2, name="Ichiro Tanaka" },
{ id=3, name="Hanako Yamada" }
];
return this;
}
##一覧画面を作成します
###メソッド getAll()を編集
データ全件を取得するメソッドを用意します。
function getAll(){
return variables.data;
}
###ハンドラーのindexアクションを編集
indexはlistに飛ばすだけにします。
function index( event, rc, prc ){
// list画面に飛ばします
setNextEvent( "users/list" );
}
###listアクションを編集
getAll()で取得した配列をrc構造体に格納します。使用するビューはusers/list
です。
function list( event, rc, prc ){
rc.aUsers = userService.getAll();
event.setView( "users/list" );
}
###ビュー list.cfmを編集
rcに格納されたユーザー一覧のデータを表示します。
event.buildLink(to="users.detail",queryString="id=#user.id#")
は/users/detail?id={id}
の意味で、detailアクションは後ほど作ります。
表の3列目が空ですが、後で作る削除ボタン用です。
<cfoutput>
<h1>users.list</h1>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>#html.nbs()#</th>
</tr>
</thead>
<tbody>
<cfloop array="#rc.aUsers#" index="user">
<tr>
<td>#html.href( href=event.buildLink(to="users.detail",queryString="id=#user.id#"), text=user.id, noBaseURL=true )#</td>
<td>#encodeForHTML(user.name)#</td>
<td></td>
</tr>
</cfloop>
</tbody>
</table>
</cfoutput>
###画面を見てみよう
まずhttp://127.0.0.1:{port}/
の下のほうにあるExecute
ボタンを押して、編集したUserServiceサービスを再読み込みします。この作業はUserServiceを書き換えるたびに行ってください。
##詳細画面を作成します
###detailアクションを編集
一覧画面の番号リンクをクリックするとdetailアクションにパラメータidが渡ってきますので、当該IDのユーザーデータを取得するコードを書きます。
function detail( event, rc, prc ){
rc.User = userService.getUser( event.getValue("id","") );
event.setView( "users/detail" );
}
###メソッド getUser()を編集
上で呼び出したgetUser()の中身を書きます。データをIDで検索し、当該ユーザーのデータを返します。
function getUser(id){
var n = arrayFind(variables.data, (x)=>{ return (x.id == id) ? true : false ; });
return variables.data[n];
}
###ビュー detail.cfmを編集
rc構造体に入れたユーザーデータを表示します。フォームのpost先はusers/edit
です。
<cfoutput>
<h1>users.detail</h1>
<form action="#event.buildLink(to="users.edit")#" method="POST">
<div class="form-group row">
<label for="staticID" class="col-sm-2 col-form-label">ID</label>
<div class="col-sm-10">
<input type="text" readonly class="form-control-plaintext" id="staticID" value="#encodeForHTMLAttribute(rc.User.id)#">
</div>
</div>
<div class="form-group row">
<label for="username" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="username" value="#encodeForHTMLAttribute(rc.User.name)#">
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<input type="submit" class="btn btn-primary float-right" value="submit">
</div>
</div>
</form>
</cfoutput>
list画面で番号リンクをクリックし、detail画面を確認します。当該ユーザーのIDとNameが表示されました。
##更新処理を作成します
###editアクションを編集
フォームに入力された値をeditUser()に渡します。エラーの場合はdetail画面に戻るようにしています。
function edit( event, rc, prc ){
rc.User.id = event.getValue("staticID","");
rc.User.name = event.getValue("username","");
if( userService.editUser( rc.User.id, rc.User.name ) ){
event.setView( "users/edit" );
}else{
event.setView( "users/detail" );
}
}
###メソッド editUser()を編集
上で呼び出したeditUser()の中身を書きます。データをIDで検索し、当該ユーザーのデータを書き換えます。
function editUser(id, name){
var n = arrayFind(variables.data, (x)=>{ return (x.id == id) ? true : false ; });
if( n != 0 ){
variables.data[n].name = name;
return true;
}else{
return false;
}
}
###ビュー edit.cfmを編集
更新完了の画面です。
<cfoutput>
<h1>users.edit</h1>
#html.meta( name="Refresh", content="3;URL=#event.buildLink(to="users.list")#", type="equiv" )#
<div class="alert alert-success" role="alert"><strong>success</strong> - Updated!</div>
</cfoutput>
##新規登録処理を作成します
###listアクションを編集
新規登録フォームを作る前に、nameフィールドの値を用意しておきます。
function list( event, rc, prc ){
rc.aUsers = userService.getAll();
rc.User.name = event.getValue("username",""); // 追加
event.setView( "users/list" );
}
###ビュー list.cfmを編集
一覧の下に新規登録フォームを追加します。フォームのpost先はusers/add
です。
<!--- tableの下にフォームを追加 --->
<form action="#event.buildLink(to="users.add")#" method="POST">
<div class="form-group row">
<label for="username" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="username" name="username" value="#encodeForHTMLAttribute(rc.User.name)#">
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<input type="submit" class="btn btn-primary float-right" value="add">
</div>
</div>
</form>
</cfoutput>
###addアクションを編集
フォームに入力された値をaddUser()に渡します。エラーの場合はlist画面に戻るようにしています。
function add( event, rc, prc ){
rc.User.name = event.getValue("username","");
if( userService.addUser( rc.User.name ) ){
event.setView( "users/add" );
}else{
event.setView( "users/list" );
}
}
###メソッド addUser()を編集
上で呼び出したaddUser()の中身を書きます。IDを採番し、ユーザーデータを追加します。
function addUser(name){
if(arrayLen(variables.data) == 0){
var id = 1;
}else{
var id = arrayLast(variables.data).id + 1;
}
arrayAppend(variables.data, { id=id, name=name })
return true;
}
###ビュー add.cfmを編集
登録完了の画面です。
<cfoutput>
<h1>users.add</h1>
#html.meta( name="Refresh", content="3;URL=#event.buildLink(to="users.list")#", type="equiv" )#
<div class="alert alert-success" role="alert"><strong>success</strong> - Added!</div>
</cfoutput>
##削除処理を作成します
###ビュー list.cfmを編集
空欄だった3列目に、削除ボタンを追加します。リンク先はusers/remove
で、パラメータidを渡します。
<!--- tbodyの3列目を以下のように変更 --->
<td>#html.href( href=event.buildLink(to="users.remove",queryString="id=#user.id#"), text="remove", noBaseURL=true, class="btn btn-danger btn-sm" )#</td>
###removeアクションを編集
パラメータのIDをremoveUser()に渡します。エラーの場合はlist画面に戻るようにしています。
function remove( event, rc, prc ){
if( userService.removeUser( event.getValue("id","") ) ){
event.setView( "users/remove" );
}else{
event.setView( "users/list" );
}
}
###メソッド removeUser()を編集
上で呼び出したremoveUser()の中身を書きます。当該IDのユーザーデータを削除します。
function removeUser(id){
var n = arrayFind(variables.data, (x)=>{ return (x.id == id) ? true : false ; });
if( n != 0 ){
arrayDeleteAt(variables.data, n);
return true;
}else{
return false;
}
}
###ビュー remove.cfmを編集
削除完了の画面です。
<cfoutput>
<h1>users.remove</h1>
#html.meta( name="Refresh", content="3;URL=#event.buildLink(to="users.list")#", type="equiv" )#
<div class="alert alert-success" role="alert"><strong>success</strong> - Removed!</div>
</cfoutput>
##日本語が文字化けする場合
コンポーネントの場合は
cfprocessingdirective(pageencoding="utf-8");
ビューやレイアウトの場合は
<cfprocessingdirective pageencoding="utf-8">
を書いておくと文字化けしません。
あと、layouts/Main.cfm
のhtmlタグのlang属性も"ja"に変えておくとよいでしょう。
##おわりに
エラー処理なんかは全く入れていませんが、CRUDの流れだけはなんとかつかめた感じです。
coldbox create
でモデルやハンドラーを作成するとTestBoxのモジュールも作成されますが、今回無視してしまいました。近いうちBDDについてもちゃんと勉強したいと思います。。。