かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その14 )( browser-sync --> Tomcat 連携してファイル変更時に自動リロードで反映される環境を構築してみる )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その13 )( HTML を Thymeleaf テンプレートファイルにする + Controller クラスを作成する2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • browser-sync –> Tomcat 連携して、ファイルを変更したらブラウザ側で自動リロードして反映される環境を構築できるか試してみます。
    • 尚、Tomcat の起動は JRebel で行っています。JRebel は personal, non-commercial use only なら free な myJRebel があります。
    • devltools + bootRun タスクで Tomcat を起動したらどうなるかも まとめ に書きました。
    • 結論を言うと完全自動リロードする環境は構築可能でしたが、その場合無駄な動作をして開発効率が上がる気がしなかったので、Thymeleaf テンプレートファイルや java ファイルは手動 build することにしました。

参照したサイト・書籍

  1. Spring Boot + Gradle on Intellij IDEA でアプリケーション実行中にコードの変更反映と Thymeleaf テンプレートの変更反映
    http://bufferings.hatenablog.com/entry/2017/07/24/010551

  2. Gulp browser-sync - redirect API request via proxy
    https://stackoverflow.com/questions/25410284/gulp-browser-sync-redirect-api-request-via-proxy

  3. http-proxy-middleware
    https://www.npmjs.com/package/http-proxy-middleware

  4. chimurai/http-proxy-middleware - Impossible to ignore proxy path prefix
    https://github.com/chimurai/http-proxy-middleware/issues/31

  5. Browsersync options
    https://browsersync.io/docs/options

  6. Chokidar
    https://github.com/paulmillr/chokidar

目次

  1. browser-sync の proxy 機能で Tomcat と連携してみる
    1. bs-springboot-config.js を新規作成する
    2. package.json に起動用の npm-scripts を追加する
    3. 自動リロードされるか確認する(js ファイル編)
    4. 自動リロードされるか確認する(Thymeleaf テンプレートファイル編)
    5. 自動リロードされるか確認する(class ファイル編)
    6. ここまでの結果をまとめる
  2. Tomcat 起動中でも自動 build されるよう IntelliJ IDEA の設定を変更して、自動リロードされるか確認する
    1. Tomcat 起動中でも自動 build されるよう IntelliJ IDEA の設定を変更する
    2. 自動リロードされるか確認する(Thymeleaf テンプレートファイル編)
    3. 自動リロードされるか確認する(class ファイル編)
    4. 結果をまとめる
  3. js ファイルは Tomcat からではなく browser-sync から直接返すようにしてみる
    1. 特定の URL だけ Tomcat へ転送せずに browser-sync から直接返すことができるのか?
    2. http-proxy-middleware をインストールする
    3. bs-springboot-config.js を変更する
    4. 自動リロードされるか確認する(js ファイル編)
  4. build 時に browser-sync が落ちないように watchOptions を設定する
  5. まとめ

手順

browser-sync の proxy 機能で Tomcat と連携してみる

browser-sync には proxy 機能がありますので、リクエストを全て Tomcat へ転送して自動リロードされる環境を構築できるか試してみます。

bs-springboot-config.js を新規作成する

bs-config.js をコピーして bs-springboot-config.js を作成した後、以下の点を変更します。

/*
 |--------------------------------------------------------------------------
 | Browser-sync config file
 |--------------------------------------------------------------------------
 |
 | For up-to-date information about the options:
 |   http://www.browsersync.io/docs/options/
 |
 | There are more options than you see here, these are just the ones that are
 | set internally. See the website for more info.
 |
 |
 */
module.exports = {
    "ui": {
        "port": 3001,
        "weinre": {
            "port": 9081
        }
    },
    "files": [
        "./build/classes/**/*"
    ],
    "watchEvents": [
        "change"
    ],
    "watchOptions": {
        "ignoreInitial": true
    },
    "server": false,
    "proxy": "localhost:8080",
    "port": 9080,
    ..........
  • Tomcat は build/classes の下のファイルを見ているようなので、このディレクトリの下のファイルの変更を監視します。files の設定を "files": [ "./build/classes/**/*" ] に変更します。
  • browser-sync からは直接ファイルを返信しなくなるので、server の設定を "server": false に変更します。
  • "proxy": false"proxy": "localhost:8080" に変更します。

package.json に起動用の npm-scripts を追加する

package.json に bs-springboot-config.js を使用して browser-sync を起動するための npm-scripts を追加します。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "run-s clean:static-dir copy:all",
    "clean:static-dir": "rimraf src/main/resources/static/*",
    "copy:all": "run-p copy:bootstrap copy:admin-lte copy:font-awesome copy:ionicons",
    "copy:bootstrap": "cpx node_modules/bootstrap/dist/**/* src/main/resources/static/vendor/bootstrap",
    "copy:admin-lte": "cpx node_modules/admin-lte/dist/**/* src/main/resources/static/vendor/admin-lte",
    "copy:font-awesome": "cpx node_modules/font-awesome/{css,fonts}/**/* src/main/resources/static/vendor/font-awesome",
    "copy:ionicons": "cpx node_modules/ionicons/dist/{css,fonts}/**/* src/main/resources/static/vendor/ionicons",
    "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll",
    "webpack": "webpack",
    "webpack:watch": "webpack --watch",
    "browser-sync": "browser-sync",
    "browser-sync:start": "browser-sync start --config bs-config.js",
    "browser-sync:springboot": "browser-sync start --config bs-springboot-config.js",
    "server": "run-p postcss:watch webpack:watch browser-sync:start",
    "springboot": "run-p postcss:watch webpack:watch browser-sync:springboot"
  },
  • 以下の2行を追加します。
    • "browser-sync:springboot": "browser-sync start --config bs-springboot-config.js"
    • "springboot": "run-p postcss:watch webpack:watch browser-sync:springboot"

これで npm run springboot コマンドを実行すれば browser-sync –> Tomcat 連携するようになります。

自動リロードされるか確認する(js ファイル編)

Tomcat を起動してから npm run springboot コマンドを実行します。

ブラウザから http://localhost:9080/inquiry/input/01/ にアクセスして入力画面1を表示した後(Tomcat ではなく browser-sync にアクセスするのでポート番号は 8080 ではなく bs-springboot-config.js に設定している 9080 になります)、js ファイル、Thymeleaf テンプレート、class ファイルを更新してみます。

まずは js ファイルから。src/main/assets/js/inquiry/input01.js を以下のように変更します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    $("h1").text("テストです");

    // 動作確認のために初期表示時に「次へ」ボタンをクリック可能にする
    $(".js-btn-next").prop("disabled", false);

    $(".js-btn-next").on("click", function (event) {
        $("#input01Form").attr("action", "/inquiry/input/01/?move=next");
        $("#input01Form").submit();

        // return false は
        // event.preventDefault() + event.stopPropagation() らしい
        return false;
    })
});
  • $("h1").text("テストです"); を追加します。

webpack が変更を検知して src/main/resources/static/js/inquiry/input01.js を出力しますが、

f:id:ksby:20170811205629p:plain

ブラウザに表示している入力画面1のタイトルは変更されませんでした。自動で build/classes/main/static/js/inquiry/input01.js の方に変更が反映されないので当然でした。

f:id:ksby:20170811205811p:plain

反映するために Ctrl+F9 を押して build したら npm run springboot コマンドが落ちました。。。 IntelliJ IDEA の方で Parsing java ... が表示されて数秒経過しているうちに npm run springboot コマンドがエラーで落ちるようです。何度試してみても Parsing java ... が表示されるとダメでした。js ファイルは webpack が出力するので Ctrl+Shift+F9 でそのファイルだけ build するという訳にもいきませんし、どうしたものでしょうか。。。

自動リロードされるか確認する(Thymeleaf テンプレートファイル編)

次は Thymeleaf テンプレートファイルで試してみます。npm run springboot コマンドを実行し直して、ブラウザでは入力画面1を表示しておきます。

src/main/resources/templates/web/inquiry/input01.html を以下のように変更します。

    <section class="content-header">
      <h1>
        テストです2
      </h1>
    </section>
  • h1 タグ内のテキストを 入力画面1テストです2 に変更します。

この時点でブラウザに切り替えても入力画面1の画面名の部分は何も変わりませんでした。何もしなければ Thymeleaf テンプレートファイルの変更は build/classes/main/templates/web/inquiry/input01.html に反映されないので当然でした。

Ctrl+F9 を押して build します。Reloading Browsers... のメッセージがいくつも出力されますが、

f:id:ksby:20170812074954p:plain

自動リロードされて画面名が テスト2 に変わりました。

f:id:ksby:20170812075104p:plain

この後何度か画面名を変更して Ctrl+F9 を押してみましたが、常に反映されました。Ctrl+F9 を押す手間はありますが、変更検知してブラウザに反映されます。

自動リロードされるか確認する(class ファイル編)

