かんがるーさんの日記

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

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その22 )( 貸出希望書籍 CSV ファイルアップロード画面の作成 )

概要

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その21 )( 検索対象図書館登録画面の作成3 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。5~6回に分けて書きます。
    • 貸出希望書籍 CSV ファイルアップロード画面の作成
      • まずは Thymeleaf テンプレートファイルと Controller クラスの雛形を作成します。

参照したサイト・書籍

  1. Bootstrap File Input
    http://plugins.krajee.com/file-input

    • ファイルアップロードの部分の見栄えの良いライブラリを探していて見つけました。今回の画面ではこれを使用してみます。
  2. Bootstrap の File input の見た目を Cool にする
    http://qiita.com/QUANON/items/dbcd694a3b66eceb3023

    • Bootstrap File Input の使用方法が書かれている記事です。こちらも参考にしました。

目次

  1. はじめに
  2. 1.0.x-make-booklist ブランチの作成
  3. Bootstrap File Input のインストール
  4. 貸出希望書籍 CSV ファイルアップロード画面の HTML ファイルの作成
  5. Thymeleaf テンプレートファイルの作成
  6. BooklistController クラスの作成
  7. 動作確認
  8. 次回は。。。

手順

はじめに

全てのユーザが使用できる貸出希望書籍 CSV ファイルアップロード画面を作成します。

  • 書籍の ISBN、書名が記入された CSV ファイルをアップロードします。
  • ファイルの内容をチェックし、エラーがある場合にはエラーメッセージを表示します。エラーがない場合には CSV ファイルの内容を表示します。
  • 内容を確認して問題なければ「登録」ボタンをクリックします。この時に貸出申請 ID を発行し、画面上に表示します。
  • メニューの「検索対象図書館登録」を管理権限 ( ROLE_ADMIN ) を持つユーザにのみ表示させる対応をまだしていなかったので、今回対応します。

1.0.x-make-booklist ブランチの作成

  1. IntelliJ IDEA で 1.0.x-make-booklist ブランチを作成します。

Bootstrap File Input のインストール

  1. build.gradle を リンク先のその1、その2の内容 に変更します。

  2. Gradle projects view から downloadCssFontsJs タスクを実行し、BUILD SUCCESSFUL のログが出力されることを確認します。

    f:id:ksby:20150919073406p:plain

  3. src/main/resources/static の下に以下のファイルが作成されていることを確認します。

    f:id:ksby:20150919075312p:plain

  4. build.gradle の downloadCssFontsJs タスクのコメントアウトした部分を元に戻します。

貸出希望書籍 CSV ファイルアップロード画面の HTML ファイルの作成

  1. src/main/resources/static/html の下に booklist.html を作成します。作成後、リンク先の内容 に変更します。

    HTMLファイルには全ての要素を入れていますが、実際には以下のように表示を切り替える想定です。

    ■ファイル選択前 f:id:ksby:20150919183323p:plain

    ■ファイル選択時 f:id:ksby:20150919183522p:plain

    ■アップロードファイルのエラー発生時 f:id:ksby:20150919210437p:plain

    ■ファイルアップロード後 f:id:ksby:20150919200827p:plain

    ■登録後 f:id:ksby:20150920073040p:plain

Thymeleaf テンプレートファイルの作成

  1. src/main/resources/templates/common の下の head-cssjs.html を リンク先の内容 に変更します。

  2. src/main/resources/templates/admin/library の下の library.html を リンク先の内容 に変更します。

  3. src/main/resources/templates の下に booklist ディレクトリを作成します。

  4. Thymeleaf テンプレートファイルでは src/main/resources/static/html/booklist.html の内容を booklist.html, fileupload.html, complete.html の3つに分割します。

  5. src/main/resources/static/html/booklist.html を src/main/resources/templates/booklist の下にコピーします。

  6. src/main/resources/templates/booklist/booklist.html をコピーして同じディレクトリ内に fileupload.html, complete.html を作成します。

  7. src/main/resources/templates/booklist の下の booklist.html を リンク先の内容 に変更します。

  8. src/main/resources/templates/booklist の下の fileupload.html を リンク先の内容 に変更します。

  9. src/main/resources/templates/booklist の下の complete.html を リンク先の内容 に変更します。

BooklistController クラスの作成

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

  2. src/main/java/ksbysample/webapp/lending/web/booklist の下に BooklistController.java を作成します。作成後、リンク先の内容 に変更します。

動作確認

  1. Gradle projects View から bootRun タスクを実行して Tomcat を起動します。

  2. ブラウザを起動し http://localhost:8080/booklist へアクセスします。最初はログイン画面が表示されますので ID に "tanaka.taro@sample.com"、Password に "taro" を入力して、「次回から自動的にログインする」をチェックせずに「ログイン」ボタンをクリックします。

    以下の画面が表示されることを確認します。

    f:id:ksby:20150920083327p:plain

  3. ファイルを選択した後「アップロード」ボタンをクリックします。以下の画面が表示されることを確認します。

    f:id:ksby:20150920083736p:plain

  4. 「登録」ボタンをクリックします。以下の画面が表示されることを確認します。

    f:id:ksby:20150920084023p:plain

  5. Ctrl+F2 を押して Tomcat を停止します。

  6. 一旦 commit します。

次回は。。。

CSVファイルアップロード機能を作成します。

ソースコード

build.gradle

■その1

task downloadCssFontsJs << {
    def staticDirPath   = 'src/main/resources/static'
    def workDirPath     = 'work'
    def adminLTEVersion     = '2.2.0'
    def jQueryVersion       = '2.1.4'
    def fontAwesomeVersion  = '4.3.0'
    def ioniconsVersion     = '2.0.1'
    def html5shivJsVersion  = '3.7.2'
    def respondMinJsVersion = '1.4.2'
    
    // 作業用ディレクトリを削除する
    clearDir("${workDirPath}")
    
/*
    // Bootstrap & AdminLTE Dashboard & Control Panel Template
    downloadAdminLTE("${adminLTEVersion}", "${jQueryVersion}", "${workDirPath}", "${staticDirPath}")
    
    // Font Awesome Icons
    downloadFontAwesome("${fontAwesomeVersion}", "${workDirPath}", "${staticDirPath}")

    // Ionicons
    downloadIonicons("${ioniconsVersion}", "${workDirPath}", "${staticDirPath}")
    
    // html5shiv.js
    downloadHtml5shivJs("${html5shivJsVersion}", "${workDirPath}", "${staticDirPath}")

    // respond.min.js
    downloadRespondMinJs("${respondMinJsVersion}", "${workDirPath}", "${staticDirPath}")
*/

    // fileinput.min.js ( v4.2.7 )
    downloadBootstrapFileInputMinJs("${workDirPath}", "${staticDirPath}")

    // 作業用ディレクトリを削除する
    clearDir("${workDirPath}")
    
    // 追加したファイルを git add する
    addGit()
}
  • downloadBootstrapFileInputMinJs("${workDirPath}", "${staticDirPath}") を追加します。
  • 今回インストールしないモジュールの関数は一旦コメントアウトします。

■その2

void downloadBootstrapFileInputMinJs(String workDirPath, String staticDirPath) {
    download {
        src "https://github.com/kartik-v/bootstrap-fileinput/zipball/master"
        dest new File("${workDirPath}/download/kartik-v-bootstrap-fileinput.zip")
    }
    copy {
        from zipTree("${workDirPath}/download/kartik-v-bootstrap-fileinput.zip")
        into "${workDirPath}/unzip"
    }
    copy {
        from "${workDirPath}/unzip/kartik-v-bootstrap-fileinput-883d8b6/js/fileinput.min.js"
        into "${staticDirPath}/js"
    }
    copy {
        from "${workDirPath}/unzip/kartik-v-bootstrap-fileinput-883d8b6/js/fileinput_locale_ja.js"
        into "${staticDirPath}/js"
    }
    copy {
        from "${workDirPath}/unzip/kartik-v-bootstrap-fileinput-883d8b6/css/fileinput.min.css"
        into "${staticDirPath}/css"
    }
}
  • build.gradle の一覧最後にこの関数を追加します。

static/html/booklist.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>貸出希望書籍 CSV ファイルアップロード</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.4 -->
    <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <!-- Font Awesome Icons -->
    <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>
    <!-- Ionicons -->
    <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css"/>
    <!-- Theme style -->
    <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css"/>
    <!-- AdminLTE Skins. Choose a skin from the css/skins
         folder instead of downloading all of them to reduce the load. -->
    <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css"/>
    <!-- Bootstrap File Input -->
    <link href="/css/fileinput.min.css" rel="stylesheet" type="text/css"/>

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="/js/html5shiv.min.js"></script>
    <script src="/js/respond.min.js"></script>
    <![endif]-->

    <style type="text/css">
        <!--
        .content-wrapper {
            background-color: #fffafa;
        }
        .jp-gothic {
            font-family: Verdana, "游ゴシック", YuGothic, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif;
        }
        .callout ul li {
            margin-left: -30px;
        }
        .box-body.no-padding {
            padding-bottom: 10px !important;
        }
        .table>tbody>tr>td
        , .table>tbody>tr>th
        , .table>tfoot>tr>td
        , .table>tfoot>tr>th
        , .table>thead>tr>td
        , .table>thead>tr>th {
            padding: 5px;
            font-size: 90%;
        }
        .lending-oneline-msgbox {
            height: 50px;
            padding-top: 10px;
        }
        -->
    </style>
</head>

<!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. -->
<body class="skin-blue layout-top-nav">
<div class="wrapper">

    <header class="main-header">
        <nav class="navbar navbar-static-top">
            <div class="container">
                <div class="navbar-header">
                    <a href="#" class="navbar-brand"><b>ksbysample-lending</b></a>
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
                        <i class="fa fa-bars"></i>
                    </button>
                </div>

                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse pull-left" id="navbar-collapse">
                    <ul class="nav navbar-nav">
                        <li class="dropdown">
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown">メニュー <span class="caret"></span></a>
                            <ul class="dropdown-menu" role="menu">
                                <li><a href="#">Action</a></li>
                                <li><a href="#">Another action</a></li>
                                <li><a href="#">Something else here</a></li>
                                <li class="divider"></li>
                                <li><a href="#">Separated link</a></li>
                                <li class="divider"></li>
                                <li><a href="#">One more separated link</a></li>
                            </ul>
                        </li>
                    </ul>
                </div>
                <!-- /.navbar-collapse -->

                <!-- Navbar Right Menu -->
                <div class="navbar-custom-menu">
                    <p class="navbar-text selected-library">選択中:テスト図書館</p>
                    <ul class="nav navbar-nav">
                        <li><a href="#">ログアウト</a></li>
                    </ul>
                </div>
                <!-- /.navbar-custom-menu -->
            </div>
            <!-- /.container-fluid -->
        </nav>
    </header>

    <!-- Full Width Column -->
    <div class="content-wrapper">
        <div class="container">
            <!-- Content Header (Page header) -->
            <section class="content-header">
                <h1>貸出希望書籍 CSV ファイルアップロード</h1>
            </section>

            <!-- Main content -->
            <section class="content">
                <div class="row">
                    <div class="col-xs-12">
                        <!-- ファイル選択前・ファイル選択時 -->
                        <form enctype="multipart/form-data">
                            <div class="form-group">
                                <input type="file" name="fileupload" class="js-fileupload"/>
                            </div>
                        </form>
                        <div class="callout callout-danger">
                            <h4><i class="fa fa-warning"></i> アップロードされたCSVファイルでエラーが発生しました。</h4>
                            <ul>
                                <li>1行目のデータの項目数がISBN、署名の2個ではありません。</li>
                                <li>2行目のデータの署名が128文字を超えています。</li>
                            </ul>
                        </div>
                        <!-- /ファイル選択前・ファイル選択時 -->

                        <!-- ファイルアップロード後 -->
                        <div class="box">
                            <div class="box-body no-padding">
                                <form id="" method="post" action="">
                                    <table class="table table-hover">
                                        <colgroup>
                                            <col width="5%"/>
                                            <col width="35%"/>
                                            <col width="60%"/>
                                        </colgroup>
                                        <thead class="bg-purple">
                                        <tr>
                                            <th>No.</th>
                                            <th>ISBN</th>
                                            <th>書名</th>
                                        </tr>
                                        </thead>
                                        <tbody class="jp-gothic">
                                        <tr>
                                            <th>1</th>
                                            <th>978-1-4302-5908-4</th>
                                            <th>Spring Recipes</th>
                                        </tr>
                                        <tr>
                                            <th>2</th>
                                            <th>978-4-7741-5380-3</th>
                                            <th>Spring3入門</th>
                                        </tr>
                                        <tr>
                                            <th>3</th>
                                            <th>978-4-87311-718-8</th>
                                            <th>Javaパフォーマンス</th>
                                        </tr>
                                        </tbody>
                                    </table>
                                    <div class="text-center">
                                        <button class="btn bg-blue"><i class="fa fa-save"></i> 登録</button>
                                        <button class="btn bg-orange"><i class="fa fa-undo"></i> ファイルをアップロードし直す</button>
                                    </div>
                                </form>
                            </div>
                        </div>
                        <!-- /ファイルアップロード後 -->

                        <!-- 登録後 -->
                        <div class="lending-oneline-msgbox">
                            <p>貸出希望書籍を登録しました。選択中の図書館に蔵書の有無と貸出状況を問い合わせます。</p>
                        </div>
                        <button class="btn bg-blue"><i class="fa fa-file-text"></i> 別の貸出希望書籍を登録する</button>
                        <!-- /登録後 -->
                    </div>
                </div>
            </section>
            <!-- /.content -->
        </div>
        <!-- /.container -->
    </div>

</div>
<!-- ./wrapper -->

<!-- jQuery 2.1.4 -->
<script src="/js/jQuery-2.1.4.min.js" type="text/javascript"></script>
<!-- Bootstrap 3.3.2 JS -->
<script src="/js/bootstrap.min.js" type="text/javascript"></script>
<!-- AdminLTE App -->
<script src="/js/app.min.js" type="text/javascript"></script>
<!-- Bootstrap File Input -->
<script src="/js/fileinput.min.js" type="text/javascript"></script>
<script src="/js/fileinput_locale_ja.js" type="text/javascript"></script>
<script type="text/javascript">
    <!--
    $(document).ready(function() {
        $('.js-fileupload').fileinput({
            language: 'ja',
            showPreview: false,
            maxFileCount: 1,
            browseClass: 'btn btn-info fileinput-browse-button',
            browseIcon: '',
            browseLabel: ' ファイル選択',
            removeClass: 'btn btn-warning',
            removeIcon: '',
            removeLabel: ' 削除',
            uploadClass: 'btn btn-success fileinput-upload-button',
            uploadIcon: '<i class="fa fa-upload"></i>',
            uploadLabel: ' アップロード',
            allowedFileExtensions: ['csv'],
            msgValidationError: '<span class="text-danger"><i class="fa fa-warning"></i> CSV ファイルのみ有効です。'
        })
    });
    -->
</script>
</body>
</html>
  • Bootstrap File Input で日本語のファイル名を選択する場合には以下2点の対応が必要です。
    • <script src="/js/fileinput.min.js" type="text/javascript"></script> の後に <script src="/js/fileinput_locale_ja.js" type="text/javascript"></script> を記述する。
    • Javascript 内で language: 'ja' を指定する。

head-cssjs.html

    <!-- Bootstrap 3.3.4 -->
    <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <!-- Font Awesome Icons -->
    <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>
    <!-- Ionicons -->
    <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css"/>
    <!-- Theme style -->
    <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css"/>
    <!-- AdminLTE Skins. Choose a skin from the css/skins
         folder instead of downloading all of them to reduce the load. -->
    <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css"/>
    
    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
        <script src="/js/html5shiv.min.js"></script>
        <script src="/js/respond.min.js"></script>
    <![endif]-->

    <style type="text/css">
        <!--
        .jp-gothic {
            font-family: Verdana, "游ゴシック", YuGothic, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif;
        }
        .content-wrapper {
            background-color: #fffafa;
        }
        .noselected-library {
            color: #ff8679 !important;
            font-size: 100%;
            font-weight: 700;
        }
        .selected-library {
            color: #ffffff !important;
            font-size: 100%;
            font-weight: 700;
        }
        .table>tbody>tr>td
        , .table>tbody>tr>th
        , .table>tfoot>tr>td
        , .table>tfoot>tr>th
        , .table>thead>tr>td
        , .table>thead>tr>th {
            padding: 5px;
            font-size: 90%;
        }
        -->
    </style>
  • 画面共通で使用する .jp-gothic, .content-wrapper, .noselected-library, .selected-library, .table>tbody>tr>... の定義を library.html からこちらに移動します。

library.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"/>
    <link th:replace="common/head-cssjs"/>

    <style type="text/css">
        <!--
        -->
    </style>
</head>
  • head タグ内の以下の点を変更します。
    • style タグを <style><style type="text/css"> へ修正し、<!----> を追加します。
    • head-cssjs.html へ移動した CSS の定義を削除します。

templates/booklist/booklist.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>貸出希望書籍 CSV ファイルアップロード</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"/>
    <link th:replace="common/head-cssjs"/>
    <!-- Bootstrap File Input -->
    <link href="/css/fileinput.min.css" rel="stylesheet" type="text/css"/>

    <style type="text/css">
        <!--
        .callout ul li {
            margin-left: -30px;
        }
        -->
    </style>
</head>

<!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. -->
<body class="skin-blue layout-top-nav">
<div class="wrapper">

    <!-- Main Header -->
    <div th:replace="common/mainparts :: main-header"></div>

    <!-- Full Width Column -->
    <div class="content-wrapper">
        <div class="container">
            <!-- Content Header (Page header) -->
            <section class="content-header">
                <h1>貸出希望書籍 CSV ファイルアップロード</h1>
            </section>

            <!-- Main content -->
            <section class="content">
                <div class="row">
                    <div class="col-xs-12">
                        <form id="uploadBooklistForm" enctype="multipart/form-data" method="post" action="/booklist/fileupload" th:action="@{/booklist/fileupload}">
                            <div class="form-group">
                                <input type="file" name="fileupload" class="js-fileupload"/>
                            </div>
                        </form>
                        <div class="callout callout-danger">
                            <h4><i class="fa fa-warning"></i> アップロードされたCSVファイルでエラーが発生しました。</h4>
                            <ul>
                                <li>1行目のデータの項目数がISBN、署名の2個ではありません。</li>
                                <li>2行目のデータの署名が128文字を超えています。</li>
                            </ul>
                        </div>
                    </div>
                </div>
            </section>
            <!-- /.content -->
        </div>
        <!-- /.container -->
    </div>

</div>
<!-- ./wrapper -->

<script th:replace="common/bottom-js"></script>
<!-- Bootstrap File Input -->
<script src="/js/fileinput.min.js" type="text/javascript"></script>
<script src="/js/fileinput_locale_ja.js" type="text/javascript"></script>
<script type="text/javascript">
    <!--
    $(document).ready(function() {
        $('.js-fileupload').fileinput({
            language: 'ja',
            showPreview: false,
            maxFileCount: 1,
            browseClass: 'btn btn-info fileinput-browse-button',
            browseIcon: '',
            browseLabel: ' ファイル選択',
            removeClass: 'btn btn-warning',
            removeIcon: '',
            removeLabel: ' 削除',
            uploadClass: 'btn btn-success fileinput-upload-button',
            uploadIcon: '<i class="fa fa-upload"></i>',
            uploadLabel: ' アップロード',
            allowedFileExtensions: ['csv'],
            msgValidationError: '<span class="text-danger"><i class="fa fa-warning"></i> CSV ファイルのみ有効です。'
        })
    });
    -->
</script>
</body>
</html>
  • 画面のメインコンテンツ以外では以下の点を修正しています。
    • <html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> へ変更します。
    • Bootstrap File Input の CSS ファイルの link タグを除く部分を <link th:replace="common/head-cssjs"/> へ変更します。
    • <style type="text/css">...</style> の中から head-cssjs.html の中に定義されているものを削除します。
    • ヘッダー部分を <header class="main-header">...</header><div th:replace="common/mainparts :: main-header"></div> へ変更します。
    • </body> 前の Bootstrap File Input の Javascript ファイルの script タグを除く部分を <script th:replace="common/bottom-js"></script> へ変更します。

templates/booklist/fileupload.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>貸出希望書籍 CSV ファイルアップロード</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"/>
    <link th:replace="common/head-cssjs"/>

    <style type="text/css">
        <!--
        .box-body.no-padding {
            padding-bottom: 10px !important;
        }
        -->
    </style>
</head>

<!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. -->
<body class="skin-blue layout-top-nav">
<div class="wrapper">

    <!-- Main Header -->
    <div th:replace="common/mainparts :: main-header"></div>

    <!-- Full Width Column -->
    <div class="content-wrapper">
        <div class="container">
            <!-- Content Header (Page header) -->
            <section class="content-header">
                <h1>貸出希望書籍 CSV ファイルアップロード</h1>
            </section>

            <!-- Main content -->
            <section class="content">
                <div class="row">
                    <div class="col-xs-12">
                        <div class="box">
                            <div class="box-body no-padding">
                                <form id="registerBooklistForm" method="post" action="/booklist/register" th:action="@{/booklist/register}">
                                    <table class="table table-hover">
                                        <colgroup>
                                            <col width="5%"/>
                                            <col width="35%"/>
                                            <col width="60%"/>
                                        </colgroup>
                                        <thead class="bg-purple">
                                        <tr>
                                            <th>No.</th>
                                            <th>ISBN</th>
                                            <th>書名</th>
                                        </tr>
                                        </thead>
                                        <tbody class="jp-gothic">
                                        <tr>
                                            <th>1</th>
                                            <th>978-1-4302-5908-4</th>
                                            <th>Spring Recipes</th>
                                        </tr>
                                        <tr>
                                            <th>2</th>
                                            <th>978-4-7741-5380-3</th>
                                            <th>Spring3入門</th>
                                        </tr>
                                        <tr>
                                            <th>3</th>
                                            <th>978-4-87311-718-8</th>
                                            <th>Javaパフォーマンス</th>
                                        </tr>
                                        </tbody>
                                    </table>
                                    <div class="text-center">
                                        <button class="btn bg-blue js-btn-register"><i class="fa fa-save"></i> 登録</button>
                                        <button class="btn bg-orange js-btn-backindex"><i class="fa fa-undo"></i> ファイルをアップロードし直す</button>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </section>
            <!-- /.content -->
        </div>
        <!-- /.container -->
    </div>

</div>
<!-- ./wrapper -->

<script th:replace="common/bottom-js"></script>
<script type="text/javascript">
    <!--
    $(document).ready(function() {
        $(".js-btn-register").click(function(){
            $("#registerBooklistForm").submit();
           return false;
        });

        $(".js-btn-backindex").click(function(){
            location.href = "/booklist";
            return false;
        });
    });
    -->
</script>
</body>
</html>

templates/booklist/complete.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>貸出希望書籍 CSV ファイルアップロード</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"/>
    <link th:replace="common/head-cssjs"/>

    <style type="text/css">
        <!--
        .lending-oneline-msgbox {
            height: 50px;
            padding-top: 10px;
        }
        -->
    </style>
</head>

<!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. -->
<body class="skin-blue layout-top-nav">
<div class="wrapper">

    <!-- Main Header -->
    <div th:replace="common/mainparts :: main-header"></div>

    <!-- Full Width Column -->
    <div class="content-wrapper">
        <div class="container">
            <!-- Content Header (Page header) -->
            <section class="content-header">
                <h1>貸出希望書籍 CSV ファイルアップロード</h1>
            </section>

            <!-- Main content -->
            <section class="content">
                <div class="row">
                    <div class="col-xs-12">
                        <div class="lending-oneline-msgbox">
                            <p>貸出希望書籍を登録しました。選択中の図書館に蔵書の有無と貸出状況を問い合わせます。</p>
                        </div>
                        <button class="btn bg-blue js-btn-moveindex"><i class="fa fa-file-text"></i> 別の貸出希望書籍を登録する</button>
                    </div>
                </div>
            </section>
            <!-- /.content -->
        </div>
        <!-- /.container -->
    </div>

</div>
<!-- ./wrapper -->

<script th:replace="common/bottom-js"></script>
<script type="text/javascript">
    <!--
    $(document).ready(function() {
        $(".js-btn-moveindex").click(function(){
            location.href = "/booklist";
            return false;
        });
    });
    -->
</script>
</body>
</html>

BooklistController.java

package ksbysample.webapp.lending.web.booklist;

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

@Controller
@RequestMapping("/booklist")
public class BooklistController {

    @RequestMapping
    public String index() {
        return "booklist/booklist";
    }

    @RequestMapping("/fileupload")
    public String fileupload() {
        return "booklist/fileupload";
    }

    @RequestMapping("/register")
    public String register() {
        return "redirect:/booklist/complete";
    }
   
    @RequestMapping("/complete")
    public String complete() {
        return "booklist/complete";
    }

}

履歴

2015/09/20
初版発行。