入力チェック・整合性チェック
最終更新日: 2022年11月10日
R8 | R9
入力チェック処理をスクリプトコードとして記述することができます。
ここに書いた処理は、その他の(必須チェックや、文字数チェックといった設定で記述できる)チェックの後に実行されます。
Designerでスクリプトを修正すると、開発機上のアプリケーションへ即座に反映されます。ビルド処理なしで修正を確認することができます。
スクリプト内では、モデル名に対応した「オブジェクト」を利用できます。
WagbyではWebフォームから入力された値は「プレゼンテーションモデル」が管理しています。
プレゼンテーションモデルの操作では、項目名の末尾に ".content" を付与することで、入力文字列を取得することができます。
次の例は、モデル schedule の日付項目 sch_date の入力が "R5" で始まる場合はエラーにする、というスクリプトです。
画面に表示するメッセージは次の3種類を利用することができます。
処理が正常に終了した場合のメッセージです。青枠にメッセージが表示されます。
警告メッセージです。黄色枠にメッセージが表示されます。
エラーメッセージです。赤枠にメッセージが表示されます。
メッセージの国際化にも対応しています。
エラーメッセージファイル $(DEVHOME)\customize\resources\myerrormsg_ja.properties.UTF8 に「キー=値」形式のメッセージを記述します。詳細は"メッセージの変更・国際化 > リソースファイルの編集"をお読みください。
次のコードでロケールに対応した適切なメッセージが出力されます。(この例では、キーに "error.original" という名称を使っています。)
エラーメッセージファイルにプレースホルダを指定することもできます。具体的には次のようになります。
Jfcerror などのクラスを使わず、例外 BusinessLogicException にエラーメッセージを含めることもできます。ここで設定したエラーメッセージが画面に表示されます。
エラーではなく警告を使う場合は、JFCHelperUtils クラスを使います。次のようにしてください。
JFCHelperUtils.checkWarnedErrorメソッドの第一引数は p となります。第二引数は「入力された値」です。第三引数は警告オブジェクト(warn)です。第四引数は「モデルID.項目ID」としてください。第五引数は国際化対応時、エラーメッセージのリソースのキー名となります。通常は上記例のように「モデルID.項目ID.error.input.generic」としてください。
プレゼンテーションモデルの入力項目に errorcode という属性が用意されています。ここに何らかの値を設定すると、当該入力欄の背景を赤色とします。
モデルID「AAA」の繰り返しコンテナ「AAAM」に含まれる項目「AAAM/item1」の errorcode をセットするコードは次のとおりです。
この項目がモデル参照項目の場合、Jfcerror クラスのインスタンス error の name 属性に項目IDを指定してください。
標準ではエラーメッセージに HTML タグを含めることはできません。これはセキュリティ対策のため、標準ではサニタイズ処理が有効となっているためです。
開発者は次のように sanitize フィールドを
Webフォームからの入力ではなく、自動計算によって求められた値を使った「整合性チェック」を行うこともできます。この場合はストアモデルを用います。ストアモデルは画面から入力された文字列を適切な型(文字、数字、日付)に変換した値を保持します。また自動計算によって求められた値も保持します。
次の例は、親モデル Parent と子モデル Child (外部キーで関連する親子関係)において、親側の Parent に、子である Child モデルの数(COUNT)および、項目の最大値(MAX)が定義されているものとします。
このとき、countOfChild 項目は2以上、maxOfChildItem1 項目が100以上というルールは次のように記述します。
親子モデル関係において、子モデルの同時更新を有効にします。
このとき「親モデルに、必ず1件以上の子モデルが必要」という入力チェックは次のように記述できます。子モデルを Child と定義しています。
親子モデル同時更新機能では、入力チェック処理のタイミングで、対象となる子モデルの配列が、p.requestオブジェクトに格納されています。キー名は「子モデル名_ary」です。末尾に "_ary" をつけたキー名で、配列を取得できます。
これを取得し、配列の数をチェックすることで、子モデルの数を知ることができます。
なお、ここで取得した Child_ary はストアモデルです。
親子モデル関係において、子モデルの同時更新を有効にします。
このとき「親モデルの出荷日と、各子モデルの納期日を比較する」というスクリプトを次のように記述することができます。
Parent ならびに Child_ary はストアモデルです。ストアモデルは文字列、数字、日付型を区別しているため、項目 sdate や nouki は日付型と認識されます。そのため JavaScript の日付オブジェクトとして操作できます。具体的には getTime() メソッドを利用した比較処理を行うことができます。
customer モデルが最大、10件までしか登録できない、というルールを想定します。このチェックのためには、現在データベースに格納されている「件数」を知る必要があります。
入力チェックのスクリプトでデータベースを操作するためには、開発者の方で「セッション」を取得します。
スクリプトの例を示します。
商品 (product) モデルを想定します。主キー項目 pkey は順序による自動採番とし、また、主キーではない項目「商品コード (pcode)」を持つとします。ここで、重複する商品コードを事前にチェックし、重複があった場合はエラーメッセージを表示するスクリプトを紹介します。
ここでは model1 モデルのラジオボタン項目「有効・無効 (enabled)」がセットされたデータが1つだけかどうかをチェックする例を説明します。有効・無効(enabled)項目の値が "1" となっているデータが2件以上あった場合、エラーメッセージを表示して、編集画面に戻します。
入力チェックスクリプトのため、ここで値を書き換えることはできません。値の参照のみを行うことができます。
「コントローラ > 一覧更新 > データベースコミット前」を用意します。
「コントローラ > 一覧更新 > 更新の実行」を用意します。
上で説明した「データベースコミット前」のスクリプトで、insertMap や updateMap に直接、エラーメッセージをセットしていました。「更新の実行」スクリプトで、これらのエラーメッセージを消去しておきます。(消去しないとエラーメッセージが残ってしまうためです。)
これまでの説明は、updateMap や insertMap といった(一覧更新画面の内部で利用している)表示用データを操作していました。次に紹介するスクリプトは、これらの内部変数を使わないパターンです。
updateMap や insertMap を意識しないでよいという要件であれば、こちらの方式を推奨します。
前節で説明したスクリプトは、画面に表示されているデータのみを対象にしていました。ここでは(画面に表示されていない分も含めた)データの入力チェックを行うスクリプトを紹介します。
入力チェックスクリプトのため、ここで値を書き換えることはできません。値の参照のみを行うことができます。
「コントローラ > 一覧更新 > データベースコミット前」を用意します。
スクリプト内部ではコンディションモデル <モデルID>_c を利用できます。
定義方法
スクリプトの種類
画面
スクリプト
登録
入力チェック
更新
一覧更新
入力チェック(一覧更新)
一覧表示・検索
入力チェック(検索)
プレゼンテーションモデル
/* 日付を取り出す。*/
var s =
(schedule_p.schDate !== null) ? schedule_p.schDate.content : null;
/* slice は JavaScript が提供する */
if (s !== null && s.slice(0,3)=="R5") {
var error = new Jfcerror();
error.content = "令和5年はまだ入力できません。";
p.errors.addJfcerror(error);
}
エラーメッセージ
INFO
var info = new Jfcinfo();
info.content="...";
p.errors.addJfcinfo(info);
WARN
var warn = new Jfcwarn();
warn.content="...";
p.errors.addJfcwarn(warn);
ERROR
var error = new Jfcerror();
error.content="...";
p.errors.addJfcerror(error);
ルール
errorManager クラスと国際化
p.errors.addJfcerror(errorManager.getJfcerror("error.original", p.locale));
var arrayOfStrings = Java.type("java.lang.String[]");
var obj = new arrayOfStrings(1);
obj[0] = "テストです。";
p.errors.addJfcerror(errorManager.getJfcerror("error.original", obj, p.locale));
例外 BusinessLogicException を利用する
throw new Packages.jp.jasminesoft.jfc.core.exception.BusinessLogicException("エラーメッセージ");
警告
var JFCHelperUtils = Java.type("jp.jasminesoft.jfc.JFCHelperUtils");
var s = model1.item1;
if (s != null && s == '(エラーとしたい値)') {
var warn = new Jfcwarn();
warn.content="..."; /*警告メッセージ*/
JFCHelperUtils.checkWarnedError(
p, s, warn, "model1.item1",
"model1.item1.error.input.generic");
}
入力欄の背景色の設定
例
var AAAMClass = Java.type("jp.jasminesoft.wagby.model.AAA_p.AAAM");
var Item1Class = Java.type("jp.jasminesoft.wagby.model.AAA_p.Item1");
var cont_ary = AAA_p.AAAM;
if (cont_ary !== null) {
var values;
for (var i=0; i<cont_ary.length; i++) {
if (cont_ary[i].item1 === null) {
values = new Item1Class();
cont_ary[i].item1 = values;
} else {
values = cont_ary[i].item1;
}
values.errorcode = "ERROR";/* errorcode をセットする */
var error = new Jfcerror();
error.content = "ERROR";
p.errors.addJfcerror(error);
}
}
...
var error = new Jfcerror();
error.name = "item1"; /* name 属性に項目IDを指定する */
error.content = "ERROR";
p.errors.addJfcerror(error);
エラーメッセージにHTMLタグを含める
false
とすることで、サニタイズ処理を無効にすることができます。
var error = new Jfcerror();
error.sanitize = false;
error.content = "<b>ERROR</b>";
p.errors.addJfcerror(error);
[例] 整合性チェック
/* Parent はストアモデル */
if (Parent.countOfChild < 2) {
var error = new Jfcerror();
error.content = "子モデルの数は2以上です。";
p.errors.addJfcerror(error);
}
if (Parent.maxOfChildItem1 < 100) {
var error = new Jfcerror();
error.content = "子モデルitem1の最大値は100以上です。";
p.errors.addJfcerror(error);
}
[例] 子モデルの存在チェック
/* 子モデル名を Child と定義している */
var Child_ary = p.request.getAttribute("Child_ary");/*子モデル配列の取得*/
var count = Child_ary.length;
if (count < 1) {
var error = new Jfcerror();
error.content = "子モデルChildは1件以上、必要です。";
p.errors.addJfcerror(error);
}
[例] 親モデルと子モデルの値を比較する
/*親モデルの出荷日と、子モデルの納期を比較する*/
var syukkabi = Parent.sdate;/*Parentはストアモデル*/
var Child_ary = p.request.getAttribute("Child_ary");
var count = Child_ary.length;
for (var i=0; i<count; i++) {
var Child = Child_ary[i];/*Childもストアモデル*/
var nouki = Child.nouki;/*syukkabiもnoukiもJavaScriptのDate型オブジェクトとして扱える*/
if (syukkabi.getTime() > nouki.getTime()) {
var error = new Jfcerror();
error.content = (i+1)+"番目の納期は、出荷日よりも早い日になっています。";
p.errors.addJfcerror(error);
}
}
[例] SQLを用いた件数チェック
var HibernateUtil = Java.type("jp.jasminesoft.jfc.app.HibernateUtil");
var session = HibernateUtil.openSession();
try {
var o = session.createSQLQuery(
"SELECT COUNT(*) FROM \"customer\"").uniqueResult();
if (o > 10) {
var error = new Jfcerror();
error.content = "10件以上のデータを登録することはできません。";
p.errors.addJfcerror(error);
}
} catch (e) {
e.printStackTrace();
} finally {
if (session !== null) {
session.close();/*忘れないこと*/
}
}
[例] EntityServiceとCriteriaを用いた存在チェック
var screentype = p.request.getAttribute("__jfc_screen_type");/* SCREENTYPE 関数のこと */
var service = p.appctx.getBean("ProductEntityService");
var criteriaConverter = p.appctx.getBean("ProductCriteriaConverter");
var criteria = criteriaConverter.defaultCriteria();
var metaClass = Java.type("jp.jasminesoft.wagby.model.product.ProductMeta");
var meta = new metaClass();
criteria.eq(meta.pcode, product.pcode);/* pcode の重複を確認する。*/
var list = service.find(criteria);
if (list !== null && list.size() >= 1) {
if (screentype === "update" && list.size() === 1) {
var o = list.get(0);
if (o.pkey === product.pkey) {
/* 自分自身なのでエラーとしない */
return;
}
}
var error = new Jfcerror();
error.content = "商品コード '"+product.pcode+"' が重複しています。";
p.errors.addJfcerror(error);
}
[例] 一覧更新画面の入力チェック (1) 画面に表示されているデータのみ
注意
データベースコミット前
function process() {
var Jfcerror = Java.type("jp.jasminesoft.jfc.error.Jfcerror");
var Item = Java.type("jp.jasminesoft.wagby.model.model1_ulp.Item");
// Map key is primary key.
// 編集中更新データのUlpモデルのItem
// Map<String, jp.jasminesoft.wagby.model.model1_ulp.Item>
var updateMap = p.pageMap.get("update_model1_ulp");
// 編集中新規登録データのUlpモデルのItem
// keyがnullのデータは既存データに紐づかない新規登録データ
// keyがnullでないデータは、既存データに紐づくデータ
// Map<String, List<jp.jasminesoft.wagby.model.model1_ulp.Item item>>
var insertMap = p.pageMap.get("insert_model1_ulp");
// 編集中削除データのUlpモデルのItem
// Map<String, jp.jasminesoft.wagby.model.model1_ulp.Item>
var deleteMap = p.pageMap.get("delete_model1_ulp");
// 更新データの編集元のEntity
// Map<String, Model1>
var updateSrcMap = p.pageMap.get("update_src_model1_ulp");
// 新規登録データの編集元のEntity
// Map<String, List<Model1>>
var insertSrcMap = p.pageMap.get("insert_src_model1_ulp");
// 画面に表示されているデータに対応する編集中データのEntity
// List<Model1>
var datas = p.pageMap.get("datas_model1_ulp");
// 画面に表示されているデータに対応する編集元データのEntity
// List<Model1>
var datasrc = p.pageMap.get("datasrc_model1_ulp");
// 画面に表示されているデータのUlp
// jp.jasminesoft.wagby.model.model1_ulp.Model1Ulp
var _listp = p.pageMap.get("model1_ulp");
var errormsg = "エラーが発生しました。有効・無効にて有効とするのは1か所のみとしてください。"
var items = _listp.item;
var enableditems = new Array();
for (i=0 ; i < items.length ; i++) {
var item = items[i];
//print("item="+item);
if (item.jfcdelete) {
continue;
}
var enabled = getChoosedEnabled(item);
//print ("item.enabled.id="+enabled.id)
if (enabled.id === 1) {
enableditems.push(item);
}
}
if (enableditems.length > 1) {
var error = new Jfcerror();
error.content = errormsg;
p.errors.addJfcerror(error);// 画面上部に表示されるエラーメッセージ。
// 画面内のデータごとにエラーメッセージを表示するために、insertMap,updateMap
// 内のItemにエラーメッセージをセットする。
// do_setDataにて、これらのデータから表示用のデータにエラーメッセージがセットされるため。
for (i=0 ; i < enableditems.length ; i++) {
var item = enableditems[i];
var jfcupdatetype = item.jfcupdatetype;
var item1 = null;
if (jfcupdatetype === "insert" || jfcupdatetype === "copy") {
var itemlist = insertMap.get(item.jfcpkey);
//print("itemlist="+itemlist)
//print("item.pkeygroupnum="+item.jfcpkeygroupnum)
if (itemlist !== null) {
item1 = itemlist.get(item.jfcpkeygroupnum-1);
}
} else if (jfcupdatetype === "update") {
item1 = updateMap.get(item.jfcpkey);
if (item1 === null) {// 未編集のデータであったため、新しい Item を用意する。
item1 = new Item(item);
updateMap.put(item.jfcpkey, item1);
}
}
if (item1 !== null) {
//print("item1="+item1)
item1.jfcerror = errormsg;
}
}
}
}
function getChoosedEnabled(item) {
var enabledary = item.enabled;
var enabled;
for (j=0; j < enabledary.length; j++) {
if (enabledary[j].choose) {
enabled = enabledary[j];
break;
}
}
return enabled;
}
var xxx = p.pageMap.get("...")
は、一覧更新処理を行っている UpdateListModel1Controller クラスの動作のために必要な内部の変数値を取得しています。スクリプト内ですべての変数を利用しているわけではありませんが、このような変数が利用できるということを示すため、すべての変数を用意しています。詳細は各行のコメントをお読みください。var _listp = p.pageMap.get("model1_ulp")
にて取得しています。接尾語に "_ulp" が付与されたプレゼンテーションモデルは一覧更新画面のみで利用できるもので、現在表示されているページのデータが格納されています。var items = _listp.item;
とすることで、この行データを配列として取得できます。この実態は jp.jasminesoft.wagby.model.model1_ulp.Item クラスです。更新の実行
function process() {
var Jfcerror = Java.type("jp.jasminesoft.jfc.error.Jfcerror");
// Map key is primary key.
// 編集中更新データのUlpモデルのItem
// Map<String, jp.jasminesoft.wagby.model.model1_ulp.Item>
var updateMap = p.pageMap.get("update_model1_ulp");
// 編集中新規登録データのUlpモデルのItem
// keyがnullのデータは既存データに紐づかない新規登録データ
// keyがnullでないデータは、既存データに紐づくデータ
// Map<String, List<jp.jasminesoft.wagby.model.model1_ulp.Item item>>
var insertMap = p.pageMap.get("insert_model1_ulp");
// 編集中削除データのUlpモデルのItem
// Map<String, jp.jasminesoft.wagby.model.model1_ulp.Item>
var deleteMap = p.pageMap.get("delete_model1_ulp");
// 更新データの編集元のEntity
// Map<String, Model1>
var updateSrcMap = p.pageMap.get("update_src_model1_ulp");
// 新規登録データの編集元のEntity
// Map<String, List<Model1>>
var insertSrcMap = p.pageMap.get("insert_src_model1_ulp");
// 画面に表示されているデータに対応する編集中データのEntity
// List<Model1>
var datas = p.pageMap.get("datas_model1_ulp");
// 画面に表示されているデータに対応する編集元データのEntity
// List<Model1>
var datasrc = p.pageMap.get("datasrc_model1_ulp");
// 画面に表示されているデータのUlp
// jp.jasminesoft.wagby.model.model1_ulp.Model1Ulp
var _listp = p.pageMap.get("model1_ulp");
var errormsg = "エラーが発生しました。有効・無効にて有効とするのは1か所のみとしてください。"
// insertMap, updateMapからエラーメッセージを削除する。
// エラーメッセージが残っていると、修正してもエラー状態となり、データベース保存への
// トランザクションが実行されないため。
var updateitems = updateMap.values().toArray();
for (i=0 ; i < updateitems.length ; i++) {
var item = updateitems[i];
//print("update item="+item);
if (item.sizeJfcerror() == 1 && item.getJfcerror(0) === errormsg) {
item.clearJfcerror();
}
}
var insertitemlists = insertMap.values().toArray();
for (i=0; i < insertitemlists.length; i++) {
var itemlist = insertitemlists[i]
if (itemlist === null) {
continue;
}
for (j=0; j < itemlist.size(); j++) {
var item = itemlis.get(j);
//print("insert item="+item);
if (item.sizeJfcerror() == 1 && item.getJfcerror(0) === errormsg) {
item.clearJfcerror();
}
}
}
}
よりシンプルなスクリプト
データベースコミット前
function process() {
var Jfcerror = Java.type("jp.jasminesoft.jfc.error.Jfcerror");
// 画面に表示されているデータのUlp
// jp.jasminesoft.wagby.model.model1_ulp.Model1Ulp
var _listp = p.pageMap.get("model1_ulp");
var errormsg = "エラーが発生しました。有効・無効にて有効とするのは1か所のみとしてください。"
var items = _listp.item;
var count = 0;
for (i=0 ; i < items.length ; i++) {
var item = items[i];
//print("item="+item);
if (item.jfcdelete) {
continue;
}
var enabled = getChoosedEnabled(item);
//print("item.enabled.id="+enabled.id);
if (enabled.id === 1) {
count++;
}
}
if (count > 1) {
var error = new Jfcerror();
error.content = errormsg;
p.errors.addJfcerror(error);// 画面上部に表示されるエラーメッセージ。
}
}
function getChoosedEnabled(item) {
var enabledary = item.enabled;
var enabled;
for (j=0; j < enabledary.length; j++) {
if (enabledary[j].choose) {
enabled = enabledary[j];
break;
}
}
return enabled;
}
更新の実行
function process() {
// 画面に表示されているデータのUlp
// jp.jasminesoft.wagby.model.model1_ulp.Model1Ulp
var _listp = p.request.getAttribute("model1_ulp");
var errormsg = "エラーが発生しました。有効・無効にて有効とするのは1か所のみとしてください。";
// p.request.getAttribute("__jfc_jfcerrors")をチェックして「データベースコミット前」のスクリプトで
// errormsgのエラーメッセージが設定されたかどうかをチェックする。
var jfcerrors = p.request.getAttribute("__jfc_jfcerrors");
var isSetErrorMessage = false;
if (jfcerrors !== null) {
var existerrmsg = jfcerrors.jfcerror;
for (var i=0; i<existerrmsg.length; i++) {
var error = existerrmsg[i];
if (error.content === errormsg) {
isSetErrorMessage = true;
break;
}
}
}
if (isSetErrorMessage) { // エラーメッセージが設定されていた
var items = _listp.item;
var enableditems = new Array();
for (i=0 ; i < items.length ; i++) {
var item = items[i];
//print("item="+item);
if (item.jfcdelete) {
continue;
}
var enabled = getChoosedEnabled(item);
//print("item.enabled.id="+enabled.id)
if (enabled.id === 1) {
item.jfcerror = errormsg;// 同じエラーメッセージを各データにもセットする。
}
}
}
}
function getChoosedEnabled(item) {
var enabledary = item.enabled;
var enabled;
for (j=0; j < enabledary.length; j++) {
if (enabledary[j].choose) {
enabled = enabledary[j];
break;
}
}
return enabled;
}
[例] 一覧更新画面の入力チェック (2) 画面に表示されていないデータも含める
注意
データベースコミット前
function process() {
var Jfcerror = Java.type("jp.jasminesoft.jfc.error.Jfcerror");
var finderContext = p.appctx.getBean("UpdateListModel1FinderContext");
var entityService = p.appctx.getBean("Model1EntityService");
var results = entityService.find(finderContext);
var count = 0;
for (i=0; i<results.size(); i++) {
var entity = results.get(i);
//print("results="+entity);
if (entity.enabled === 1) {
count++;
}
}
if (count > 1) {
var error = new Jfcerror();
error.content = "エラーが発生しました。有効・無効にて有効とするのは1か所のみとしてください。"
p.errors.addJfcerror(error);
}
}
[例] 検索フォームの入力チェック9.0.0
var ASSETNO = JSHASSET_c.ASSETNO;
var NAME = JSHASSET_c.NAME;
if (ASSETNO === null && NAME === null) {
var error = new Jfcerror();
error.content = "ASSETNOかNAMEのいずれかを入力してください。";
p.errors.addJfcerror(error);
}