最後に class ファイルで試してみます。npm run springboot コマンドを実行し直して、ブラウザでは入力画面1を表示しておきます。

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java を以下のように変更します。

    /**
     * 入力画面1 初期表示処理
     *
     * @return 入力画面1の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping("/01")
    public String input01() {
        return TEMPLATE_INPUT02;
    }
  • TEMPLATE_INPUT01TEMPLATE_INPUT02 に変更します。

IntelliJ IDEA では通常自動コンパイルされないので、この時点では画面は何も変わりません。

Ctrl+F9 を押して build します。大量の Reloading Browsers... が出力されますが、

f:id:ksby:20170812080832p:plain f:id:ksby:20170812080947p:plain f:id:ksby:20170812081056p:plain f:id:ksby:20170812081220p:plain f:id:ksby:20170812081321p:plain

自動リロードされて入力画面2の画面が表示されました。

f:id:ksby:20170812081429p:plain

Controller クラスが返す画面を 入力画面1→入力画面2→入力画面1→… と何度か変更して Ctrl+F9 を押してみましたが、browser-sync は落ちることなくブラウザの画面に反映されました。Ctrl+F9 を押す手間がありますが、こちらも変更検知してブラウザに反映されるようです。

ここまでの結果をまとめる

現時点の状況としては、

  • js ファイルは、変更した後で js ファイルからカーソルを外せば webpack が変更検知して出力し直してくれますが、その後に自動で Tomcat が見ている build/classes ディレクトリに反映されないので自動リロードされません。また Ctrl+F9 を押して build/classes の下に反映しても browser-sync が落ちてしまいます。
  • Thymeleaf テンプレートファイルと class ファイルは Ctrl+F9 を押す手間は必要ですが、build するとブラウザには自動リロードされて反映されます。Tomcat 起動中も自動 build されるよう設定すればよさそう?

Tomcat 起動中でも自動 build されるよう IntelliJ IDEA の設定を変更して、自動リロードされるか確認する

Tomcat 起動中でも自動 build されるよう IntelliJ IDEA の設定を変更する

Tomcat を起動していない場合に自動 build する方法は知っているのですが Tomcat 起動中でも自動 build する方法がないか調べたところ、Spring Boot + Gradle on Intellij IDEA でアプリケーション実行中にコードの変更反映と Thymeleaf テンプレートの変更反映 という記事を見つけました。ちょうどやりたいことと一致していたので、この記事の通り設定してみます。

IntelliJ IDEA のメインメニューから「File」-「Settings…」を選択します。

「Settings」ダイアログが表示されたら、画面左側の一覧から「Build, Execution, Deployment」-「Compiler」を選択した後、画面右側の「Build project automatically」をチェックします。チェック後「OK」ボタンをクリックしてダイアログを閉じます。

f:id:ksby:20170812093132p:plain

次に IntelliJ IDEA のメインメニューから「Help」-「Find Action…」を選択します。

「Enter action or option name」ダイアログが表示されますので、registry と入力して、表示された一覧から Registry... を選択します。

f:id:ksby:20170812093421p:plain

「Registry」ダイアログが表示されますので「compiler.automake.allow.when.app.running」をチェックします。チェック後「Close」ボタンをクリックしてダイアログを閉じます。

f:id:ksby:20170812094401p:plain

自動リロードされるか確認する(Thymeleaf テンプレートファイル編)

設定は以上で終了です。今度は Ctrl+F9 を押すことなしに自動リロードされるか試してみます。

Tomcat を起動してから npm run springboot コマンドを実行し、ブラウザで入力画面1を表示します。

先程と同様に src/main/resources/templates/web/inquiry/input01.html の h1 のテキストを テストです2 に変更します。が、ブラウザに反映されません。また Thymeleaf を編集しようとした時に、先程 java ファイルを変更して Ctrl+F9 を押して build した時と同様に大量の Reloading Browsers... のメッセージが出力されました。

Ctrl+F9 を押さなくても自動 build されて build/classes/main/templates/web/inquiry/input01.html に変更は反映されていたのですが、browser-sync が変更を検知できないようです。ブラウザで F5 を押して手動リロードすると画面上に変更が反映されました。

自動リロードされるか確認する(class ファイル編)

今度は src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java の input01 メソッドの戻り値を TEMPLATE_INPUT01TEMPLATE_INPUT02 に変更してみます。

こちらは自動でブラウザまで反映されました。ただしリロードが2回発生します。ブラウザのタブに表示される文字列を見ていると、1回目は入力画面1で、2回目は入力画面2でした。何度か試しても同じです。またリロードが発生するのは IntelliJ IDEA 内で別のファイルにタブを切り替えるか、別のプロセスに切り替える必要がありました。自動 build は編集しているファイルにカーソルがある間は実行されないようです。

f:id:ksby:20170812102639p:plain

結果をまとめる

IntelliJ IDEA で自動 build の設定をすると class ファイルの方は自動リロードされますが、Thymeleaf テンプレートファイルの方は手動リロードが必須でした。また少し多めにリロードが発生するのが気になりました。一旦自動 build の設定は元に戻します。

js ファイルは Tomcat からではなく browser-sync から直接返すようにしてみる

特定の URL だけ Tomcat へ転送せずに browser-sync から直接返すことができるのか?

js ファイルを変更すれば webpack の watch 機能で変更検知されて自動で src/main/resources/static/js の下に出力されることは既に分かっており、build/classes/main/static の下のファイルは src/main/resources/static の下のファイルをコピーしているだけであること、src/main/resources/static の下のファイルが変更されたら browser-sync が変更検知して自動リロードしてくれることは既に確認していることから、js ファイルは Tomcat へ転送せずに browser-sync から直接返すようにすれば Ctrl+F9 を押す手間がなくなるのではないか?、と思いました。

browser-sync でそのような設定が可能なのか調べてみたところ、以下の記事を見つけました。browser-sync の proxy 機能は使用せずに、http-proxy-middleware パッケージを使用すれば実現できそうなので、試してみることにします。

http-proxy-middleware をインストールする

npm install --save-dev http-proxy-middleware コマンドを実行してインストールします。

f:id:ksby:20170812150752p:plain

bs-springboot-config.js を変更する

bs-springboot-config.js の以下の点を変更します。

var httpProxyMiddleware = require('http-proxy-middleware');
var proxy = httpProxyMiddleware(
    [
        // /css, /js, /vendor と *.html は Tomcat に転送しない
        "!/css/**/*",
        "!/js/**/*",
        "!/vendor/**/*",
        "!/**/*.html",
        "/**/*"
    ],
    {target: "http://localhost:8080"}
);

/*
 |--------------------------------------------------------------------------
 | Browser-sync config file
 |--------------------------------------------------------------------------
 |
 | For up-to-date information about the options:
 |   http://www.browsersync.io/docs/options/
 |
 | There are more options than you see here, these are just the ones that are
 | set internally. See the website for more info.
 |
 |
 */
module.exports = {
    "ui": {
        "port": 3001,
        "weinre": {
            "port": 9081
        }
    },
    "files": [
        "./build/classes/**/*.class",
        "./build/classes/**/*.html",
        "./static/**/*",
        "./src/main/resources/static/**/*"
    ],
    "watchEvents": [
        "change"
    ],
    "watchOptions": {
        "ignoreInitial": true
    },
    "server": {
        "baseDir": [
            "./static",
            "./src/main/resources/static"
        ],
        "middleware": [proxy]
    },
    "proxy": false,
    "port": 9080,
    ..........
  • ファイルの先頭に以下の2行を追加します。
    • var httpProxyMiddleware = require('http-proxy-middleware');
    • var proxy = httpProxyMiddleware(["!/css/**/*", "!/js/**/*", "!/vendor/**/*", "!/**/*.html", "/**/*"], {target: "http://localhost:8080"});
  • "files": ["./build/classes/**/*"]"files": ["./build/classes/**/*.class", "./build/classes/**/*.html", "./static/**/*", "./src/main/resources/static/**/*"] に変更します。
  • "server": false"server": {"baseDir": ["./static", "./src/main/resources/static"], "middleware": [proxy]} に変更します。
  • "proxy": "localhost:8080""proxy": false に変更します。

自動リロードされるか確認する(js ファイル編)

js ファイル変更時に自動リロードされるか試してみます。

Tomcat を起動してから npm run springboot コマンドを実行し、ブラウザで入力画面1を表示します。

src/main/assets/js/inquiry/input01.js に $("h1").text("テストです"); を追加します。ブラウザに切り替えると画面名が テストです にすぐに切り替わりました。

f:id:ksby:20170812155952p:plain

ただしこの後に Ctrl+F9 を押して build し、Parsing java ... のメッセージが数秒出る状態になると browser-sync が落ちました。。。 コマンドプロンプトに出力されているエラーメッセージを見ると Error: EPERM: operation not permitted, lstat 'c:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\build\classes\main\static\js\inquiry' と出力されています。おそらく build 時に lstat できなくなる場合があって、それに引っかかるとエラーで落ちてしまうものと思われます。

build 時に browser-sync が落ちないように watchOptions を設定する

build 時に browser-sync が落ちてしまうのを回避できる設定がないか Browsersync options のページを見たのですが、「watchOptions」のところに File watching options that get passed along to Chokidar. Check their docs for available options という記述と Chokidar へのリンクがありました。

Chokidar のページを見てみると ignorePermissionErrors という設定がありました。この項目の説明文に EPERM の記述があったので、おそらくこの設定でエラーを無視できると思われます。デフォルトでは false なので true にしてみます。

Tomcat を起動してから npm run springboot コマンドを実行し、ブラウザで入力画面1を表示します。

src/main/assets/js/inquiry/input01.js に $("h1").text("テストです"); を追加してから Ctrl+F9 を押して build を実行し Parsing java ... のメッセージが数秒出る状態を何度か再現してみましたが、今度は browser-sync がエラーで落ちることはありませんでした。

また Chokidar のページには usePolling というオプションもあり、ファイルの変更をポーリングでチェックできるようでした。IntelliJ IDEA で Tomcat 起動時にも自動 build できるように設定を変更して、browser-sync の watchOptions に "usePolling": true を追加してみたところ、Thymeleaf テンプレートファイルを変更したときも class ファイルを変更したときもどちらも自動リロードさせることができたのですが、class ファイル変更時に自動リロードが2回動くことと、js ファイルの時と比較するとあまり動きが軽快でないことが自分好みではなかったので、このオプションは使用しないことにします。usePolling を使用しなくても Ctrl+F9 か Ctrl+Shift+F9(変更したファイルだけ build)で手動 build すれば自動リロードされるので、build は手動で行うことにします。

まとめ

  • browser-sync –> Tomcat 連携は可能。ただし Tomcat は JRebel で起動すること。使い勝手は申し分ない。
  • browser-sync –> Tomcat 連携は browser-sync の proxy 機能は使用せず http-proxy-middleware パッケージを使用する。
  • http-proxy-middleware パッケージを使用する理由は /css, /js 及び *.html にマッチする URL の場合には Tomcat に転送せず browser-proxy から直接返信するためである。その方が js ファイル変更 –> ブラウザの自動リロードまでのレスポンスが軽快で開発しやすい。
  • Thymeleaf テンプレートファイルと class ファイルは Tomcat から返信する。
  • IntelliJ IDEA の自動 build の設定は有効にしない。Thymeleaf テンプレートファイルや java ファイルを変更した時には Ctrl+F9 あるいは Ctrl+Shift+F9 で手動 build する。手動 build すると browser-sync がファイルの変更を検知してブラウザを自動リロードする。
  • browser-sync, Tomcat はどちらから起動してもよい。また途中でどちらかを再起動しても問題ない。
  • Tomcat を devtools + bootRun タスクで起動した場合、class ファイル変更時には自動リロードで反映されない。devtools がクラスローダーを再起動する時間が長く、class ファイルの変更を検知して自動リロードしようとしても Tomcat からの応答がタイムアウトして Error occured while trying to proxy to: localhost:9080/inquiry/input/01/ のようなエラーメッセージがブラウザ上に表示される。

最後に最終版の package.json と bs-springboot-config.js を載せておきます。

ソースコード

package.json

{
  "name": "boot-npm-geb-sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "run-s clean:static-dir copy:all",
    "clean:static-dir": "rimraf src/main/resources/static/*",
    "copy:all": "run-p copy:bootstrap copy:admin-lte copy:font-awesome copy:ionicons",
    "copy:bootstrap": "cpx node_modules/bootstrap/dist/**/* src/main/resources/static/vendor/bootstrap",
    "copy:admin-lte": "cpx node_modules/admin-lte/dist/**/* src/main/resources/static/vendor/admin-lte",
    "copy:font-awesome": "cpx node_modules/font-awesome/{css,fonts}/**/* src/main/resources/static/vendor/font-awesome",
    "copy:ionicons": "cpx node_modules/ionicons/dist/{css,fonts}/**/* src/main/resources/static/vendor/ionicons",
    "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll",
    "webpack": "webpack",
    "webpack:watch": "webpack --watch",
    "browser-sync": "browser-sync",
    "browser-sync:start": "browser-sync start --config bs-config.js",
    "browser-sync:springboot": "browser-sync start --config bs-springboot-config.js",
    "server": "run-p postcss:watch webpack:watch browser-sync:start",
    "springboot": "run-p postcss:watch webpack:watch browser-sync:springboot"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "admin-lte": "^2.3.11",
    "bootstrap": "^3.3.7",
    "font-awesome": "^4.7.0",
    "ionicons": "^3.0.0"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "browser-sync": "^2.18.13",
    "cpx": "^1.5.0",
    "cssnano": "^3.10.0",
    "http-proxy-middleware": "^0.17.4",
    "npm-run-all": "^4.0.2",
    "postcss-cli": "^4.1.0",
    "rimraf": "^2.6.1",
    "stylelint": "^8.0.0",
    "stylelint-config-standard": "^17.0.0",
    "webpack": "^3.3.0"
  }
}

