SIerっぽいWEBアプリをAngularJS+JavaEEで組んでみたの3回目。これで終わりです。
今回はコード参照画面とページ検索です。
こんな画面です。
手作り感溢れててアレですが。。
複数画面から呼び出される入力補助のコード参照画面を想定しています。
まあ、AngularJS使うならインクリメンタルサーチとかも簡単に実装できるだろうし、旧態依然としたこんな機能なくても。。。とは思います。
ソースはこちらです。
https://github.com/ko-aoki/angularJS_practice/tree/forQiita
テンプレートファイル
- mntMstUser.html
ユーザマスタメンテナンス画面。
<div csng-code-dept></div>
で部門コード検索のカスタムディレクティブを使用しています。
<link href="css/common-app.css" rel="stylesheet" type="text/css" />
<link href="css/mntMUser.css" rel="stylesheet" type="text/css" />
<h1 style="font-size:16px;">ユーザマスタメンテ</h1>
<div id="app" style="z-index: 1">
<div csng-code-dept></div>
<div id="message">
//略
- codeDept.html
部門コードの検索画面。
カスタムディレクティブのテンプレートです。
明細内の「OK」押下で明細データを親画面に反映、「Cancel」押下で親画面に戻る機能です。
<div csng-page>
でページ検索のカスタムディレクティブを使用しています。
<div id="codeDept" class="hide"
style="z-index:5; position: absolute; width: 100%; height: 100%; opacity: 0.7; background-color: gray">
<div id="finderDiv" style="position: absolute; top:20%;left: 20%;background-color: white;width:25%;">
<div id="header">
<div id="condition" class="content-wrap">
<form>
<table border="1">
<tr>
<th>組織1コード
</th>
<td>
<input type="text" ng-model="pDeptId"/>
</td>
</tr>
//略 検索条件
</table>
<button type="button" ng-click="find()" id="find">検索</button>
<div csng-page page-info="pageInfo" page-jump="pageJump"></div>
</form>
<table border="1">
<thead>
<tr>
<th>選択</th>
<th>親部門ID</th>
<th>親部門名</th>
<th>部門ID</th>
<th>部門名</th>
</tr>
</thead>
<tr ng-repeat="rec in recs">
<td>
<button ng-click="ok(rec)">OK</button>
</td>
<td>
<label for="deptIds"><span>{{rec.pDeptId}}</span></label>
</td>
<td>
<label for="deptIds"><span>{{rec.pDeptNm}}</span></label>
</td>
<td>
<label for="deptIds"><span>{{rec.deptId}}</span></label>
</td>
<td>
<label for="deptIds"><span>{{rec.deptNm}}</span></label>
</td>
</tr>
</table>
<button ng-click="cancel(rec)">Cancel</button>
</div>
</div>
</div>
</div>
- page.html
ページ検索のテンプレート。
"<"や">"、ページの数字リンクで指定のページ範囲の検索をします。
ロジックは現在ページならリンク状態にしないとか最終ページなら">"(進む)を表示しないとか。
ng-click
で指定しているfunctionは呼び出し元のcodeDeptCtrl.js
に定義しています。
<div class="page" ng-show="pageInfo != null">
<span>{{pageInfo.startNum}} - {{pageInfo.endNum}} / {{pageInfo.totalNum}}</span>
<br>
<span ng-show="pageInfo.curPage > 1">
<a href ng-click="pageJump(1)"><<</a>
<a href ng-click="pageJump(pageInfo.curPage - 1)"><</a>
</span>
<span ng-repeat="dispPage in pageInfo.dispPageList">
<a ng-if="dispPage !== pageInfo.curPage" href ng-click="pageJump(dispPage)">{{dispPage}}</a>
<span ng-if="dispPage === pageInfo.curPage">{{dispPage}}</span>
</span>
<span ng-show="pageInfo.curPage < pageInfo.totalPage">
<a href ng-click="pageJump(pageInfo.curPage + 1)">></a>
<a href ng-click="pageJump(pageInfo.totalPage)">>></a>
</span>
<input type="text" ng-model="pageInfo.requestPage" />ページ
<button type="button" ng-click="pageJump(pageInfo.requestPage)">ページ指定</button>
</div>
カスタムディレクティブファイル
カスタムディレクティブってなんぞ、なのですが、
JavaEEに慣れた人なら、カスタムタグと同じと思えば分かりやすいかと。
もちろん、それだけではないのでしょうが。
複数ページで同じロジック/デザインを使用する場合に作成することになります。
今回は使用していませんがjsp:include
はng-include
で代替できるのかな。
ng-include
はヘッダ/フッタ/サイドバーあたりに使用しそうです。
- csngCodeDept.js
テンプレートとコントローラを指定しています。
restrict: 'A'
はタグの属性として使用。
scope:true
で親のscopeから派生した別の新しいscopeになります。falseだとscopeが親と共有なので意図せずに同一名の変数を使用していたりすると思わぬ結果になったり。
define(['directives'],function(directives) {
directives.directive('csngCodeDept', ['$rootScope', '$http',function($rootScope, $http) {
return {
restrict: 'A',
scope: true,
templateUrl:'templates/codeDept.html',
replace:true,
controller: 'CodeDeptCtrl'
};
}]);
});
- csngPage.js
こちらはコントローラの指定がありません。
ページ検索で呼ぶリソースは親画面ごとに異なってくるので、親画面のコントローラに処理を記述しています。
scopeの'='はカスタムタグ使用時の属性とデータバインディングします(分離スコープ)。分離スコープはほかにも数種類あって、汎用的なディレクティブでは分離スコープを使用したほうがよいそうです。
確かに、使用時に何の値をセットすべきか属性でわかるのは便利ですね。
define(['directives'], function(directives) {
directives.directive('csngPage', ['$rootScope', function($rootScope) {
return {
restrict: 'A',
scope: {
pageInfo: '=',
pageJump: '='
},
templateUrl:'templates/page.html',
replace:true
};
}]);
});
コントローラファイル
- codeDeptCtrl.js
部門コード検索画面のコントローラ。
カスタムディレクティブに対応するコントローラですが、特に通常と差異なく記述しています。
POSTした結果をページのカスタムディレクティブcsngPage
で使用するpageInfo
、pageJump
をここで定義しています。
$scope.ok
ではCodeDeptOK
イベントを発行して、親画面に通知しています。
define(['controllers' ],
function(controllers) {
controllers.controller('CodeDeptCtrl', ['$scope', '$http',
function ($scope, $http) {
$scope.find = function () {
$http.post('webresources/codeDept/',
{
pDeptId: $scope.pDeptId,
pDeptNm: $scope.pDeptNm,
deptId: $scope.deptId,
deptNm: $scope.deptNm,
pageInfo: $scope.pageInfo
},
{headers: {
'Content-Type': 'application/json'
}}
).success(function (data) {
$scope.pageInfo = data.pageInfo;
$scope.recs = data.recs;
});
};
$scope.pageJump = function (page) {
$scope.pageInfo.requestPage = page;
$scope.find();
};
$scope.ok = function ok(rec) {
$scope.$emit('CodeDeptOK', rec);
};
$scope.cancel = function cancel() {
$("#codeDept").removeClass("show");
$("#codeDept").addClass("hide");
};
}]
)
});
- mntMstUserCtrl.js
部門コード検索画面(カスタムディレクティブ)の親画面のコントローラです。
codeDeptCtrl.jsで発行されたイベントを$scope.$on
で処理しています。
define(['jquery', 'controllers', 'angularResource', 'services/dtoSrv'],
function($, controllers) {
controllers.controller('MntMstUserCtrl', ['$scope', '$http', '$location', '$resource', 'dtoSrv',
function ($scope, $http, $location, $resource, dtoSrv) {
//略
//部門検索「OK」押下
$scope.$on('CodeDeptOK', function (event, rec) {
$scope.cond.deptId1 = rec.pDeptId;
$scope.cond.deptNm1 = rec.pDeptNm;
$scope.cond.deptId2 = rec.deptId;
$scope.cond.deptNm2 = rec.deptNm;
$("#codeDept").removeClass("show");
$("#codeDept").addClass("hide");
});
}]
)
}
);
#JAX-RSリソースクラス
ここからJavaです。
- CodeDeptResource.java
サービス呼んでるだけです。
AngularJSがJSONでPOSTしているので、@Consumes("application/json")
しています。
@Path("codeDept")
public class CodeDeptResource {
@Inject
private CodeDeptService codeDeptService;
/**
* Creates a new instance of MenuService
*/
public CodeDeptResource() {
}
/**
* 部門コード検索画面を取得します.
* @param form
* @return
*/
@POST
@Consumes("application/json")
@Produces("application/json")
public CodeDeptForm getDepts(CodeDeptForm form) {
HashMap<String, String> param = new HashMap<>();
param.put("pDeptId", form.getpDeptId());
param.put("pDeptNm", form.getpDeptNm());
param.put("deptId", form.getDeptId());
param.put("deptNm", form.getDeptNm());
int curPage = form.getPageInfo() != null ? form.getPageInfo().getRequestPage() : 0;
CodeDeptForm rtnForm = codeDeptService.getDepts(param, curPage);
return rtnForm;
}
}
サービスクラス
- CodeDeptServiceImple.java
ページ情報と検索結果をformに設定します。
MstDeptRepositoryImpl
ではNativeQuery使用して(内部ではOracleのROW_NUMBER()使用)ページ検索しています。(ページ情報ふくめて、ゴリゴリすぎるのでソースは割愛します。)
でも、実はJPAはページ検索できるんですね…!
public class CodeDeptServiceImple implements CodeDeptService{
@Inject
private MstDeptRepository mstDeptRep;
/**
* Creates a new instance of MenuService
*/
public CodeDeptServiceImple() {
}
/**
* 部門コード検索画面情報を取得します.
* @param param
* @param curPage
* @return
*/
public CodeDeptForm getDepts(Map<String, String> param, int curPage){
BigDecimal cnt = mstDeptRep.countLevel1to2DeptList(param);
//ページ込みで明細取得
PageBean page = new PageBean();
if (curPage < 1) {
curPage = 1;
}
page.setCurPage(curPage);
//ページ込みで明細取得
PageBuilder pb = new PageBuilder();
page = pb.build(curPage, 5, cnt.intValue(), 3);
CodeDeptForm form = new CodeDeptForm();
List<CodeDeptBean> recs = mstDeptRep.selectLevel1to2DeptList(param, page.getStartNum(), page.getEndNum());
form.setRecs(recs);
form.setPageInfo(page);
form.setResult("ok");
return form;
}
}
まとめ
ざっくり動くレベルで作成しましたが、JSPを使用していた時と比べると、書いていて楽しかったことが印象に残りました。
ボタン押下して画面遷移とか、ブラウザ内の出来事がきちんとサーバから切り離されていることが心地よく感じる。デモの紙芝居HTMLをJSPに変換する作業から解放されるのは嬉しいですね。
コアとなるサンプルがあれば、Javaしか書いていなかったプログラマでもなんとかなるんじゃないでしょうか。
最初にJSP見た時の戸惑いとそれほど変わらないような。
(スクリプトレットだらけになったなぁ…)
一方、課題も山積みです。
- セキュリティ
- ファイル構成
- ネーミングルール
- 共通ロジックの記述方法
などなど。
もっと勉強しないと怖くて、とても業務に使えないです。。。