astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

そうだAsta4dでWebアプリケーションを作ろう(第6回)

FormFlow

最終回は簡単なFormなら本当に簡単に作れちゃうFormFlowについて書いていきます。
複雑なFormも作れますが、複雑なFormは複雑な処理になってしまうので、ここでは触れないこととします。
興味がある方はユーザーガイドとかサンプルとか見てみてください。

実装

例としては名前とメールアドレスを入力してサーバーに送信するFormを例にして解説していきます。
stepはinput -> confirm -> completeという順番で行うものとします。

良くある簡易登録みたいな感じですかね。
流石にデータをDBに入れるとか登録完了メール送信とかはスコープ外です。

URLRule

まずはFormFlow用にURLRuleを作成します。
Ruleは簡単で、同じURLをGET、POSTで作れば良いです。
GETは最初のページ(input)用でPOSTはそれ以降のページ(confirm、complete)用です。

package com.astamuse.blog_sample.rules;

import static com.astamuse.asta4d.web.dispatch.HttpMethod.GET;
import static com.astamuse.asta4d.web.dispatch.HttpMethod.POST;

import com.astamuse.asta4d.web.dispatch.mapping.UrlMappingRuleInitializer;
import com.astamuse.asta4d.web.dispatch.mapping.ext.UrlMappingRuleHelper;
import com.astamuse.blog_sample.handler.TestFormHandler;

public class UrlRules implements UrlMappingRuleInitializer{
    public void initUrlMappingRules(UrlMappingRuleHelper rules) {
        rules.add(GET, "/form").handler(TestFormHandler.class);
        rules.add(POST, "/form").handler(TestFormHandler.class);
    }
}

HandlerとSnippet

基本的な実装はフレームワーク側で提供されているので、必要なものだけ追加実装すれば問題ありません。
それぞれ説明していきます。

Handler

HandlerはClassicalMultiStepFormFlowHandlerTrait<T>を実装します。
Tはクライアント側から来るデータのデータ型を指定します。
データ型については後ほど書きます。

実装については幾つかの必須の実装と任意の実装があります。
軽くメソッドの説明をしてから全部の実装を載せます。

それではまずはメソッドの説明から。

public Class<T> getFormCls();

実装しなければなりません。
T型のクラスを返却すれば大丈夫です。

public String getTemplateBasePath();

実装しなければなりません。
FormのHtmlTemplateのSuffixを文字列で返却します。
細かいルールはHTMLの説明の時に書きます。

public void updateForm(T form);

実装しなければなりません。
クライアント側からのデータが渡ってくるのでDBへの保存などなどデータ型に対する処理を全て行ってください。
ここはconfirmのページからPOSTされた時にのみ呼ばれます。

public boolean treatCompleteStepAsExit();

任意の実装です。
完了画面を最後に表示するかのフラグを返却するメソッドでデフォルトの実装はtrueで表示しないとなっています。
今回は表示するとしたいと思うのでfalseを返却するよう実装を変更します。
ちなみにtrueの場合はURLRuleのPOSTに処理完了後のredirect処理を入れてあげてください。
ルールで設定すれば大丈夫です。

public FormValidator getValueValidator();

任意の実装です。
FormFlowのvalidation処理はBeanValidationで行うようになっていますが、カスタムクラスを実装する場合はここに独自で実装したクラスを返却するようにしてください。
今回はカスタム方法についても紹介するので、作成するクラスを返却するよう実装を変更します。

public TestForm createInitForm()

任意の実装です。
初期のフォームに表示するデータを返却します。
編集のフォームの時は今持っているデータを初期値として描画するや、途中離脱したユーザーに対して書いたデータを描画するといった使い方が出来ます。
今回はそういうのは無しなので、何も値を設定していないクラスを返却するようにします。

そんな実装をするとこんな感じになります。 updateFormは特に今回することがないので実装を省いてます。

package com.astamuse.blog_sample.handler;

import com.astamuse.asta4d.web.form.flow.classical.ClassicalMultiStepFormFlowHandlerTrait;
import com.astamuse.asta4d.web.form.validation.FormValidator;
import com.astamuse.blog_sample.form.TestForm;
import com.astamuse.blog_sample.utils.FormJsrValidator;

public class TestFormHandler implements ClassicalMultiStepFormFlowHandlerTrait<TestForm> {
    public Class<TestForm> getFormCls() {
        return TestForm.class;
    }