bs-springboot-config.js

var httpProxyMiddleware = require('http-proxy-middleware');
var proxy = httpProxyMiddleware(
    [
        // /css, /js, /vendor と *.html は Tomcat に転送しない
        "!/css/**/*",
        "!/js/**/*",
        "!/vendor/**/*",
        "!/**/*.html",
        "/**/*"
    ],
    {target: "http://localhost:8080"}
);

/*
 |--------------------------------------------------------------------------
 | Browser-sync config file
 |--------------------------------------------------------------------------
 |
 | For up-to-date information about the options:
 |   http://www.browsersync.io/docs/options/
 |
 | There are more options than you see here, these are just the ones that are
 | set internally. See the website for more info.
 |
 |
 */
module.exports = {
    "ui": {
        "port": 3001,
        "weinre": {
            "port": 9081
        }
    },
    "files": [
        "./build/classes/**/*.class",
        "./build/classes/**/*.html",
        "./static/**/*",
        "./src/main/resources/static/**/*"
    ],
    "watchEvents": [
        "change"
    ],
    "watchOptions": {
        "ignoreInitial": true,
        "ignorePermissionErrors": true
    },
    "server": {
        "baseDir": [
            "./static",
            "./src/main/resources/static"
        ],
        "middleware": [proxy]
    },
    "proxy": false,
    "port": 9080,
    "middleware": false,
    "serveStatic": [],
    "ghostMode": {
        "clicks": true,
        "scroll": true,
        "location": true,
        "forms": {
            "submit": true,
            "inputs": true,
            "toggles": true
        }
    },
    "logLevel": "info",
    "logPrefix": "Browsersync",
    "logConnections": false,
    "logFileChanges": true,
    "logSnippet": true,
    "rewriteRules": [],
    "open": "local",
    "browser": "default",
    "cors": false,
    "xip": false,
    "hostnameSuffix": false,
    "reloadOnRestart": false,
    "notify": false,
    "scrollProportionally": true,
    "scrollThrottle": 0,
    "scrollRestoreTechnique": "window.name",
    "scrollElements": [],
    "scrollElementMapping": [],
    "reloadDelay": 0,
    "reloadDebounce": 0,
    "reloadThrottle": 0,
    "plugins": [],
    "injectChanges": true,
    "startPath": null,
    "minify": true,
    "host": null,
    "localOnly": false,
    "codeSync": true,
    "timestamps": true,
    "clientEvents": [
        "scroll",
        "scroll:element",
        "input:text",
        "input:toggles",
        "form:submit",
        "form:reset",
        "click"
    ],
    "socket": {
        "socketIoOptions": {
            "log": false
        },
        "socketIoClientConfig": {
            "reconnectionAttempts": 50
        },
        "path": "/browser-sync/socket.io",
        "clientPath": "/browser-sync",
        "namespace": "/browser-sync",
        "clients": {
            "heartbeatTimeout": 50000
        }
    },
    "tagNames": {
        "less": "link",
        "scss": "link",
        "css": "link",
        "jpg": "img",
        "jpeg": "img",
        "png": "img",
        "svg": "img",
        "gif": "img",
        "js": "script"
    }
};

履歴

2017/08/12
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その13 )( HTML を Thymeleaf テンプレートファイルにする + Controller クラスを作成する2 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その12 )( HTML を Thymeleaf テンプレートファイルにする + Controller クラスを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 前回からの続きです。今回は確認画面、完了画面の Thymeleaf テンプレートファイルと Controller クラスを実装します。

参照したサイト・書籍

目次

  1. 確認画面の Controller クラスを作成する
  2. 完了画面の Controller クラスを作成する
  3. Thymeleaf テンプレートファイルを変更する
  4. 画面遷移用の js ファイルを実装する
  5. 動作確認
  6. 次回は。。。

手順

確認画面の Controller クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryConfirmController.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry;

import ksbysample.webapp.bootnpmgeb.constants.UrlConst;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * 確認画面用 Controller クラス
 */
@Controller
@RequestMapping("/inquiry/confirm")
public class InquiryConfirmController {

    private static final String TEMPLATE_BASE = "web/inquiry";
    private static final String TEMPLATE_CONFIRM = TEMPLATE_BASE + "/confirm";

    /**
     * 確認画面 初期表示処理
     *
     * @return 確認画面の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping
    public String index() {
        return TEMPLATE_CONFIRM;
    }

    /**
     * 確認画面 「送信する」ボタンクリック時の処理
     *
     * @return 完了画面の URL
     */
    @PostMapping("/send")
    public String send(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_COMPLETE).toUriString();
    }

}

完了画面の Controller クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryCompleteController.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 完了画面用 Controller クラス
 */
@Controller
@RequestMapping("/inquiry/complete")
public class InquiryCompleteController {

    private static final String TEMPLATE_BASE = "web/inquiry";
    private static final String TEMPLATE_COMPLETE = TEMPLATE_BASE + "/complete";

