実装例:登録画面を作る¶
この項では、スマートフォンでTODOを登録する画面の実装例を紹介します。
項目
前提条件¶
intra-mart Accel Platform をインストールし、初期設定までが完了していること。
ベースモジュールに SAStruts 開発フレームワーク、およびIM-Mobile Frameworkモジュールを含めて環境を作成してください。実行環境は単体テスト用で作成してください。
下準備 テーブル作成¶
以下手順を行う前に、以下のテーブルを作成してください。
mfw_sample
列名 データ型 主キー NOT NULL 説明 id VARCHAR(20) ○ ○ レコードのID user_cd VARCHAR(20) ○ 登録ユーザID user_nm VARCHAR(20) ○ 登録ユーザ名 limit_date VARCHAR(20) TODOの期限 title VARCHAR(100) TODOのタイトル comment VARCHAR(1000) コメント progress NUMBER(3) 進捗度 complete VARCHAR(1) 完了/未完了 priority VARCHAR(1) 重要度 timestmp VARCHAR(20) タイムスタンプ ※その他のデータベースの場合は環境に合わせて調整してください。
画面を表示できるようにする¶
ソースの準備と配置¶
まず、以下2点のファイルを作成します。
sample.sastruts.action.samplesp.StoreAction
package sample.sastruts.action.samplesp; import org.seasar.struts.annotation.Execute; public class StoreAction { /** * 登録ページのパスを返却します。 * @return 入力ページのパス */ @Execute(validator = false) public String index() { return "/sample/imsp/store/store.jsp"; } }%CONTEXT_PATH%/WEB-INF/view/sample/imsp/store/store.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="imsp" uri="http://www.intra-mart.co.jp/taglib/imsp" %> <%@ taglib prefix="imui" uri="http://www.intra-mart.co.jp/taglib/imui" %> <imui:head> <title>TODO登録</title> </imui> <div data-role="page" id="main"> <imsp:headerWithLink headerText="TODO登録" /> <div class="ui-content" role="main"> </div> <imsp:commonFooter dataPosition="fixed" /> </div>コラム
スマートフォン向けタグライブラリを使用するには以下のtaglibディレクティブを指定してください。<%@ taglib prefix="imsp" uri="http://www.intra-mart.co.jp/taglib/imsp" %><imsp:headerWithLink> - ヘッダ部左端に、任意のページに遷移のボタンを備えたヘッダを表示します。
<imsp:spCommonFooter> - フッタ部にHOMEボタンとログアウトボタンを表示します。
jqueyMobile1.4.5を読み込むようにする¶
ライブラリ群の設定を行います。本サンプルでは /samplesp 以下すべてに jQueryMobile1.4.5 が読み込まれるように設定します。
%CONTEXT_PATH%/WEB-INF/conf/theme-full-theme-path-config/im_mobile_sample.xml
<theme-full-theme-path-config xmlns="http://www.intra-mart.jp/theme/theme-full-theme-path-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.intra-mart.jp/theme/theme-full-theme-path-config theme-full-theme-path-config.xsd"> <path client-type="sp" libraries-version="iap-8.0.11">/samplesp/.*</path> </theme-full-theme-path-config>
メニューの作成¶
メニューの設定を行います。
PCブラウザからテナント管理者でログインし、「メニュー設定」画面を表示します。
グローバルナビ(スマートフォン用)を選択し、新規メニューグループ「テストTODO」を作成します。
新規メニューアイテムを作成します。
メニューアイテム名を「TODO登録」とし、URLを”samplesp/store”とします。
画面に要素を配置する¶
<div class=”ui-content” role=”main”>内に要素を配置します。例としてテキストボックスとラベルを配置してみます。ラベルを配置するには<imsp:fieldContain>タグを使用します。
store.jsp
<imsp:fieldContain label="TODO名" required="true"> <input type="text" name="title" /> </imsp:fieldContain>マークアップ結果。テーマにより最適化されたテキストボックスがされました。
同様に他の要素も配置していきます。
imsp:datePicker–日付文字列を参照入力するためのインタフェースを提供します。
<imsp:fieldContain label="期限" required="true"> <imsp:datePicker name="limit_date" /> </imsp:fieldContain>textarea–テキストエリアを提供します。
<imsp:fieldContain label="コメント"> <textarea name="comment"></textarea> </imsp:fieldContain>imsp:controlGroup–フォーム要素をグループ化します。
imsp:radioButton–jQuery mobileで最適化されたラジオボタンを提供します。
<imsp:controlGroup label="重要度"> <imsp:radioButton name="priority" id="radio1" value="0" label="低" /> <imsp:radioButton name="priority" id="radio2" value="1" label="中" /> <imsp:radioButton name="priority" id="radio3" value="2" label="高" /> </imsp:controlGroup>imsp:slider–スライダーを提供します。
<imsp:fieldContain label="進捗"> <imsp:slider name="progress" min="<%=0 %>" max="<%=100 %>" /> </imsp:fieldContain>imsp:toggle–トグルスイッチを提供します。
<imsp:fieldContain label="完了"> <imsp:toggle name="complete" onLabel="完了" offLabel="未完了" onValue="1" offValue="0" /> </imsp:fieldContain>マークアップ結果。各要素が表示されました。
その他使用可能な要素についてはAPIリストを参照してください。
コラム
この項目では、下記のポイントを確認しました。
- フォーム要素は<div class=”ui-content” role=”main”>内に配置する
- ラベルを与える場合は<imsp:fieldContain>タグを使用する
登録処理を実装する¶
まず、フォームを受け取るためのFormクラスを作成します。
sample.sastruts.form.samplesp.StoreForm
package sample.sastruts.form.samplesp; public class StoreForm { public String title; public String limit_date; public String comment; public String priority; public String progress; public String complete; }次に登録処理用のエンティティクラスを作成します。
sample.sastruts.entity.MfwSample
package sample.sastruts.entity; import java.math.BigDecimal; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; @Entity @Table(name="mfw_sample") public class MfwSample { @Column(name = "id") public String todoId; @Column(name = "user_cd") public String userCd; @Column(name = "user_nm") public String userName; public String title; @Column(name = "limit_date") public String limitDate; public String comment; public String priority; public BigDecimal progress; public String complete; public String timestmp; }コラム
エンティティクラスの詳細については S2JDBC エンティティ を参照してください。
登録処理用のサービスクラスを定義します。S2AbstractServiceクラスを継承し、型変数の宣言は上記で作成したエンティティを指定します。
sample.sastruts.service.samplesp.MfwSampleService
package sample.sastruts.service.samplesp; import org.seasar.extension.jdbc.service.S2AbstractService; import sample.sastruts.entity.MfwSample; public class MfwSampleService extends S2AbstractService<MfwSample> { }コラム
サービスクラスの詳細については S2JDBC サービスの作り方 を参照してください。
StoreActionクラスにフォームとサービスクラスのフィールド宣言を追加します。
import javax.annotation.Resource; import org.seasar.struts.annotation.ActionForm; import sample.sastruts.form.samplesp.StoreForm; import sample.sastruts.service.samplesp.MfwSampleService; public class StoreAction { @Resource @ActionForm public StoreForm storeForm; @Resource public MfwSampleService mfwSampleService;StoreActionクラスに登録処理メソッドを追加します。
import java.io.IOException; import java.math.BigDecimal; import jp.co.intra_mart.foundation.context.Contexts; import jp.co.intra_mart.foundation.context.model.AccountContext; import jp.co.intra_mart.foundation.database.exception.DatabaseException; import jp.co.intra_mart.foundation.i18n.datetime.DateTime; import jp.co.intra_mart.foundation.i18n.datetime.format.DateTimeFormatter; import jp.co.intra_mart.foundation.service.client.information.Identifier; import jp.co.intra_mart.foundation.user_context.model.UserContext; import sample.sastruts.entity.MfwSample; ... @Execute(validator = false) public String doStore() throws IOException, DatabaseException { //ユーザコンテキストを取得します。 UserContext userProfile = Contexts.get(UserContext.class); //アカウントコンテキストからタイムゾーンを取得します。 AccountContext accountContext = Contexts.get(AccountContext.class); DateTimeFormatter formatter = DateTimeFormatter.withPattern("yyyy/MM/dd HH:mm"); //登録値の設定 MfwSample insertData = new MfwSample(); insertData.todoId = new Identifier().get(); insertData.userCd = userProfile.getUserProfile().getUserCd(); insertData.userName = userProfile.getUserProfile().getUserName(); insertData.title = storeForm.title; insertData.limitDate = storeForm.limit_date; insertData.comment = storeForm.comment; insertData.priority = storeForm.priority; insertData.complete = storeForm.complete; if (storeForm.progress != null && !storeForm.progress.equals("")) { insertData.progress = new BigDecimal(storeForm.progress); } insertData.timestmp = formatter.format(DateTime.now(accountContext.getTimeZone())); //登録処理実行 int result = mfwSampleService.insert(insertData); return "/sample/imsp/store/store.jsp"; }コラム
- データベースの詳細については データベース を参照してください。
- コンテキストの詳細については アクセスコンテキスト を参照してください。
画面にFormタグと登録ボタンを配置します。 formタグのaction属性は先ほど追加したdoStoreメソッドとします。
<form name="storeForm" name="storeForm" id="storeForm" method="POST" action="samplesp/store/doStore" data-ajax="false"> <imsp:fieldContain label="TODO名" required="true"> <input type="text" name="title" /> </imsp:fieldContain> <imsp:fieldContain label="期限" required="true"> ... <imsp:fieldContain label="完了"> <imsp:toggle name="complete" onLabel="完了" offLabel="未完了" onValue="1" offValue="0" /> </imsp:fieldContain> <a class="ui-btn ui-btn-b ui-corner-all" id="storeButton">登録</a> </form>コラム
- リンクにui-btnクラスを指定するとリンクをボタン表示します。
- テーマを指定するにはクラス属性にui-btn-bのように「ui-btn-」の後にテーマを指定します。
- ボタンを角丸にするにはui-corner-allクラスを指定します。
- 「data-role=”button”」を指定してもボタンになりますが、jQuery Mobile1.4以降では非推奨となっています。
- リンク、またはFORM要素にdata-ajax=”false”属性を付加することで明示的にAjax画面遷移をキャンセルすることができます。
最後に登録ボタン押下時のイベントを実装します。このとき、記述箇所は<div data-role=”page”>内に実装することに気を付けてください。<div data-role="page" id="main"> <script> (function($){ $('#main').on("pagecreate", function() { $("#storeButton").tap(function() { $("#storeForm").submit(); }); }); })(jQuery); </script>注意
jQuery Mobileでは、Ajaxを使って画面遷移をする場合遷移先画面の<div data-role=”page”>要素のみ取得し、表示中画面に挿入します。そのため、<HEAD>タグ内にスクリプト、およびスタイルシートを宣言すると画面遷移時に読み込まれないため、不正動作をする場合があります。
- サーバを再起動して画面を再表示します。 登録ボタン押下時、登録処理を経て画面が再表示されます。
コラム
この項目では、下記のポイントを確認しました。
- フォームの入力内容を受け取るにはFormクラスを定義する
- 任意のアクションに遷移するにはパスにアクション名を指定する
入力チェック処理を実装する(クライアントサイド)¶
store.jspにバリデーションルールを定義します。rulesオブジェクトは入力チェックのルール、messagesオブジェクトは各ルールに対応したメッセージを定義します。
JSP
<script> var rules = { "title": { required:true, maxlength:20 }, "limit_date": { required:true, date:true } }; var messages = { "title": { required:"タイトルは必須です。", maxlength:"タイトルは20文字以内で入力してください" }, "limit_date": { required:"期限は必須です。", maxlength:"期限は日付形式で入力してください" } };登録ボタン押下時のスクリプト処理を修正します。
JSP
(function($){ $('#main').on("pagecreate", function() { //登録ボタン押下時のイベント $("#storeButton").tap(function() { //入力チェック実行 if (imspValidate('#storeForm', rules, messages)) { //正常時 imspAlert('入力エラーはありませんでした'); $("#storeForm").submit(); } else { imspAlert('入力エラーが発生しました', 'エラー'); } return false; }); }); })(jQuery); </script> マークアップ結果。タイトルと日付未入力の状態で登録ボタンを押下すると、エラーダイアログが表示され警告が出力されます。コラム
エラー仕様の詳細は、別ドキュメント クライアントサイド JavaScript の imspValidate を参照してください。
コラム
この項目では、下記のポイントを確認しました。
- クライアントサイドで入力チェックを実装するにはバリデーションルールオブジェクトを定義する
非同期で登録処理を実行する¶
登録ボタン押下時のスクリプト処理を修正します。
store.jsp
(function($){ $('#main').on("pagecreate", function() { // Formの2度押し防止 $('#storeForm').imspDisableOnSubmit(); $("#storeButton").tap(function() { if (imspValidate('#storeForm', rules, messages)) { //Ajaxでのデータ送信 imspAjaxSend('#storeForm', 'POST', 'json'); //バリデーションのリセット imspResetForm('#storeForm'); } else { imspAlert('入力エラーが発生しました', 'エラー'); } }); }); })(jQuery); </script>非同期で返却するレスポンスオブジェクトを定義します。
sample.sastruts.dto.samplesp.MyAjaxResponse
package sample.sastruts.dto.samplesp; public class MyAjaxResponse { public String result; public boolean error; public String errorMessage; public String successMessage; public String[] detailMessages; } doStoreメソッドを修正します。メソッドの返却値はnullとし、ResponseUtil.writeメソッドを使用しレスポンスオブジェクトのJSON形式に変換した文字列を返却します。import sample.sastruts.dto.samplesp.MyAjaxResponse; import net.arnx.jsonic.JSON; import org.seasar.struts.util.ResponseUtil; ... @Execute(validator = false) public String doStore() throws IOException, DatabaseException { //ユーザコンテキストを取得します。 UserContext userProfile = Contexts.get(UserContext.class); ... insertData.timestmp = formatter.format(DateTime.now(accountContext.getTimeZone())); MyAjaxResponse responseObject = new MyAjaxResponse(); try { //登録処理実行 int result = mfwSampleService.insert(insertData); //成功時 responseObject.result = "success"; responseObject.error = false; responseObject.successMessage = "登録が完了しました。"; } catch (Exception e) { responseObject.error = true; responseObject.errorMessage = "データ登録時にエラーが発生しました。"; responseObject.detailMessages = new String[] {"管理者にお問い合わせください。"}; } //レスポンスオブジェクトをJSON文字列で返却 ResponseUtil.write(JSON.encode(responseObject)); return null; } マークアップ結果。登録ボタン押下後にダイアログが表示され、登録処理が正常終了したことが確認できるようになりました。
コラム
この項目では、下記のポイントを確認しました。
- クライアントから非同期でリクエストを送信するにはimspAjaxSend関数を使う
最終結果¶
sample.sastruts.action.samplesp.StoreAction.java
package sample.sastruts.action.samplesp; import java.io.IOException; import java.math.BigDecimal; import javax.annotation.Resource; import jp.co.intra_mart.foundation.context.Contexts; import jp.co.intra_mart.foundation.context.model.AccountContext; import jp.co.intra_mart.foundation.database.exception.DatabaseException; import jp.co.intra_mart.foundation.i18n.datetime.DateTime; import jp.co.intra_mart.foundation.i18n.datetime.format.DateTimeFormatter; import jp.co.intra_mart.foundation.service.client.information.Identifier; import jp.co.intra_mart.foundation.user_context.model.UserContext; import net.arnx.jsonic.JSON; import org.seasar.struts.annotation.ActionForm; import org.seasar.struts.annotation.Execute; import org.seasar.struts.util.ResponseUtil; import sample.sastruts.dto.samplesp.MyAjaxResponse; import sample.sastruts.entity.MfwSample; import sample.sastruts.form.samplesp.StoreForm; import sample.sastruts.service.samplesp.MfwSampleService; public class StoreAction { @Resource @ActionForm public StoreForm storeForm; @Resource public MfwSampleService mfwSampleService; @Execute(validator = false) public String doStore() throws IOException, DatabaseException { // ユーザコンテキストを取得します。 final UserContext userProfile = Contexts.get(UserContext.class); // アカウントコンテキストからタイムゾーンを取得します。 final AccountContext accountContext = Contexts.get(AccountContext.class); final DateTimeFormatter formatter = DateTimeFormatter.withPattern("yyyy/MM/dd HH:mm"); // 登録値の設定 final MfwSample insertData = new MfwSample(); insertData.todoId = new Identifier().get(); insertData.userCd = userProfile.getUserProfile().getUserCd(); insertData.userName = userProfile.getUserProfile().getUserName(); insertData.title = storeForm.title; insertData.limitDate = storeForm.limit_date; insertData.comment = storeForm.comment; insertData.priority = storeForm.priority; insertData.complete = storeForm.complete; if (storeForm.progress != null && !storeForm.progress.equals("")) { insertData.progress = new BigDecimal(storeForm.progress); } insertData.timestmp = formatter.format(DateTime.now(accountContext.getTimeZone())); final MyAjaxResponse responseObject = new MyAjaxResponse(); try { // 登録処理実行 final int result = mfwSampleService.insert(insertData); // 成功時 responseObject.result = "success"; responseObject.error = false; responseObject.successMessage = "登録が完了しました。"; } catch (final Exception e) { e.printStackTrace(); responseObject.error = true; responseObject.errorMessage = "データ登録時にエラーが発生しました。"; responseObject.detailMessages = new String[] { "管理者にお問い合わせください。" }; } // レスポンスオブジェクトをJSON文字列で返却 ResponseUtil.write(JSON.encode(responseObject)); return null; } /** * 登録ページのパスを返却します。 * @return 入力ページのパス */ @Execute(validator = false) public String index() { return "/sample/imsp/store/store.jsp"; } }
%CONTEXT_PATH%/WEB-INF/view/sample/imsp/store/store.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="imsp" uri="http://www.intra-mart.co.jp/taglib/imsp" %> <%@ taglib prefix="imui" uri="http://www.intra-mart.co.jp/taglib/imui" %> <imui:head> <title>TODO登録</title> </imui> <div data-role="page" id="main"> <script> var rules = { "title": { required:true, maxlength:20 }, "limit_date": { required:true, date:true } }; var messages = { "title": { required:"タイトルは必須です。", maxlength:"タイトルは20文字以内で入力してください" }, "limit_date": { required:"期限は必須です。", maxlength:"期限は日付形式で入力してください" } }; </script> <script> (function($){ $('#main').on("pagecreate", function() { // Formの2度押し防止 $('#storeForm').imspDisableOnSubmit(); $("#storeButton").tap(function() { //入力チェック実行 if (imspValidate('#storeForm', rules, messages)) { //Ajaxでのデータ送信 imspAjaxSend('#storeForm', 'POST', 'json'); //バリデーションのリセット imspResetForm('#storeForm'); } }); }); })(jQuery); </script> <imsp:headerWithLink headerText="TODO登録" /> <div class="ui-content" role="main"> <form name="storeForm" id="storeForm" method="POST" action="samplesp/store/doStore" data-ajax="false"> <imsp:fieldContain label="TODO名" required="true"> <input type="text" name="title" /> </imsp:fieldContain> <imsp:fieldContain label="期限" required="true"> <imsp:datePicker name="limit_date" /> </imsp:fieldContain> <imsp:fieldContain label="コメント"> <textarea name="comment"></textarea> </imsp:fieldContain> <imsp:controlGroup label="重要度"> <imsp:radioButton name="priority" id="radio1" value="0" label="低" /> <imsp:radioButton name="priority" id="radio2" value="1" label="中" /> <imsp:radioButton name="priority" id="radio3" value="2" label="高" /> </imsp:controlGroup> <imsp:fieldContain label="進捗"> <imsp:slider name="progress" min="<%=0 %>" max="<%=100 %>" /> </imsp:fieldContain> <imsp:fieldContain label="完了"> <imsp:toggle name="complete" onLabel="完了" offLabel="未完了" onValue="1" offValue="0" /> </imsp:fieldContain> <a data-role="button" data-theme="b" id="storeButton">登録</a> </form> </div> <imsp:commonFooter dataPosition="fixed" /> </div>
sample.sastruts.form.samplesp.StoreForm.java
package sample.sastruts.form.samplesp; public class StoreForm { public String title; public String limit_date; public String comment; public String priority; public String progress; public String complete; }
sample.sastruts.entity.MfwSample.java
package sample.sastruts.entity; import java.math.BigDecimal; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; @Entity @Table(name = "mfw_sample") public class MfwSample { @Column(name = "id") public String todoId; @Column(name = "user_cd") public String userCd; @Column(name = "user_nm") public String userName; public String title; @Column(name = "limit_date") public String limitDate; public String comment; public String priority; public BigDecimal progress; public String complete; public String timestmp; }
sample.sastruts.service.samplesp.MfwSampleService.java
package sample.sastruts.service.samplesp; import org.seasar.extension.jdbc.service.S2AbstractService; import sample.sastruts.entity.MfwSample; public class MfwSampleService extends S2AbstractService<MfwSample> { }
sample.sastruts.dto.samplesp.MyAjaxResponse.java
package sample.sastruts.dto.samplesp; public class MyAjaxResponse { public String result; public boolean error; public String errorMessage; public String successMessage; public String[] detailMessages; }