    public String getTemplateBasePath() {
        return "/html/form-";
    }

    public void updateForm(TestForm form) {
    }

    public boolean treatCompleteStepAsExit() {
        return false;
    }

    public FormValidator getValueValidator() {
        return new FormJsrValidator();
    }

    public TestForm createInitForm() {
        return new TestForm();
    }
}

Snippet

ClassicalMultiStepFormFlowSnippetTraitを実装します。
ただ、基本的にはすべてinterface側での実装で問題ないため、自分で実装する箇所はありません。

package com.astamuse.blog_sample.snippet;

import com.astamuse.asta4d.web.form.flow.classical.ClassicalMultiStepFormFlowSnippetTrait;

public class TestFormSnippet implements ClassicalMultiStepFormFlowSnippetTrait {

}

データ型

クライアントから来るデータのデータ型クラスについての説明です。
今回のFormでは名前ととメールアドレスをサーバー側に送信するというようなFormなので、データ型としてnameとmailを変数として用意します。

そんな訳で実装しました。

package com.astamuse.blog_sample.form;

import org.hibernate.validator.constraints.NotBlank;

import com.astamuse.asta4d.web.form.annotation.Form;
import com.astamuse.asta4d.web.form.annotation.renderable.Input;

@Form
public class TestForm {
    private String name;
    private String mail;

    @Input(nameLabel="名前")
    @NotBlank
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Input(nameLabel="メールアドレス")
    @NotBlank
    public String getMail() {
        return mail;
    }
    public void setMail(String mail) {
        this.mail = mail;
    }
}

と、これだけでは説明が不足しているので説明します。

@Form

FormFlowのデータ型クラスにはこのアノテーションが必須です。

@Input(nameLabel="名前")

このアノテーションはHTMLとデータ型の紐づけに必要なアノテーションです。
Inputタグであれば@Input、Selectタグであれば@Selectのようにタグと合わせたアノテーションを付けることで紐づけを行います。
他にもFormで使いそうなアノテーションはそろってるので、用途に合わせて使ってください。

今回、nameLabelを付けているのは、validationのためです。
説明は後ほどしますので、ここでは割愛します。

@NotBlank

こっちのアノテーションはValidation用のアノテーションです。
必要なアノテーションを付けておけば勝手にValidationしてくれるようになっています。
もちろん、Handlerの実装を変更して他のValidationを行うことや拡張することも可能となっています。

Validation

Validationはasta4dで用意しているJsrValidatorクラスがデフォルトでは使用されるようになっていますが、メッセージの形式の変更とか色々行いたいことが多くデフォルトだと日本語の対応も微妙なので、カスタムクラスを作成するようにしてます。 メッセージの変更はcreateMessageクラスの実装を変更すれば良いです。
データ型で設定したnameLabelは引数のfieldLabelに入ってきます。

この実装は必須ではないので、問題なければ実装しなくても大丈夫です。

ちなみにこのValidation機能のためにpom.xmlにhibernate-validatorを追加してあげる必要があります。
細かい仕様などは長くなりそうなので割愛しますので、別途調べていただけますと幸いです。

package com.astamuse.blog_sample.utils;

import javax.validation.Validation;
import javax.validation.Validator;

import org.apache.commons.lang3.StringUtils;

import com.astamuse.asta4d.web.form.validation.JsrValidator;

public class FormJsrValidator extends JsrValidator {

    //@formatter:off
    private static Validator validator = Validation.byDefaultProvider()
                                                    .configure()
                                                    .messageInterpolator(
                                                        new Asta4DIntegratedResourceBundleInterpolator(
                                                            new Asta4DResourceBundleFactoryAdapter("FormFlowValidationMessages")
                                                        )
                                                     )
                                                    .buildValidatorFactory()
                                                    .getValidator();
    //@formatter:on

    public FormJsrValidator() {
        this(true);
    }

    public FormJsrValidator(boolean addFieldLablePrefixToMessage) {
        super(validator, addFieldLablePrefixToMessage);
    }

    @SuppressWarnings("rawtypes")
    @Override
    protected String createAnnotatedMessage(Class formCls, String fieldName, String fieldLabel, String annotatedMsg) {
        return annotatedMsg;
    }