    /**
     * 完了画面 初期表示処理
     *
     * @return 完了画面の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping
    public String index() {
        return TEMPLATE_COMPLETE;
    }

}

Thymeleaf テンプレートファイルを変更する

完了画面は js ファイルを作成する必要がなかったので、以下の点を変更します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 完了画面</title>
</head>

<body class="skin-blue layout-top-nav">
<div class="wrapper">

  <!-- Content Wrapper. Contains page content -->
  <div class="content-wrapper">
    <!-- Content Header (Page header) -->
    <section class="content-header">
      <h1>
        完了画面
      </h1>
    </section>

    <!-- Main content -->
    <section class="content">
      <div class="row">
        <div class="col-xs-12">
          <div class="text-center">
            <p>お問い合わせありがとうございました。</p>
            <a href="/inquiry/input/01/"><button class="btn bg-green"><i class="fa fa-reply"></i> 入力画面へ</button></a>
          </div>
        </div>
      </div>
    </section>
    <!-- /.content -->
  </div>
  <!-- /.content-wrapper -->
</div>
<!-- ./wrapper -->

<!-- REQUIRED JS SCRIPTS -->

</body>
</html>
  • 「入力画面へ」ボタンのリンクを <a href="/inquiry/input01.html"><a href="/inquiry/input/01/"> へ変更します。
  • <script src="/js/inquiry/complete.js"></script> を削除します。

画面遷移用の js ファイルを実装する

webpack.config.js を以下のように変更します。

module.exports = {
    entry: {
        "js/app": ["./src/main/assets/js/app.js"],
        "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
        "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"],
        "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"],
        "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"]
    },
    output: {
        path: __dirname + "/src/main/resources/static",
        publicPath: "/",
        filename: "[name].js"
    }
};
  • "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"] を追加します。

js ファイル実装前に npm run server コマンドを実行します。起動していた場合には一旦終了させてから実行し直します。

src/main/assets/js/inquiry の下に confirm.js を新規作成し、以下の内容を記述します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    $(".js-btn-input01").on("click", function (event) {
        location.href = "/inquiry/input/01/";
        return false;
    });

    $(".js-btn-input02").on("click", function (event) {
        location.href = "/inquiry/input/02/";
        return false;
    });

    $(".js-btn-input03").on("click", function (event) {
        location.href = "/inquiry/input/03/";
        return false;
    });

    $(".js-btn-send").on("click", function (event) {
        $("#confirmForm").attr("action", "/inquiry/confirm/send/");
        $("#confirmForm").submit();
        return false;
    });
});

動作確認

Tomcat を起動します。

ブラウザを起動し http://localhost:8080/inquiry/input/03/ にアクセスして入力画面3を表示します。

f:id:ksby:20170811133503p:plain

「確認画面へ」ボタンをクリックすると確認画面へ遷移します。アドレスバーに表示される URL は http://localhost:8080/inquiry/confirm/ に変わっています。

f:id:ksby:20170811133748p:plain

「送信する」ボタンをクリックすると完了画面へ遷移します。アドレスバーに表示される URL は http://localhost:8080/inquiry/complete/ に変わっています。

f:id:ksby:20170811133943p:plain

「入力画面へ」ボタンをクリックすると入力画面1へ遷移すること、確認画面で「修正する」ボタンをクリックすると入力画面1~3の画面へ遷移することも確認できました。

npm run server コマンド、Tomcat を停止します。

次回は。。。

  • browser-sync の自動リロードような利便性を Tomcat で起動した時にも欲しいなあと思いました。browser-sync の proxy 機能を利用して browser-sync –> Tomcat 連携することで自動リロードできないか試してみます。
  • その後で Flyway によるテーブル作成処理を実装します。

履歴

2017/08/11
初版発行。
2017/08/13
* DB のマイグレーションツールを Liquibase → Flyway に変更しました。
2017/09/02
<html xmlns:th="http://www.w3.org/1999/xhtml"> と間違えて記述していた箇所があったので <html xmlns:th="http://www.thymeleaf.org"> へ修正した。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その12 )( HTML を Thymeleaf テンプレートファイルにする + Controller クラスを作成する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その11 )( PostCSS で common.css を minify する + autoprefixer, stylelint を導入する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 作成した HTML ファイルを Thymeleaf テンプレートファイルにします。
    • Controller クラスを作成し、画面遷移に必要な js ファイルも実装して、画面の表示と単純な画面遷移ができるようにします。
    • 2回に分けます。今回は入力画面1~3まで実装します。

参照したサイト・書籍

  1. HTML5 & CSS3 デザインレシピ集

    HTML5 & CSS3 デザインレシピ集

    HTML5 & CSS3 デザインレシピ集

  2. HTML5 Form Validation のカスタマイズ
    http://www.girliemac.com/blog/2012/12/01/html5-form-validation-jp/

  3. WAI-ARIAを活用したフロントエンド実装 - 第1回 role属性、aria属性の基礎知識
    https://app.codegrid.net/entry/wai-aria-1

  4. Spring Bootでリダイレクト先のURLを組み立てる
    http://qiita.com/rubytomato@github/items/8d132dec042f695e50f6

  5. Guide to UriComponentsBuilder in Spring
    http://www.baeldung.com/spring-uricomponentsbuilder

  6. jQueryイベントハンドラでreturn falseするとイベントのバブリングが止まる
    http://webtech-walker.com/archive/2012/09/event_handler_return_false.html

目次

  1. HTML ファイルを少し修正する
  2. Thymeleaf を 3.0.6 → 3.0.7 へバージョンアップする
  3. HTML ファイルから Thymeleaf テンプレートファイルを作成する
  4. ksbysample-webapp-lending から config ディレクトリをコピーする
  5. URL を定義する定数クラスを作成する
  6. Controller クラスを作成する
  7. 画面遷移用の js ファイルを実装する
  8. 動作確認
  9. メモ書き

手順

HTML ファイルを少し修正する

最近画面を作っているからか、ふと書店で HTML5 & CSS3 デザインレシピ集 という本を見かけて気になって読んでみて、また家に帰ってから Web でいろいろ調べて HTML5 Form Validation のカスタマイズ という記事を読んで気づきましたが、入力エラー時に入力項目に吹き出しが出る画面を見かけることがありますがあれば HTML5 + CSS3 の機能だったのかとか、HTML5 + CSS3 だけでも結構入力チェックできるんだなとか、自分には HTML5 + CSS3 に関する知識が結構無いんだな。。。とか。

また試しに required 属性を付けて試してみようとしたところ aria-required という属性が候補に表示されました。

f:id:ksby:20170806120707p:plain

aria- って何?と思い調べると WAI-ARIAを活用したフロントエンド実装 - 第1回 role属性、aria属性の基礎知識 の記事が見つかりました。そんな仕様が出来ているとは。。。 全然知りませんでした。

知らないことが多いなあと思いつつ、HTML5 & CSS3 デザインレシピ集 の本は購入したのでさっと読んでどんなことが出来るのかは理解しておこうと思います。

今回の画面では入力チェックは全て Javascript で実装して HTML5 + CSS3 では行いませんが、作成した HTML で autofocus 属性を付けておくのを忘れていたので、一番最初の項目に追加します。

例えば static/inquiry/input01.html の場合には、placeholder="例)田中" の後に autofocus を追加します。

            <!-- お名前(漢字) -->
            <div class="form-group" id="form-group-name">
              <div class="control-label col-sm-2">
                <label class="float-label">お名前(漢字)</label>
                <div class="label label-required">必須</div>
              </div>
              <div class="col-sm-10">
                <div class="row"><div class="col-sm-10">
                  <input type="text" name="lastname" id="lastname" class="form-control form-control-inline" style="width: 150px;" value="" placeholder="例)田中" autofocus/>
                  <input type="text" name="firstname" id="firstname" class="form-control form-control-inline" style="width: 150px;" value="" placeholder="例)太郎"/>
                </div></div>
                <div class="row hidden js-errmsg"><div class="col-sm-10"><p class="form-control-static text-danger"><small>ここにエラーメッセージを表示します</small></p></div></div>
              </div>
            </div>

画面を表示すると、以下の画像のように autofocus 属性を付けた入力項目にフォーカスがセットされます。

f:id:ksby:20170806114945p:plain

Thymeleaf を 3.0.6 → 3.0.7 へバージョンアップする

http://www.thymeleaf.org/ を見たところ、いつの間にか 3.0.7 がリリースされていました。build.gradle には 3.0.6 を記述していたので 3.0.7 へバージョンアップします。

build.gradle の dependencyManagement 内の記述を以下のように変更します。

dependencyManagement {
    imports {
        // BOM は https://repo.spring.io/release/io/spring/platform/platform-bom/Brussels-SR3/
        // の下を見ること
        mavenBom("io.spring.platform:platform-bom:Brussels-SR3") {
            bomProperty 'guava.version', '22.0'
            bomProperty 'thymeleaf.version', '3.0.7.RELEASE'
            bomProperty 'thymeleaf-extras-springsecurity4.version', '3.0.2.RELEASE'
            bomProperty 'thymeleaf-layout-dialect.version', '2.2.2'
            bomProperty 'thymeleaf-extras-data-attribute.version', '2.0.1'
            bomProperty 'thymeleaf-extras-java8time.version', '3.0.0.RELEASE'
        }
    }
}
  • bomProperty 'thymeleaf.version', '3.0.6.RELEASE'bomProperty 'thymeleaf.version', '3.0.7.RELEASE' に変更します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

HTML ファイルから Thymeleaf テンプレートファイルを作成する

src/main/resources/templates の下に web/common, web/inquiry の2つのディレクトリを新規作成します。

まずは src/main/resources/templates/web/common の下に共通部分定義用の fragments.html を作成します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="common_header(title, links, style)">
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title th:replace="${title} ?: _">画面名</title>
  <!-- Tell the browser to be responsive to screen width -->
  <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
  <!-- Bootstrap 3.3.6 -->
  <link rel="stylesheet" href="/vendor/bootstrap/css/bootstrap.min.css">
  <!-- Font Awesome -->
  <link rel="stylesheet" href="/vendor/font-awesome/css/font-awesome.min.css">
  <!-- Ionicons -->
  <link rel="stylesheet" href="/vendor/ionicons/css/ionicons.min.css">
  <!-- Theme style -->
  <link rel="stylesheet" href="/vendor/admin-lte/css/AdminLTE.min.css">
  <link rel="stylesheet" href="/vendor/admin-lte/css/skins/skin-blue.min.css">

  <!-- ここに各htmlで定義された link タグが追加される -->
  <th:block th:replace="${links} ?: _"/>

  <!-- 画面共通で使用する css -->
  <link rel="stylesheet" href="/css/common.min.css">

  <!-- ここに各htmlで定義された style タグが追加される -->
  <th:block th:replace="${style} ?: _"/>
</head>

<body class="skin-blue layout-top-nav">
<div class="wrapper">
</div>
</body>
</html>
  • このファイルも Thymeleaf テンプレートファイルなので、<html> ではなく <html xmlns:th="http://www.thymeleaf.org"> を記述します。
  • <head> タグには th:fragment="common_header(title, links, style)" を追加します。
  • <title> タグには th:replace="${title} ?: _" を追加します。
  • <!-- ここに各htmlで定義された link タグが追加される --> <th:block th:replace="${links} ?: _"/> を追加します。
  • <!-- ここに各htmlで定義された style タグが追加される --> <th:block th:replace="${style} ?: _"/> を追加します。

次に、src/main/resources/templates/web/inquiry の下に static/inquiry の下の全てのファイルをコピーした後、以下の点を変更します。

  • <html><html xmlns:th="http://www.thymeleaf.org"> に変更します。
  • <head><head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}"> に変更します。
  • <head> ... </head> 内は以下の記述のみ残します。
    • <title> ... </title>
    • 独自に定義した <link ...>
    • 独自に定義した <style> ... </style>
  • complete.html 以外は <form> タグの最後に th:action="@{/inquiry/input/01/}" を追加します(@{...} の中は自分の画面の URL にします)。これにより CSRF対策用のトークンが自動で挿入されます。

各ファイルの body タグより上の記述は以下のようになります。

■src/main/resources/templates/web/inquiry/input01.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 入力画面1</title>
</head>

■src/main/resources/templates/web/inquiry/input02.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 入力画面2</title>
</head>

■src/main/resources/templates/web/inquiry/input03.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 入力画面3</title>

  <style>
    /* 「チェックボックス複数行」の入力項目のチェックボックスを複数行に書くので、 */
    /* 異なる行のチェックボックスの位置を左揃えにする                         */
    @media (min-width: 768px) {
      #multiline-checkbox .checkbox label {
        display: block;
        float: left;
        width: 180px;
      }
    }
    @media (max-width: 767px) {
      #multiline-checkbox .checkbox label {
        display: block;
        float: left;
        width: 100%;
      }
    }
  </style>
</head>

■src/main/resources/templates/web/inquiry/confirm.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 確認画面</title>

  <style>
    /* セルの上部の罫線を表示しないようようにし、セル内の余白を詰める */
    .table > tbody> tr > th,
    .table > tbody> tr > td {
      border-top: none;
      padding: 5px;
    }
  </style>
</head>

■src/main/resources/templates/web/inquiry/complete.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 完了画面</title>
</head>

ksbysample-webapp-lending から config ディレクトリをコピーする

Project 作成時に config ディレクトリの下の checkstyle, findbugs, pmd 用の設定ファイルをコピーするのを忘れていました。ksbysample-webapp-lending から以下のファイルを config ディレクトリごとコピーします。

また IntelliJ IDEA のメインメニューから「File」-「Settings」を選択して「Settings」ダイアログを表示した後、CheckstyleFindBugs-IDEA の設定を変更します。

画面左側の一覧から「Other Settings」-「Checkstyle」を選択した後、画面右側の「Configuration File」の右にある「+」ボタンをクリックします。

f:id:ksby:20170806193223p:plain

ダイアログが表示されますので、以下の画像のように入力した後「Next」ボタンをクリックします。

f:id:ksby:20170806193337p:plain

  • 「Description」に google_checks.xml を入力します。
  • 「Use a local Checkstyle file」の「File」に C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\config\checkstyle\google_checks.xml を入力し、「Store relative to project location」をチェックします。

“The rules file has been validated and is ready to add.” のメッセージの画面が表示されたら「Finish」ボタンをクリックします。

「Settings」ダイアログに戻ると「Configuration File」に “google_checks.xml” が追加されていますので、左側の「Active」のチェックボックスをチェックした後「Apply」ボタンをクリックします。

f:id:ksby:20170806193723p:plain

画面左側の一覧から「Other Settings」-「FindBugs-IDEA」を選択した後、画面右側の「Filter」タブをクリックしてから画面中央の「Exclude filter files」の「+」ボタンをクリックします。

f:id:ksby:20170807002949p:plain

「Exclude Filter Files」ダイアログが表示されますので、C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\config\findbugs\findbugs-exclude.xml を選択して「OK」ボタンをクリックします。

f:id:ksby:20170807003204p:plain

「Settings」ダイアログに戻ると「Exclude filter files」に C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\config\findbugs\findbugs-exclude.xml が表示されています。「OK」ボタンをクリックしてダイアログを閉じます。

f:id:ksby:20170807003338p:plain

URL を定義する定数クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb の下に constants パッケージを新規作成します。

src/main/java/ksbysample/webapp/bootnpmgeb/constants の下に UrlConst.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.constants;

/**
 * URL 定数定義用クラス
 */
public class UrlConst {

    public static final String URL_INQUIRY_BASE = "/inquiry";
    public static final String URL_INQUIRY_INPUT_01 = URL_INQUIRY_BASE + "/input/01/";
    public static final String URL_INQUIRY_INPUT_02 = URL_INQUIRY_BASE + "/input/02/";
    public static final String URL_INQUIRY_INPUT_03 = URL_INQUIRY_BASE + "/input/03/";
    public static final String URL_INQUIRY_CONFIRM = URL_INQUIRY_BASE + "/confirm/";
    public static final String URL_INQUIRY_COMPLETE = URL_INQUIRY_BASE + "/complete/";

}

Controller クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb の下に web.inquiry パッケージを作成します。

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryInputController.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry;

import ksbysample.webapp.bootnpmgeb.constants.UrlConst;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * 入力画面1~3用 Controller クラス
 */
@Controller
@RequestMapping("/inquiry/input")
public class InquiryInputController {

    private static final String TEMPLATE_BASE = "web/inquiry";
    private static final String TEMPLATE_INPUT01 = TEMPLATE_BASE + "/input01";
    private static final String TEMPLATE_INPUT02 = TEMPLATE_BASE + "/input02";
    private static final String TEMPLATE_INPUT03 = TEMPLATE_BASE + "/input03";

    /**
     * 入力画面1 初期表示処理
     *
     * @return 入力画面1の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping("/01")
    public String input01() {
        return TEMPLATE_INPUT01;
    }

    /**
     * 入力画面1 「次へ」ボタンクリック時の処理
     *
     * @return 入力画面2の URL
     */
    @PostMapping(value = "/01", params = {"move=next"})
    public String input01MoveNext(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_INPUT_02).toUriString();
    }

    /**
     * 入力画面2 初期表示処理
     *
     * @return 入力画面2の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping("/02")
    public String input02() {
        return TEMPLATE_INPUT02;
    }

    /**
     * 入力画面2 「前へ」ボタンクリック時の処理
     *
     * @return 入力画面1の URL
     */
    @PostMapping(value = "/02", params = {"move=back"})
    public String input02MoveBack(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_INPUT_01).toUriString();
    }

    /**
     * 入力画面2 「次へ」ボタンクリック時の処理
     *
     * @return 入力画面3の URL
     */
    @PostMapping(value = "/02", params = {"move=next"})
    public String input02MoveNext(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_INPUT_03).toUriString();
    }

    /**
     * 入力画面3 初期表示処理
     *
     * @return 入力画面3の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping("/03")
    public String input03() {
        return TEMPLATE_INPUT03;
    }

    /**
     * 入力画面3 「前へ」ボタンクリック時の処理
     *
     * @return 入力画面2の URL
     */
    @PostMapping(value = "/03", params = {"move=back"})
    public String input03MoveBack(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_INPUT_02).toUriString();
    }

    /**
     * 入力画面3 「確認画面へ」ボタンクリック時の処理
     *
     * @return 確認画面の URL
     */
    @PostMapping(value = "/03", params = {"move=next"})
    public String input03MoveNext(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_CONFIRM).toUriString();
    }

}

画面遷移用の js ファイルを実装する

webpack.config.js を以下のように変更します。

module.exports = {
    entry: {
        "js/app": ["./src/main/assets/js/app.js"],
        "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
        "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"],
        "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"]
    },
    output: {
        path: __dirname + "/src/main/resources/static",
        publicPath: "/",
        filename: "[name].js"
    }
};
  • 以下の3行を追加します。
    • "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"]
    • "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"]
    • "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"]

js ファイル実装前に npm run server コマンドを実行しておきます。

src/main/assets/js/inquiry の下に input01.js を新規作成し、以下の内容を記述します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    // 動作確認のために初期表示時に「次へ」ボタンをクリック可能にする
    $(".js-btn-next").prop("disabled", false);

    $(".js-btn-next").on("click", function (event) {
        $("#input01Form").attr("action", "/inquiry/input/01/?move=next");
        $("#input01Form").submit();

        // return false は
        // event.preventDefault() + event.stopPropagation() らしい
        return false;
    })
});

src/main/assets/js/inquiry の下に input02.js を新規作成し、以下の内容を記述します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    // 動作確認のために初期表示時に「次へ」ボタンをクリック可能にする
    $(".js-btn-next").prop("disabled", false);

    $(".js-btn-back").on("click", function (event) {
        $("#input02Form").attr("action", "/inquiry/input/02/?move=back");
        $("#input02Form").submit();
        return false;
    })

    $(".js-btn-next").on("click", function (event) {
        $("#input02Form").attr("action", "/inquiry/input/02/?move=next");
        $("#input02Form").submit();
        return false;
    })
});

src/main/assets/js/inquiry の下に input03.js を新規作成し、以下の内容を記述します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    // 動作確認のために初期表示時に「確認画面へ」ボタンをクリック可能にする
    $(".js-btn-confirm").prop("disabled", false);

    $(".js-btn-back").on("click", function (event) {
        $("#input03Form").attr("action", "/inquiry/input/03/?move=back");
        $("#input03Form").submit();
        return false;
    })

    $(".js-btn-confirm").on("click", function (event) {
        $("#input03Form").attr("action", "/inquiry/input/03/?move=next");
        $("#input03Form").submit();
        return false;
    })
});

動作確認

Tomcat を起動します。

ブラウザを起動し http://localhost:8080/inquiry/input/01/ にアクセスすると入力画面1が表示されて、「次へ」ボタンもクリックできるようになっています。

f:id:ksby:20170811004724p:plain

「次へ」ボタンをクリックすると入力画面2へ遷移します。アドレスバーに表示される URL は http://localhost:8080/inquiry/input/02/ に変わっています。

f:id:ksby:20170811004855p:plain

「次へ」ボタンをクリックすると入力画面3へ遷移します。アドレスバーに表示される URL は http://localhost:8080/inquiry/input/03/ に変わっています。

f:id:ksby:20170811005023p:plain

「前の画面へ戻る」ボタンをクリックすると入力画面3→入力画面2→入力画面1へ戻り、URL もきちんと変わりました。

メモ書き

  • 現在の設定では、js ファイルを修正すると webpack の watch 機能で検知されて自動でバンドルされた js ファイルが出力されますが、IntelliJ IDEA で Ctrl+F9 を押して build しないとブラウザに反映されませんでした。browser-sync で html を作成していた時と比較してかなり手間がかかるようになってしまいました。もう少し開発しやすくできないものでしょうか。。。

履歴

2017/08/11
初版発行。
2017/09/02
<html xmlns:th="http://www.w3.org/1999/xhtml"> と間違えて記述していた箇所があったので <html xmlns:th="http://www.thymeleaf.org"> へ修正した。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( 番外編 )( IntelliJ IDEA 2017.2 の新機能 Run Dashboard を試してみる )

記事一覧はこちらです。

IntelliJ IDEA 2017.2.1 で Run Dashboard が正式に新機能として公開されましたので、試してみます。2017.2 では -Dide.run.dashboard=true オプションを指定する必要がありましたが、2017.2.1 からは不要になりました。

目次

  1. IntelliJ IDEA でオープンしている Project の Web アプリケーションを Run Dashboard で実行する
  2. IntelliJ IDEA でオープンしている Project とは別の Project の Web アプリケーションを Run Dashboard で実行する
  3. jar ファイルで提供されている Zipkin Server を Run Dashboard で実行する
  4. IntelliJ IDEA でオープンしている Project とは別の Project を変更したい場合には?
  5. Console を広く表示させたい場合には?
  6. 最後に

本編

IntelliJ IDEA でオープンしている Project の Web アプリケーションを Run Dashboard で実行する

Run ボタンで起動すると、

f:id:ksby:20170805165839p:plain

Run Dashboard ではなく通常の Run Tool Window で起動します。

f:id:ksby:20170805165955p:plain

これを Run Dashboard で起動するには、IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations…」を選択して「Run/Debug Configurations」ダイアログを表示した後、画面左側で「Spring Boot」-「Application」(ここに表示される名称は作成している Web アプリケーションにより異なります)を選択してから、画面右側の中央あたりにある「Show in Run Dashboard」をチェックします。

f:id:ksby:20170805170710p:plain

「OK」ボタンをクリックして「Run/Debug Configurations」ダイアログを閉じてから、再度 Run ボタンで起動します。

今度は Run Dashboard で Web アプリケーションが起動します。

f:id:ksby:20170805170940p:plain

IntelliJ IDEA でオープンしている Project とは別の Project の Web アプリケーションを Run Dashboard で実行する

今度は IDEA で boot-npm-geb-sample プロジェクトをオープンしたまま、ksbysample-webapp-lending プロジェクトの Web アプリケーションを実行してみます。

まずは ksbysample-webapp-lending プロジェクトを開いてから build タスクを実行して build\libs の下に jar ファイル(今回は ksbysample-webapp-lending-1.5.4-RELEASE.jar)を作成しておきます。

次に IDEA で boot-npm-geb-sample プロジェクトを開いた後、メインメニューから「Run」-「Edit Configurations…」を選択して「Run/Debug Configurations」ダイアログを表示します。