    @SuppressWarnings("rawtypes")
    @Override
    protected String createMessage(Class formCls, String fieldName, String fieldLabel, String fieldTypeName, String cvMsg) {
        if (addFieldLablePrefixToMessage && StringUtils.isNotBlank(fieldLabel)) {
            String msgTemplate = "%s : %s";
            return String.format(msgTemplate, fieldLabel, cvMsg);
        } else {
            return cvMsg;
        }
    }
}

HTML

今回のHTMLとしてはFormFlowのデフォルトであるinput -> confirm -> completeのフローを表示するため3つ作成します。

htmlファイル名はHandlerのgetTemplateBasePath()で返却するパス+各step名とする必要があります。
今回のケースであればform-input.html form-confirm.html form-complete.htmlと命名したhtmlの作成を行います。

各ファイルの中身はinput、confirmとcompleteで違います。
completeは最終ページなのでただの完了ページとなるため、好きな感じで良いです。 inputとconfirmに関してはルールの元に作成する必要があります。

  • formタグのmethodはPOSTにします
    • actionは設定しなくて良いです(そういうルールですし)
  • form送信時にstepの情報を入れて送信する必要があります
    • 特に表示する必要はないのでinput hiddenで作成します
    • name="step-current"のvalueに現在のstep(input or confirm)を設定します
    • name="step-failed"のvalueにサーバーサイドで失敗した際に行くstepを設定します
      • 今回は元のstepに戻るとします
  • 入力タグのnameでデータ型と紐づくので、nameを設定してください
    • アノテーション、変数名とhtml側の整合が取れている必要があります
  • confirmもinputタグとしていますが、Snippetで自動で置き換わります。nameが同じであれば問題ありません
    • 使いまわさない場合は、idに<変数名>-displayを設定したタグの箇所に描画してくれます
    • 今回は使いまわす形でhtmlを書いています
  • validationエラーの場合は<変数名>-err-msgをidに設定した箇所に描画してくれるので、タグだけ用意しておいてください
    • エラー時の戻り先として"step-failed"に設定した場所に存在する必要があります
  • Snippetの設定は入力箇所を対象となるよう設定してください

とまぁ、こんな感じでルールがあります。
色々と書きましたが、実際のHTMLは簡単です。
上のルールと照らし合わせながら見れば分かるレベルかと思います。

</html/form-input.html>

<html>
<head><meta charset="utf-8" /></head>
<body>
<div>
  <form method="POST">
    <div>
      <afd:embed target="/html/formContent.html"></afd:embed>
      <input type="hidden" name="step-current" value="input">
      <input type="hidden" name="step-failed" value="input">
      <button type="submit" name="step-success" value="confirm">送信
      </button>
    </div>
  </form>
</div>
</body>
</html>

</html/form-confirm.html>

<html>
<head><meta charset="utf-8" /></head>
<body>
<div>
  <form method="POST">
    <div>
      <afd:embed target="/html/formContent.html"></afd:embed>
      <input type="hidden" name="step-current" value="confirm">
      <input type="hidden" name="step-failed" value="confirm">
      <button type="submit" name="step-success" value="complete">送信
      </button>
    </div>
  </form>
</div>
</body>
</html>

</html/form-complete.html>

<html><head><meta charset="utf-8" /></head><body>
    <div>
        完了
    </div>
</body></html>

</html/formContent.html>

<html>
<head><meta charset="utf-8" /></head>
<body>
  <afd:snippet render="TestFormSnippet">
  <ul>
    <li>
      <span>名前</span>
      <div>
        <p><strong id="name-err-msg"></strong></p>
        <input type="text" placeholder="名前" name="name">
      </div>
    </li>
    <li>
      <span>メールアドレス</span>
      <div>
        <p><strong id="mail-err-msg"></strong></p>
        <input type="text" placeholder="test@astamuse.com" name="mail">
      </div>
    </li>
  </ul>
  </afd:snippet>
</body>
</html>

パーツの使いまわしについては機能的な紹介のために使用しましたが、実際の使用場面では別々になるかと思います。
しかし、そこもasta4dの良さをそこなわず対応することが可能となっていますので、

最後に

これで基本的なところは全て解説できたかと思います。
結構使いやすいフレームワークだと思いますので、興味を持った方はぜひとも使って世の中に広めていただければと思います。  

Copyright © astamuse company, ltd. all rights reserved.