ダイアログ左上の「+」ボタンをクリックして「Add New Configuration」のリストを表示した後、「Spring Boot」を選択します。

f:id:ksby:20170805172226p:plain

画面左側の「Spring Boot」の下に「Unnamed」が追加されますので、「Unnamed」の設定を以下の画像のように変更します。

f:id:ksby:20170805173501p:plain

  • 「Name」を Unnamedksbysample-webapp-lending に変更します。
  • 「Main class」に ksbysample-webapp-lending のメインクラスである ksbysample.webapp.lending.Application を入力します。
  • VM options」に -Dspring.profiles.active=develop -Dfile.encoding=UTF-8 -jar ksbysample-webapp-lending-1.5.4-RELEASE.jar を入力します。必ず -D... の設定は -jar の前に入れます(前に入れないと無視されます)。
  • 「Working directory」に jar ファイルがある C:\project-springboot\ksbysample-webapp-lending\build\libs を入力します。
  • 「Use classpath of module」で boot-npm-geb-sample_main を選択します。ここは何かを選択しておかないと Run Dashboard で起動できません(実際のクラスパスは -jar オプションを指定しているので jar ファイル内の方になります)。
  • JRE」に起動に使用する JRE を指定します。Default のままでも構いませんが、今回は 1.8.0_141 を選択しておきます。
  • 「Show in Run Dashboard」をチェックします。
  • 尚、-Dspring.profiles.active=develop ではなく「Environment variables」で SPRING_PROFILES_ACTIVE=develop としても反映されませんでした。「Show in Run Dashboard」の下にある「Active Profiles」に develop と入力しても反映されませんでした。

「OK」ボタンをクリックして「Run/Debug Configurations」ダイアログを閉じます。

Run Dashboard を表示すると「Stopped」の下に「ksbysample-webapp-lending」が追加されており、選択すると画面左側の「Run」ボタンがクリックできるようになっています(設定が足りなかったり間違っていると「Run」ボタンは色なしで表示されクリックできません)。

f:id:ksby:20170805173750p:plain

「Run」ボタンをクリックすると ksbysample-webapp-lending Web アプリケーションが起動します。もし他の Web アプリケーションと使用するポート番号が重複する場合には「Run/Debug Configurations」ダイアログの「VM options」で -Dserver.port=... オプションを追加してポート番号を変更します。

f:id:ksby:20170805174307p:plain

jar ファイルで提供されている Zipkin Server を Run Dashboard で実行する

今度は Zipkin Server を Run Dashboard から起動してみます。Zipkin Server の jar ファイルは C:\zipkin の下に入れてある前提で記載します。

f:id:ksby:20170805175804p:plain

やることは ksbysample-webapp-lending プロジェクトの時と同じですので画像は必要なものだけ掲載します。

最初に Zipkin server のメインクラスを確認します。コマンドラインから java -jar zipkin-server-1.21.0-exec.jar を実行するとメインクラス zipkin.server.ZipkinServer が表示されます。

f:id:ksby:20170805180423p:plain

IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations…」を選択して「Run/Debug Configurations」ダイアログを表示します。

ダイアログ左上の「+」ボタンをクリックして「Add New Configuration」のリストを表示した後、「Spring Boot」を選択します。

画面左側の「Spring Boot」の下に「Unnamed」が追加されますので、「Unnamed」の設定を以下の画像のように変更します。

f:id:ksby:20170805180739p:plain

  • 「Name」を UnnamedZipkin server に変更します。
  • 「Main class」に zipkin.server.ZipkinServer を入力します。
  • VM options」に -jar zipkin-server-1.21.0-exec.jar を入力します。
  • 「Working directory」に jar ファイルがある C:\zipkin を入力します。
  • 「Use classpath of module」で boot-npm-geb-sample_main を選択します。
  • JRE」に起動に使用する JRE を指定します。Default のままでも構いませんが、今回は 1.8.0_141 を選択しておきます。
  • 「Show in Run Dashboard」をチェックします。

「OK」ボタンをクリックして「Run/Debug Configurations」ダイアログを閉じます。

Run Dashboard を表示すると「Stopped」の下に「Zipkin server」が追加されており、選択すると画面左側の「Run」ボタンがクリックできるようになっています。

f:id:ksby:20170805181047p:plain

「Run」ボタンをクリックすると Zipkin server が起動します。

f:id:ksby:20170805181227p:plain

http://localhost:9411/ にアクセスすると Zipkin の画面が表示されました。

f:id:ksby:20170805181344p:plain

IntelliJ IDEA でオープンしている Project とは別の Project を修正したくなった場合には?

IntelliJ IDEA のメインメニューから「File」-「Open Recent」を選択すると最近オープンした Project のリストが表示されますので、そこから変更したい Project を選択します。

f:id:ksby:20170805204820p:plain

「Open Project」ダイアログが表示されますので、「New Window」ボタンをクリックします。

f:id:ksby:20170805210125p:plain

別の IntelliJ IDEA が起動してそちらで Project が開きますので、そこで変更して jar ファイルを作り直します。

Console を広く表示させたい場合には?

自分が使用しているノートPCだと横幅が狭いので、Console を通常の Run Tool Window で起動しているのと同じように横いっぱいに表示させたいなと思ったのですが、その場合には「Show Configurations」のボタンをクリックすると、

f:id:ksby:20170806011652p:plain

左側の表示が消えて Console が広く表示されます。

f:id:ksby:20170806011853p:plain

また画面上部の実行中のアプリケーションが表示されている部分をクリックすることで、Console を表示しているアプリケーションを切り替えることができます。

f:id:ksby:20170806012034p:plain

最後に

  • 2017.2 のイチオシの機能だと思っていたのですが、設定方法を説明しているページが見つかりません。。。 上の内容は何となく試した結果なのですが、ここはこうする方がいいよという点があるかもしれません。
  • 複数の Web アプリケーションを1画面上から起動・停止したり、Console や Spring Boot Actuator の内容を見れるのは便利ですね。

IntelliJ IDEA を 2017.2 → 2017.2.1 へバージョンアップ

IntelliJ IDEA を 2017.2 → 2017.2.1 へバージョンアップする

IntelliJ IDEA の 2017.2.1 がリリースされたのでバージョンアップします。

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates…」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。

    f:id:ksby:20170805092559p:plain

  3. Plugin の update も表示されました。「Error-prone Compiler Integration」はバージョンアップすると動かなくなりますので、これだけチェックを外して「Update and Restart」ボタンをクリックします。

    f:id:ksby:20170805092755p:plain

  4. Patch がダウンロードされて IntelliJ IDEA が再起動します。

  5. IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20170805095416p:plain

  6. Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    f:id:ksby:20170805095637p:plain

  7. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2017.2.1 へバージョンアップされていることを確認します。

  8. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20170805100254p:plain

  9. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。

    f:id:ksby:20170805100752p:plain

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その11 )( PostCSS で common.css を minify する + autoprefixer, stylelint を導入する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その10 )( 各画面の HTML を作成する3 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • これまでは cpx パッケージで watch してコピーするだけでしたが、PostCSS で watch + コピー + minify するように変更します。
    • postcss の autoprefixer, stylelint プラグインも導入します。

参照したサイト・書籍

  1. PostCSS
    https://github.com/postcss/postcss

  2. PostCSS CLI
    https://github.com/postcss/postcss-cli

  3. cssnano
    https://github.com/ben-eb/cssnano

  4. Autoprefixer
    https://github.com/postcss/autoprefixer

  5. Browserslist
    https://github.com/ai/browserslist

  6. stylelint
    https://github.com/stylelint/stylelint

  7. stylelint-config-standard
    https://github.com/stylelint/stylelint-config-standard

  8. ‘at-rule-empty-line-before’ do not accept except + ignore
    https://github.com/stylelint/stylelint/issues/691

  9. PostCSS まとめ
    http://qiita.com/morishitter/items/4a04eb144abf49f41d7d

  10. Sassを捨ててPostCSSに移行したのでそのときの工程メモ
    http://qiita.com/nabeliwo/items/0aeea21e95f3fbab3955

  11. ここがすごいぞ! stylelint!
    http://qiita.com/inuscript/items/ff4f6972c988afbec3a8

  12. PostCSSとstylelintの環境構築
    http://qiita.com/buchiya4th/items/01b4ad050b7c59b48539

目次

  1. はじめに
  2. PostCSS と cssnano プラグインをインストールして minify してみる
    1. PostCSS をインストールする
    2. cssnano プラグインをインストールする
    3. postcss.config.js を作成する
    4. npm-scripts を追加する
    5. 動作確認する
    6. npm-scripts を変更して正式に取り込む
    7. 動作確認する(その2)
  3. Autoprefixer プラグインを追加する
    1. Autoprefixer プラグインをインストールする
    2. postcss.config.js を変更する
    3. 動作確認する
  4. stylelint プラグインを追加する
    1. stylelint プラグインと stylelint-config-standard をインストールする
    2. postcss.config.js を変更する
    3. stylelint.config.js を作成する
    4. 動作確認する
    5. stylelint で出力されたメッセージに対応する
  5. 次回は。。。

手順

はじめに

作成している common.css を単にコピーするだけでなく圧縮(minify)もしたいと思い調べたところ、PostCSS を使えば出来るようなので、試してみます。

PostCSS ではいろいろなプラグインを組み合わせて使用しますが、今回は以下のプラグインを入れてみます。

  • Autoprefixer(必要なベンダープリフィックスを自動で付与する)
  • cssnano(minify)
  • stylelint(CSSリンター)

PostCSS と cssnano プラグインをインストールして minify してみる

PostCSS をインストールする

npm install --save-dev postcss-cli コマンドを実行してインストールします。

f:id:ksby:20170730191424p:plain f:id:ksby:20170730191623p:plain

cssnano プラグインをインストールする

npm install --save-dev cssnano コマンドを実行してインストールします。

f:id:ksby:20170805011113p:plain f:id:ksby:20170805011246p:plain f:id:ksby:20170805011400p:plain f:id:ksby:20170805011510p:plain

postcss.config.js を作成する

プロジェクトのルートディレクトリに postcss.config.js を新規作成し、以下の内容を記述します。

module.exports = {
    plugins: [
        require('cssnano')({
            preset: 'default'
        })
    ]
};

npm-scripts を追加する

package.json の scripts に以下の記述を追加します。

  "scripts": {
    ..........
    "copy:css:watch": "cpx src/main/assets/css/**/* src/main/resources/static/css -w",
    "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll",
    "server": "run-p copy:css:watch webpack:watch browser-sync:start"
  },
  • "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll" を追加します。-w オプションだけだとなぜかうまく watch してくれない(数回変更を検知するとそれ以降検知しなくなる時がある)ので、--poll オプションも追加しました。
    ※この後の動作確認の時はまだ --poll オプションは付けていません。うまく動かない時があることに気付いて後から付けています。
  • postcss を実行した同じディレクトリに postcss.config.js があると自動で読み込んでくれるようなので -c postcss.config.js は付けません。

動作確認する

src/main/resources/static の下の css ディレクトリを削除します。

npm run postcss:watch コマンドを実行します。

f:id:ksby:20170730231735p:plain

src/main/resources/static の下を見ると css/common.min.css が生成されており、

f:id:ksby:20170730231349p:plain

common.min.css を開くと minify されていました。

f:id:ksby:20170730231845p:plain

src/main/assets/css/common.cssbackground-color: ivory;background-color: snow; に変更すると、

f:id:ksby:20170730232034p:plain

再び Finished src\main\assets\css\common.css のメッセージが出力されて、

f:id:ksby:20170730232154p:plain

src/main/resources/static/css/common.min.css に反映されます。

f:id:ksby:20170730232357p:plain

うまく動作しているようです。ただし、以下1点注意があります。

  • IntelliJ IDEA で src/main/assets/css/common.css を変更して、IntelliJ IDEA を Active な Window にしたまま src/main/resources/static/css/common.min.css を表示しても反映はされませんでした。一度 IntelliJ IDEA から他のプロセスに Active な Window を切り替えないと(マウスか Alt+Tab 等のキーで別の Window を Active にしないと)反映されませんでした。

npm-scripts を変更して正式に取り込む

package.json の scripts を以下のように変更します。

  "scripts": {
    ..........
    "copy:ionicons": "cpx node_modules/ionicons/dist/{css,fonts}/**/* src/main/resources/static/vendor/ionicons",
    "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll",
    "webpack": "webpack",
    "webpack:watch": "webpack --watch",
    "browser-sync": "browser-sync",
    "browser-sync:start": "browser-sync start --config bs-config.js",
    "server": "run-p postcss:watch webpack:watch browser-sync:start"
  },
  • "copy:css:watch": "cpx src/main/assets/css/**/* src/main/resources/static/css -w" を削除します。
  • "server" 内の copy:css:watchpostcss:watch に変更します。
  • "postcss:watch" を記述する位置を "webpack" の上に変更します。

動作確認する(その2)

今度は npm run server コマンドを実行した後、browser-sync でブラウザに反映されるか確認します。

static/input01.html を以下のように変更します。

<head>
  ..........

  <!-- 画面共通で使用する css -->
  <link rel="stylesheet" href="/css/common.min.css">
</head>
  • head タグ内の <link rel="stylesheet" href="/css/common.css"><link rel="stylesheet" href="/css/common.min.css"> に変更します。

npm run server コマンドを実行します。

f:id:ksby:20170730234240p:plain

ブラウザで http://localhost:9080/inquiry/input01.html にアクセスすると以下の画面が表示されます。

f:id:ksby:20170730234403p:plain

src/main/assets/css/common.cssbackground-color: snow;background-color: white; に変更すると、

f:id:ksby:20170730234558p:plain

postcss と browser-sync のメッセージが出力されて、

f:id:ksby:20170730234803p:plain

ブラウザに表示している入力画面1の背景色が white に変更されました。

f:id:ksby:20170730234913p:plain

問題なさそうです。

src/main/assets/css/common.cssbackground-color: ivory; に戻した後、Ctrl+C を押して npm run server コマンドを停止します。また static の下の input01.html 以外の html ファイルも <link rel="stylesheet" href="/css/common.min.css"> に変更します。

Autoprefixer プラグインを追加する

Autoprefixer プラグインをインストールする

npm install --save-dev autoprefixer コマンドを実行してインストールします。

f:id:ksby:20170803002524p:plain

postcss.config.js を変更する

postcss.config.js に autoprefixer の設定を追加します。

module.exports = {
    plugins: [
        require('autoprefixer')({
            browsers: [
                "last 2 versions"
            ]
        }),
        require('cssnano')({
            preset: 'default'
        })
    ]
};
  • require('autoprefixer')({ ... }) を追加します。

動作確認する

npm run server コマンドを実行します。src/main/resources/static/css/common.min.css は作り直されましたが、今のファイルだと何の prefix も追加されませんでした。

src/main/resources/static/css/common.min.cssa { display: flex; } を追加してみます。

a {
    display: flex;
}
/* body 内の背景色を白より少し色がついた色にする */
.content-wrapper {
    background-color: ivory;
}
..........

変更が検知されて、

f:id:ksby:20170803000914p:plain

src/main/resources/static/css/common.min.css を見ると vendor prefixes が自動で追加されていました。

f:id:ksby:20170803001118p:plain

stylelint プラグインを追加する

stylelint プラグインと stylelint-config-standard をインストールする

stylelint で適用するルールは stylelint-config-standard をそのまま使うことにします。npm install --save-dev stylelint stylelint-config-standard コマンドを実行してインストールします。

f:id:ksby:20170803061012p:plain f:id:ksby:20170803061142p:plain

postcss.config.js を変更する

postcss.config.js の以下の点を変更します。

module.exports = {
    plugins: [
        require('stylelint'),
        require('autoprefixer')({
            browsers: [
                "last 2 versions"
            ]
        }),
        require('cssnano')({
            preset: 'default'
        })
    ]
};
  • require('stylelint') を追加します。記述された順序でプラグインが適用されるので、一番最初に stylelint を記述します。最初はアルファベット順に一番最後に記述したら common.min.css に対して stylelint が適用されて大量にメッセージが出力されました。。。

stylelint.config.js を作成する

プロジェクトのルートディレクトリの直下に stylelint.config.js を新規作成し、以下の内容を記述します。

module.exports = {
    extends: "stylelint-config-standard"
}

動作確認する

npm run server コマンドを実行します。

common.min.css 生成時に stylelint が実行されて以下の画像のように大量のメッセージが出力されました。動作しましたが、大した行数の CSS でもないのに結構メッセージが出ますね。。。

f:id:ksby:20170803065048p:plain f:id:ksby:20170803065234p:plain

stylelint で出力されたメッセージに対応する

出力されたメッセージは以下の8種類でした。出力されたメッセージの説明は https://github.com/stylelint/stylelint/tree/master/lib/rules にあります。

  • Expected indentation of 2 spaces (indentation)
    • インデントがスペース2個でないというメッセージでした。IntelliJ IDEA だとデフォルトではスペース4個に設定されているので、stylelint.config.js に "indentation": 4 を追加して設定を変更します。
  • Expected empty line before comment (comment-empty-line-before)
    • コメントの前に1行空行がないというメッセージでした。コメントの前に1行空行を追加します。
  • Expected a leading zero (number-leading-zero)
    • 小数点以下がある数値の記述を 0.nnn ではなく .nnn のように先頭の 0 がない書き方をしていたので出力されていました。指摘された CSS は Bootstrap の css ファイルから持ってきたものなので、ここは .nnn の書き方でもメッセージが出力されないよう stylelint.config.js に "number-leading-zero": "never" を追加して設定を変更します。
  • Unexpected unit (length-zero-no-unit)
    • 値が 0 の時に 0px のように単位を記述していたので出力されているメッセージでした。単位を削除して 0px → 0 に変更します。
  • Expected single space after ":" with a single-line declaration (declaration-colon-space-after)
    • ここまでの修正でメッセージが出なくなったので対応しません。
  • Expected empty line before rule (rule-empty-line-before)
    • 定義と定義の間に空行を入れていないというメッセージでした。空行を入れずに連続して記述したい場合があるので、stylelint.config.js に "rule-empty-line-before": "never-multi-line" を追加して設定を変更します。
  • Expected indentation of 4 spaces (indentation)
    • ここまでの修正でメッセージが出なくなったので対応しません。
  • Expected empty line before at-rule (at-rule-empty-line-before)
    • .label-required を定義しているところで @media (min-width: 768px) { ... }@media (max-width: 767px) { ... } の間に空行を入れていないというメッセージでした。ここに空行は入れたくないので、stylelint.config.js に "at-rule-empty-line-before": [ "always", {"except": ["after-same-name"]} ] を追加して設定を変更します。

ここまで対応すると、追加で以下の3種類のメッセージが表示されました。

  • Expected empty line before comment (comment-empty-line-before)
    • /* ... */ のコメント行を空行を入れずに2行続けて書いていたために出ているメッセージでした。コメント行は連続で書けるようにしたいので、stylelint.config.js に "comment-empty-line-before": [ "always", {"ignore": ["after-comment"]} ] を追加して設定を変更します。
  • Expected empty line before at-rule (at-rule-empty-line-before)
    • コメント行の後に @media (min-width: 768px) { を記述しているところでメッセージが出ていました。上で追加した "at-rule-empty-line-before": [ ... ] の中に "ignore": ["after-comment"] を追加して、チェックされないようにします。
  • Unexpected empty line before rule (rule-empty-line-before)
    • .form-group .float-label の定義の前に空行が入っていたのでエラーが出ていました。ここは空行を取り除きます。

以上の対応で stylelint からメッセージが出ないようになりました。最終版の stylelint.config.js は以下のようになります。

module.exports = {
    extends: "stylelint-config-standard",
    rules: {
        "at-rule-empty-line-before": [
            "always",
            {
                "except": ["after-same-name"],
                "ignore": ["after-comment"]
            }
        ],
        "comment-empty-line-before": [
            "always",
            {
                "ignore": ["after-comment"]
            }
        ],
        "indentation": 4,
        "number-leading-zero": "never",
        "rule-empty-line-before": "never-multi-line"
    }
};

次回は。。。

Controller クラスと Thymeleaf テンプレートファイルを作成する予定です。

ソースコード

package.json

{
  "name": "boot-npm-geb-sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "run-s clean:static-dir copy:all",
    "clean:static-dir": "rimraf src/main/resources/static/*",
    "copy:all": "run-p copy:bootstrap copy:admin-lte copy:font-awesome copy:ionicons",
    "copy:bootstrap": "cpx node_modules/bootstrap/dist/**/* src/main/resources/static/vendor/bootstrap",
    "copy:admin-lte": "cpx node_modules/admin-lte/dist/**/* src/main/resources/static/vendor/admin-lte",
    "copy:font-awesome": "cpx node_modules/font-awesome/{css,fonts}/**/* src/main/resources/static/vendor/font-awesome",
    "copy:ionicons": "cpx node_modules/ionicons/dist/{css,fonts}/**/* src/main/resources/static/vendor/ionicons",
    "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll",
    "webpack": "webpack",
    "webpack:watch": "webpack --watch",
    "browser-sync": "browser-sync",
    "browser-sync:start": "browser-sync start --config bs-config.js",
    "server": "run-p postcss:watch webpack:watch browser-sync:start"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "admin-lte": "^2.3.11",
    "bootstrap": "^3.3.7",
    "font-awesome": "^4.7.0",
    "ionicons": "^3.0.0"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "browser-sync": "^2.18.13",
    "cpx": "^1.5.0",
    "cssnano": "^3.10.0",
    "npm-run-all": "^4.0.2",
    "postcss-cli": "^4.1.0",
    "rimraf": "^2.6.1",
    "stylelint": "^8.0.0",
    "stylelint-config-standard": "^17.0.0",
    "webpack": "^3.3.0"
  }
}

postcss.config.js

module.exports = {
    plugins: [
        require('stylelint'),
        require('autoprefixer')({
            browsers: [
                "last 2 versions"
            ]
        }),
        require('cssnano')({
            preset: 'default'
        })
    ]
};

stylelint.config.js

module.exports = {
    extends: "stylelint-config-standard",
    rules: {
        "at-rule-empty-line-before": [
            "always",
            {
                "except": ["after-same-name"],
                "ignore": ["after-comment"]
            }
        ],
        "comment-empty-line-before": [
            "always",
            {
                "ignore": ["after-comment"]
            }
        ],
        "indentation": 4,
        "number-leading-zero": "never",
        "rule-empty-line-before": "never-multi-line"
    }
};

common.css

/* body 内の背景色を白より少し色がついた色にする */
.content-wrapper {
    background-color: ivory;
}

/* フォーカス時の入力フィールドの色合いが adminLTE より Bootstrap のものが好みなので、そちらに定義し直す */
.form-control:focus {
    border-color: #66afe9;
    outline: 0;
    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
}

/* form-control-static の文字列を入力項目のすぐ下に隙間をほとんど空けずに表示されるようにする */
.form-control-static {
    padding-top: 0;
    padding-bottom: 0;
    margin-bottom: -15px;
}

/* 入力チェックエラーの入力項目の背景色を薄い赤色にする */
.has-error .form-control {
    background-color: #fff5ee;
}

/* form-horizontal 内で form-control, form-control-static-inline の入力フィールドを横並びで表示する */
.form-control-inline {
    float: left;
    margin-right: 10px;
}
.form-control-static-inline {
    float: left;
    padding-top: 7px;
    margin-right: 10px;
}

/* checkbox の文字列の右側に少し隙間を空ける */
.checkbox label {
    margin-right: 10px;
}

/* 必須ラベル */
/* 767px 以下の場合には必須ラベルを右側に移動する */
@media (min-width: 768px) {
    .label-required {
        background-color: red;
        margin-right: 5px;
    }
    .form-group .float-label {
        float: right;
    }
}
@media (max-width: 767px) {
    .label-required {
        background-color: red;
        margin-left: 5px;
    }
}

/* 画面下のボタンを大きく表示させる */
.btn {
    width: 150px;
}

履歴

2017/08/05 初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その10 )( 各画面の HTML を作成する3 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その9 )( 各画面の HTML を作成する2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 各画面の HTML を作成します。
    • 今回は確認画面、完了画面です。

参照したサイト・書籍

目次

  1. 確認画面の HTML を作成する
  2. 完了画面の HTML を作成する

手順

確認画面の HTML を作成する

static/inquiry/confirm.html を新規作成します。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>入力フォーム - 確認画面</title>
  <!-- Tell the browser to be responsive to screen width -->
  <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
  <!-- Bootstrap 3.3.6 -->
  <link rel="stylesheet" href="/vendor/bootstrap/css/bootstrap.min.css">
  <!-- Font Awesome -->
  <link rel="stylesheet" href="/vendor/font-awesome/css/font-awesome.min.css">
  <!-- Ionicons -->
  <link rel="stylesheet" href="/vendor/ionicons/css/ionicons.min.css">
  <!-- Theme style -->
  <link rel="stylesheet" href="/vendor/admin-lte/css/AdminLTE.min.css">
  <link rel="stylesheet" href="/vendor/admin-lte/css/skins/skin-blue.min.css">

  <!-- 画面共通で使用する css -->
  <link rel="stylesheet" href="/css/common.css">
  <style>
    /* セルの上部の罫線を表示しないようようにし、セル内の余白を詰める */
    .table > tbody> tr > th,
    .table > tbody> tr > td {
      border-top: none;
      padding: 5px;
    }
  </style>
</head>

<body class="skin-blue layout-top-nav">
<div class="wrapper">

  <!-- Content Wrapper. Contains page content -->
  <div class="content-wrapper">
    <!-- Content Header (Page header) -->
    <section class="content-header">
      <h1>
        確認画面
      </h1>
    </section>

    <!-- Main content -->
    <section class="content">
      <div class="row">
        <div class="col-xs-12">
          <form id="confirmForm" method="post" action="">
            <table class="table">
              <colgroup>
                <col width="15%"/>
                <col width="85%"/>
              </colgroup>

              <!-- 入力画面1の項目 -->
              <tr>
                <th nowrap>お名前(漢字)</th>
                <td>田中 太郎</td>
              </tr>
              <tr>
                <th nowrap>お名前(かな)</th>
                <td>たなか たろう</td>
              </tr>
              <tr>
                <th nowrap>性別</th>
                <td>男性</td>
              </tr>
              <tr>
                <th nowrap>年齢</th>
                <td>30歳</td>
              </tr>
              <tr>
                <th nowrap>職業</th>
                <td>会社員</td>
              </tr>
              <tr>
                <td colspan="2"><button class="btn bg-blue js-btn-input01"><i class="fa fa-arrow-left"></i> 修正する</button></td>
              </tr>

              <!-- 入力画面2の項目 -->
              <tr>
                <th nowrap>郵便番号</th>
                <td>〒102-0072</td>
              </tr>
              <tr>
                <th nowrap>住所</th>
                <td>東京都千代田区飯田橋1-1</td>
              </tr>
              <tr>
                <th nowrap>電話番号</th>
                <td>03-1234-5678</td>
              </tr>
              <tr>
                <th nowrap>メールアドレス</th>
                <td>taro.tanaka@sample.co.jp</td>
              </tr>
              <tr>
                <th nowrap>職業</th>
                <td>会社員</td>
              </tr>
              <tr>
                <td colspan="2"><button class="btn bg-blue js-btn-input02"><i class="fa fa-arrow-left"></i> 修正する</button></td>
              </tr>

              <!-- 入力画面3の項目 -->
              <tr>
                <th nowrap>お問い合わせの種類1</th>
                <td>製品に関するお問い合わせ</td>
              </tr>
              <tr>
                <th nowrap>お問い合わせの種類2</th>
                <td>見積が欲しい</td>
              </tr>
              <tr>
                <th nowrap>お問い合わせの内容</th>
                <td>
                  ここに、<br/>
                  入力されたお問い合わせの内容が表示されます。
                </td>
              </tr>
              <tr>
                <th nowrap>アンケート</th>
                <td>
                  <ul style="padding-left: 20px">
                    <li>選択肢1だけ長くしてみる</li>
                    <li>選択肢5が少し長い</li>
                    <li></li>
                  </ul>
                </td>
              </tr>
              <tr>
                <td colspan="2"><button class="btn bg-blue js-btn-input03"><i class="fa fa-arrow-left"></i> 修正する</button></td>
              </tr>
            </table>

            <div class="text-center">
              <button class="btn bg-green js-btn-send"><i class="fa fa-arrow-right"></i> 送信する</button>
            </div>
          </form>
        </div>
      </div>
    </section>
    <!-- /.content -->
  </div>
  <!-- /.content-wrapper -->
</div>
<!-- ./wrapper -->

<!-- REQUIRED JS SCRIPTS -->
<script src="/js/inquiry/confirm.js"></script>

</body>
</html>

画面の初期表示は以下のようになります(IE のキャプチャツールがうまく動かなかったため、今回は Chrome でキャプチャしています)。

f:id:ksby:20170730104232p:plain f:id:ksby:20170730104332p:plain

完了画面の HTML を作成する

static/inquiry/complete.html を新規作成します。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>入力フォーム - 完了画面</title>
  <!-- Tell the browser to be responsive to screen width -->
  <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
  <!-- Bootstrap 3.3.6 -->
  <link rel="stylesheet" href="/vendor/bootstrap/css/bootstrap.min.css">
  <!-- Font Awesome -->
  <link rel="stylesheet" href="/vendor/font-awesome/css/font-awesome.min.css">
  <!-- Ionicons -->
  <link rel="stylesheet" href="/vendor/ionicons/css/ionicons.min.css">
  <!-- Theme style -->
  <link rel="stylesheet" href="/vendor/admin-lte/css/AdminLTE.min.css">
  <link rel="stylesheet" href="/vendor/admin-lte/css/skins/skin-blue.min.css">

  <!-- 画面共通で使用する css -->
  <link rel="stylesheet" href="/css/common.css">
</head>

<body class="skin-blue layout-top-nav">
<div class="wrapper">

  <!-- Content Wrapper. Contains page content -->
  <div class="content-wrapper">
    <!-- Content Header (Page header) -->
    <section class="content-header">
      <h1>
        完了画面
      </h1>
    </section>

    <!-- Main content -->
    <section class="content">
      <div class="row">
        <div class="col-xs-12">
          <div class="text-center">
            <p>お問い合わせありがとうございました。</p>
            <a href="/inquiry/input01.html"><button class="btn bg-green"><i class="fa fa-reply"></i> 入力画面へ</button></a>
          </div>
        </div>
      </div>
    </section>
    <!-- /.content -->
  </div>
  <!-- /.content-wrapper -->
</div>
<!-- ./wrapper -->

<!-- REQUIRED JS SCRIPTS -->
<script src="/js/inquiry/complete.js"></script>

</body>
</html>

画面の初期表示は以下のようになります。

f:id:ksby:20170730104512p:plain f:id:ksby:20170730104606p:plain

履歴

2017/07/30
初版発行。
2017/08/11
* 「修正する」ボタンのセレクタを全て js-btn-input01 にしていたので、js-btn-input02, js-btn-input03 に修正した。