Chapter 22

画面構成(Thymeleaf、css)

kazpgm
kazpgm
2022.01.13に更新

自動作成されたThymeleaf、cssを説明する

例:商品情報登録画面("/members/admin/shohin/shohin")

以下のような仕組みで、商品情報登録画面テンプレート(shohinRegister.html)のThymeleaf機能「 th:replace="~{/members/admin/template :: layout(~{::act}, ~{::main})}">」により、基本画面テンプレート(template.html)のlayoutフラグメントの内容が置換され表示されます。

・Thymeleafの機能「 th:replace="~{/members/admin/template :: layout(~{::act}, ~{::main})}">」で、→1
 基本画面テンプレートのact、mainは、商品情報登録画面の内容「th:fragment="act" 」→2、「th:fragment="main" 」→3で置き換えられる。

・更に、商品情報登録画面のactは「<div id="act1" th:replace="~{/members/admin/shohin/shohinActionStr :: action}"></div> 」により
 商品情報更新削除などのアクションテンプレート(shohinActionStr.html)の「th:fragment="action" 」の内容に置き換えられる。→4

・更に、商品情報登録画面のactは「<div id="act2" th:replace="~{/members/admin/actionStr :: action}"></div> 」により
 リスト昇降順切替などのアクションテンプレート(actionStr.html)の「th:fragment="action" 」の内容に置き換えられる。→5
  補足:actionStr.htmlは全画面共通です。

・更に、商品情報登録画面のerrorSuccessMsgは「th:replace="~{/members/admin/errorSuccessMsg :: errorSuccessMsg}"」によりエラー正常メッセージテンプレート(errorSuccessMsg.html)の「th:fragment="errorSuccessMsg" 」の内容に置き換えられる。→6
  補足:errorSuccessMsg.htmlは全画面共通です。

・更に、基本画面テンプレート(template.html)のth:replace="~{/members/admin/header :: header}"は、header.html、→7
 th:replace="~{/members/admin/side :: side}"はside.html →8、th:replace="~{/members/admin/footer :: footer}は、footer.html →9に置換される。
  補足:header.html、side.html、footer.htmlは全画面共通です。

・商品情報登録画面テンプレートの「<div id="mainSub" th:replace="~{/members/admin/shohin/shohinAmendRegister :: mainSub}"></div>」で、商品情報登録画面のmainSubは、商品情報登録更新画面テンプレート(shohinAmendRegister.html)の内容「th:fragment="mainSub" 」→10で置き換えられる。

画面はtemplate.htmlに記述されている"/css/screen.css" によりデザインされている。→11

■src\main\resources\templates\members\admin\shohin\shohinRegister.html 商品情報登録画面("/members/admin/shohin/shohin")

<!DOCTYPE html>

Thymeleaf 用の属性は xmlns:th="http://www.thymeleaf.org" で名前空間を宣言して利用できます。

<html xmlns:th="http://www.thymeleaf.org"
  th:replace="~{/members/admin/template :: layout(~{::act}, ~{::main})}">  <!-- ←1 -->
<head>

このhtmlでの、<meta name=・・・などは使用されません。 <meta name=・・・などは、template.htmlの内容が使用されます。なぜなら、div th:fragment="act"及び 、div th:fragment="main"で定義されたタグ内部のみ使用されるからです。

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>商品情報登録</title>
</head>
<body>

合成後にdiv タグを残さないため、th:remove 属性の値にtag を設定している。

<div th:fragment="act" th:remove="tag">   <!-- ←2 -->

th:with="actionStr = '/members/admin/shohin/shohin'"により、定義されたタグ内部の$actionStr{}を '/members/admin/shohin/shohin'に置換する。具体的にはshohinActionStr.htmlのth:action="@{${actionStr}}"がth:action="@{/members/admin/shohin/shohin}"に変わる。

 <div th:with="actionStr = '/members/admin/shohin/shohin'">
     <div id="act1" th:replace="~{/members/admin/shohin/shohinActionStr ::
				action}"></div>   <!-- ←4 -->
     <div id="act2" th:replace="~{/members/admin/actionStr :: action}">
	 </div>  <!-- ←5 -->
    </div>
</div>
<div th:fragment="main" th:remove="tag">  <!-- ←3 -->
 <div id="main">
  <h1>商品情報管理</h1>
  <h2>商品情報登録</h2>
     <div id="errorSuccessMsg" th:replace="~{/members/admin/errorSuccessMsg :: 
					   errorSuccessMsg}"></div>  <!-- ←6-->

URL:/members/admin/shohin/shohin、mode=ins_doでmethod="post" される。th:object="${shohinForm}" は画面オブジェクト(自動生成PGMは、テーブルid+"Form".java)です。
autocomplete="off"にして、フォームにおける自動補完を無効にする。

  <form name="frm" id="frm" th:action="@{/members/admin/shohin/shohin}" 
	th:object="${shohinForm}" method="post" autocomplete="off">
  <input type="text" name="dummy" style="display:none;" />
  <input type="hidden" name="mode" th:value="'ins_do'"/>
      <div id="mainSub" th:replace="~{/members/admin/shohin/shohinAmendRegister :: 
				    mainSub}"></div>  <!-- ←9 -->
  <p class="text-center"><a href="javascript:do_Submit_Clk1();">
	  <img src="/img/btn/button-sub.gif" alt="登録" width="61" 
	       height="22" border="0" class="right5" /></a>
   <a th:href="'javascript:submitFrm5(\'/members/admin/shohin/shohin\', 
	       \'ins\')'"><img src="/img/btn/button-reset.gif" alt="リセット" 
		width="84" height="22" border="0" /></a></p>
  </form>  
 </div>
 <!-- main end -->
</div>
</body>
</html>

■src\main\resources\templates\members\admin\shohin\shohinActionStr.html 商品情報更新削除などのアクションテンプレート

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>

このhtmlでの、<meta name=・・・などは使用されません。 <meta name=・・・などは、template.htmlの内容が使用されます。なぜなら、div th:fragment="action"で定義されたタグ内部のみ使用されるからです。

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>header</title>
</head>
<body>
<div th:fragment="action" th:remove="tag">  <!-- ←4 -->
 <form name="frm3" method="post" th:action="@{${actionStr}}">
  <input type="hidden" name="mode" th:value="''"/>
  <input type="hidden" name="products" th:value="''">
  <input type="hidden" name="page" th:value="''">
 </form>
 <script type="text/JavaScript"><!-- 

submitFrm3関数は、検索一覧表示において、更新一覧画面とリスト画面を切り替える時、一覧画面から詳細画面、更新画面、削除を行う時、詳細画面から更新画面を表示する時に使用します。
説明例は、登録画面なので、submitFrm3関数を使いません、なので、xxxxxRegister.htmlの 「 <div id="act1" th:replace="~{/members/admin/xxxxx/xxxxxActionStr :: action}"></div> 」を削除しても動くかもしれませんが共通的にPGM自動作成しているのでこのままにしています。

 function submitFrm3( mode, products, page ) {
     document.frm3.mode.value = mode;
  document.frm3.products.value = products;
     document.frm3.page.value = page;
  if (mode == "del_do") {
   if ( window.confirm('削除してもよろしいですか?') ) {
       document.frm3.submit();
   }
  } else {
      document.frm3.submit();
  }
 }
  --></script>
 </div>
</body>
</html>

■src\main\resources\templates\members\admin\actionStr.html リスト昇降順切替などのアクションテンプレート

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>

このhtmlでの、<meta name=・・・などは、template.htmlの内容が使用されます。なぜなら、div th:fragment="action"で定義されたタグ内部のみ使用されるからです。

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>header</title>
</head>
<body>
<div th:fragment="action" th:remove="tag">   <!-- ←5 -->

frm2は、検索一覧表示において、タイトル昇順降順クリック時に使用します。
frm4は、登録画面、詳細画面から、検索一覧表示画面に戻るときに使用します。
説明例は、登録画面なので、frm2、frm4を使いません、なので、xxxxxRegister.htmlの 「 <div id="act1" th:replace="~{/members/admin/actionStr :: action}"></div> 」を削除しても動くかもしれませんが共通的にPGM自動作成しているのでこのままにしています。

  <form name="frm2" method="post" th:action="@{${actionStr}}">
   <input type="text" name="dummy" style="display:none;" />
   <input type="hidden" name="mode" th:value="'list_up_dwn'"/>
   <input type="hidden" name="sortItemName" th:value="''">
   <input type="hidden" name="sortOrder" th:value="''">
  </form>
  <form name="frm4" method="post" th:action="@{${actionStr}}">
   <input type="hidden" name="mode" th:value="'list_back'"/>
   <input type="hidden" name="page" th:value="''">
  </form>
</div>
</body>
</html>

■src\main\resources\templates\members\admin\errorSuccessMsg.html エラー正常メッセージ

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>

このhtmlでの、<meta name=・・・などは、template.htmlの内容が使用されます。なぜなら、div th:fragment="errorSuccessMsg"で定義されたタグ内部のみ使用されるからです。

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>header</title>
</head>
<body>
<div th:fragment="errorSuccessMsg" th:remove="tag"> <!-- ←6 -->
 <th:block th:if="${successMessage != null or errorMessage != null}">
  <div class="font-s-red-form" align="center">
   <p th:text="${successMessage}">successMessage</p>
   <p th:text="${errorMessage}">errorMessage</p>
  </div>
 </th:block>
</div>
</body>
</html>

■src\main\resources\templates\members\admin\header.html ヘッダー

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>

このhtmlでの<meta name=・・・などは、template.htmlの内容が使用されます。なぜなら、div th:fragment="header"で定義されたタグ内部のみ使用されるからです。

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, 
			                  shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>header</title>
</head>
<body>


<div th:fragment="header" th:remove="tag"> <!-- ←7 -->
 <div id="header">
  <a th:href="'javascript:submitFrm5(\'/members/admin/index\', \'\')'">
	  <div style="font-size: large; color:#FFFFFF; text-align: center;">
	  <strong>あなたのPGMタイトル&nbsp;&nbsp;管理システム</strong></div></a>
  <div id="r-navi">
   <form class="text-center" th:action="@{/logout}" method="post">
   <th:block th:if="${#strings.contains(#authentication.principal.user.getRoles(),
		    'ROLE_USER')}">
    <a style="color:#FFFFFF;" th:href="'javascript:submitFrm5(\'/members/user/index\',
				       \'\')'">ユーザー側メニュー</a>
   </th:block>
   <input type="image" src="/img/botton_logout.gif" alt="ログアウト" 
	  width="59" height="14" hspace="5" vspace="7" border="0">
   </form>
  </div>
  <p class="clear"></p>
 </div>
</div>
</body>
</html>

■src\main\resources\templates\members\admin\side.html サイドメニュー

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>

このhtmlでの、<meta name=・・・などは、template.htmlの内容が使用されます。なぜなら、div th:fragment="side"で定義されたタグ内部のみ使用されるからです。

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, 
			       shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>header</title>
</head>
<body>


<div th:fragment="side" th:remove="tag"> <!-- ←8 -->
 <div id="side">
  <form name="frm5" method="post" th:action="@{/members/admin/index}">
   <input type="text" name="dummy" style="display:none;" />
   <input type="hidden" name="mode" th:value="''"/>
  </form>
  <script type="text/JavaScript"><!-- 
  function submitFrm5( url, mode ) {
      document.frm5.action = url;
      document.frm5.mode.value = mode;
      document.frm5.submit();
  }
   --></script>

  <div id="menu-top"><a th:href="'javascript:submitFrm5(\'/members/admin/index\',
	  \'\')'" class="btn-menu-top">管理者機能TOP</a></div>
  <div class="menu-news">
   <p>ユーザー情報管理</p>
  </div>
  <ul class="menu">
   <li><a th:href="'javascript:submitFrm5(\'/members/admin/user/userA\', 
	   \'ins\')'">ユーザー情報登録</a></li>
   <li><a th:href="'javascript:submitFrm5(\'/members/admin/user/userA\', 
	   \'list\')'">ユーザー情報一覧(更新削除)</a></li>
  </ul>

  <div class="menu-news">
   <p>商品情報管理</p>
  </div>
  <ul class="menu">
   <li><a th:href="'javascript:submitFrm5(\'/members/admin/shohin/shohin\', 
	   \'ins\')'">商品情報登録</a></li>
   <li><a th:href="'javascript:submitFrm5(\'/members/admin/shohin/shohin\', 
	   \'insList\')'">商品情報一括登録</a></li>
   <li><a th:href="'javascript:submitFrm5(\'/members/admin/shohin/shohin\', 
	   \'list\')'">商品情報一覧(更新削除)</a></li>
  </ul>
        ・・・(省略)
  <div class="menu-news">
   <p>初期データアップロード管理</p>
  </div>
  <ul class="menu">
   <li><a th:href="'javascript:submitFrm5(\'/members/admin/user/userA\',
	   \'up_csv\')'">ユーザー情報アップロード</a></li>
   <li><a th:href="'javascript:submitFrm5(\'/members/admin/shohin/shohin\',
	   \'up_csv\')'">商品情報アップロード</a></li>
        ・・・(省略)
  </ul>

 </div>
</div>
</body>
</html>

■src\main\resources\templates\members\admin\footer.html フッター

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>

このhtmlでの、<meta name=・・・などは、template.htmlの内容が使用されます。なぜなら、div th:fragment="footer"で定義されたタグ内部のみ使用されるからです。

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, 
			       shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>footer</title>
</head>
<body>

<div th:fragment="footer" th:remove="tag"><!-- ←9 -->
 <div id="footer">
  <p>Copyright (c) あなたのPGM著作権表示 All rights reserved.</p>
    </div>
</div>
</body>
</html>

■src\main\resources\templates\members\admin\shohin\shohinAmendRegister.html 商品情報登録更新画面テンプレート

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>

このhtmlでの<form name="frm" id="frm" ・・・、<meta name=・・・などは使用されません。 <form name="frm" id="frm" ・・・は、shohinRegister.htmlの内容が使用され、<meta name=・・・は、template.htmlの内容が使用されます。なぜなら、div th:fragment="mainSub"で定義されたタグ内部のみ使用されるからです。

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, 
			       shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>商品情報登録</title>
</head>
<body>
 <div id="main">
  <h1>商品情報管理</h1>
  <h2>商品情報登録</h2>
  <form name="frm" id="frm" th:action="@{/members/admin/shohin/shohin}" 
	th:object="${shohinForm}" method="post" autocomplete="off">
  <input type="text" name="dummy" style="display:none;" />
  <input type="hidden" name="mode" th:value="''"/>
  <div th:fragment="mainSub" th:remove="tag">  <!-- ←10 -->
   <font color="#FF6600">*</font>は必須記入項目<br>
   <table class="tbl-01">

<th:block th:if="${mode != 'ins'}">で、更新時は主キーproductsを表示し、hiddenで保持する。登録時は表示しない(DB登録時に自動採番される)。
<td th:text="*{products}">は、th:object="${shohinForm}" のオブジェクト中のproductsの値を表示する記述。

  <th:block th:if="${mode != 'ins'}">
    <tr>
      <th width="18%" nowrap  colspan=2>商品CD</th>
      <td th:text="*{products}">products</td>
    </tr>
 <input type="hidden" th:field="*{products}"/>
  </th:block>
    <tr>
      <th width="18%" nowrap  colspan=2><label for="productsname">商品名
	      &nbsp;&nbsp;<font color="#FF6600">*</font></label></th>

<td th:class="${#fields.hasErrors('productsname')} ? 'error' : 'none'">で、productsnameに入力エラーがあるとき、errorクラス、無いときnoneクラスにしている。
<input th:field="{productsname}" class="form-jpn" type="text" size="50" maxlength="50" />で、テキスト入力項目に、th:object="${shohinForm}" のオブジェクト中のproductsnameの値を入れて表示している。
<div th:if="${#fields.hasErrors('productsname')}" th:errors="
{productsname}" th:errorclass="font-s-red-form">で、productsnameに入力エラーがあるとき、エラー内容を赤文字で表示している。

      <td th:class="${#fields.hasErrors('productsname')} ? 'error' : 'none'">
      <input th:field="*{productsname}"  class="form-jpn"   
	     type="text" size="50" maxlength="50" />
      全角50文字以内&nbsp;&nbsp;
      <div th:if="${#fields.hasErrors('productsname')}" 
	   th:errors="*{productsname}" th:errorclass="font-s-red-form"></div>
      </td>
    </tr>
    <tr>
        ・・・(省略)
   </table>
  </div>
  </form>  
  <!-- main end -->
 </div>
</body>
</html>

■src\main\resources\templates\members\admin\template.html 基本画面テンプレート

<!DOCTYPE html>
<html lang="ja" 
      xmlns:th="http://www.thymeleaf.org" 

「xmlns:sec="http://www.thymeleaf.org/extras/spring-security"」を記述していますが、「sec:authorize」など使用していません。削除しても動きますが、あとで使うかもしれないのでこのままにしておきます。
「th:fragment="layout (act, main)">」により、このhtmlで記述されている内容により、htmlが作成されます。

      xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
      th:fragment="layout (act, main)">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, 
				   shrink-to-fit=no">
    <meta http-equiv="x-ua-compatible" content="ie=edge">

トークン値「<meta name="_csrf" th:content="${_csrf.token}"/>"」、ヘッダー名「<meta name="_csrf_header" th:content="${_csrf.headerName}"/>」はDBデータをajaxで取得するときに使用しています。
例:kaz.jsから、DBデータをajaxで取得する部分抜粋
function createLrgChange( LrgValue, MidSelObj, SmlSelObj, midashi, indelkbn, urlArg, fmElementNm )
{
( function ( $ ) {
//Spring SecurityのCSRF対策用(metaタグに設定したものを取得する)
let token = $("meta[name='_csrf']").attr("content"); //←ここで使用
let header = $("meta[name='_csrf_header']").attr("content"); //←ここで使用
//Controllerの@RequestParamたち
let data = {mode:"elem",fmElementNm:fmElementNm, fmLrgKey:LrgValue,
fmMidKey:"",indelkbn:indelkbn};
//Spring SecurityのCSRF対策用
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});

<!-- トークン値 -->
<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- ヘッダー名 -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
 <title>あなたのPGMタイトル&nbsp;&nbsp;サイト管理システム</title>

DBデータをajaxで取得するために、jqueryを使っています。

<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/
			                     themes/base/jquery-ui.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/
	                                     jquery-ui.min.js"></script>

カレンダーを利用した日付入力のため、createDatepicker.jsで、jquery.ui.datepicker-ja.min.jsを使っています。

<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1/i18n/
	                         jquery.ui.datepicker-ja.min.js"></script>
<link rel="stylesheet" href="/css/screen.css" type="text/css"
                               media="screen,print" />  <!-- ←11 -->

kaz.jsに、フォームサブミット関数、selectのoption作成関数、 DBデータをajaxで取得する関数が定義されている。

<script type="text/javascript" src="/js/kaz.js"></script>
    <script type="text/JavaScript"><!-- 

submitFrm2関数は、検索一覧表示において、タイトル昇順降順クリック時に使用します。

    function submitFrm2(sortItemName, sortOrder) {
        document.frm2.sortItemName.value = sortItemName;
        document.frm2.sortOrder.value = sortOrder;
        document.frm2.submit();
    }

paging関数は、登録画面、詳細画面から、検索一覧表示画面に戻るときに使用します。

    function paging( page ) {
        document.frm4.page.value = page;
        document.frm4.submit();
    }

checkbox_clear関数は、指定されたチェックボックスを全クリアする関数です。

    function checkbox_clear(itemName){
        for(i = 0; i < document.frm.elements.length; i++) {
            if(document.frm.elements[i].type == "checkbox") {
                if(document.frm.elements[i].name == itemName) {
                    document.frm.elements[i].checked = false;
                }
            }
        }
    }

checkbox_set関数は、指定されたチェックボックスを全チェックする関数です。

    function checkbox_set(itemName){
        for(i = 0; i < document.frm.elements.length; i++) {
            if(document.frm.elements[i].type == "checkbox") {
                if(document.frm.elements[i].name == itemName) {
                    document.frm.elements[i].checked = true;
                }
            }
        }
    }
     --></script>
</head>
<body>
<div id="wrapper">
    <script type="text/javascript" src="/js/kaz.js"></script>
    <div id="act" th:replace="${act}"></div>  <!-- ←2-->
    <div id="header" th:replace="~{/members/admin/header :: header}">
	                                         </div>  <!-- ←7-->
    <div id="contents">
        <div id="side" th:replace="~{/members/admin/side :: side}">
	                                         </div>  <!-- ←8-->
        <div id="main" th:replace="${main}"></div>   <!-- ←3-->
        <p class="clear"></p>
    </div>
    <div id="footer" th:replace="~{/members/admin/footer :: footer}">
                                                 </div>  <!-- ←9-->
</div>

カレンダーを利用した日付入力のための関数を、createDatepicker.jsに作りました。

<script type="text/javascript" src="/js/createDatepicker.js"></script>
</body>
</html>

画面デザインのCSS screen.css ←11

template.htmlで「<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen,print" /> 」のように使用している。→11

■src\main\resources\static\css\screen.css

"reset.css"、"layout.css"、"common.css"、"parts.css"をインポートしている。

@charset "UTF-8";
/* CSS import */
@import "reset.css";
@import "layout.css";
@import "common.css";
@import "parts.css";
td.error input,
td.error select{
    background-color: #FFDDDD;
}
td.none{
    background-color: none;
}

td.error input,td.error selectで、エラー時の入力項目色を設定している。→12
・tdにあるinputまたはselect項目にerrorクラスが設定されているときの色です。

■実行時のhtmlより抜粋

<tr>
  <th width="18%" nowrap  colspan=2><label for="productsname">商品名&nbsp;&nbsp;
	  <font color="#FF6600">*</font></label></th>
  <td class="error">  <!-- ←12->
  <input class="form-jpn"   type="text" size="50" maxlength="50" 
        id="productsname" name="productsname" value="" />
  全角50文字以内&nbsp;&nbsp;
  <div class="font-s-red-form">『商品名』は必須入力です</div>
  </td>
</tr>
<tr>
  <th width="18%" nowrap colspan=2><label for="biztypeCd">業種ID&nbsp;&nbsp;
        <font color="#FF6600">*</font></label></th>
     <td class="error">  <!-- ←12->
       <select class="form-control" id="biztypeCd" name="biztypeCd">
           <option value="">---未選択---</option>
        ・・・(省略)
    </select>
  </td> 
</tr>

td.noneで正常時の入力項目色を設定している。→13
・tdにある項目にnoneクラスが設定されているときの色です。

■実行時のhtmlより抜粋

<tr>
  <th width="18%" nowrap  colspan=2><label for="productsname">商品名&nbsp;&nbsp;
	  <font color="#FF6600">*</font></label></th>
  <td class="none">  <!-- ←13->
  <input class="form-jpn"   type="text" size="50" maxlength="50" 
            id="productsname" name="productsname" value="" />
  全角50文字以内&nbsp;&nbsp;
  </td>
</tr>
<tr>
  <th width="18%" nowrap colspan=2><label for="biztypeCd">業種ID&nbsp;&nbsp;
        <font color="#FF6600">*</font></label></th>
  <td class="none">  <!-- ←13->
    <select class="form-control" id="biztypeCd" name="biztypeCd">
        <option value="">---未選択---</option>
        ・・・(省略)
  </td> 
</tr>

■src\main\resources\templates\members\admin\shohin\shohinAmendRegister.htmlより抜粋

テンプレート(Thymeleaf)は「<td th:class="${#fields.hasErrors('項目id')} ? 'error' : 'none'">」となっていて
エラー有無によりclass設定値を切り替えています。→14

<tr>
  <th width="18%" nowrap  colspan=2><label for="productsname">商品名&nbsp;&nbsp;
	          <font color="#FF6600">*</font></label></th>
  <td th:class="${#fields.hasErrors('productsname')} ? 'error' : 'none'">  <!-- ←13->
  <input th:field="*{productsname}"  class="form-jpn"   type="text" 
        size="50" maxlength="50" />
  全角50文字以内&nbsp;&nbsp;
  <div th:if="${#fields.hasErrors('productsname')}" 
        th:errors="*{productsname}" th:errorclass="font-s-red-form"></div>
  </td>
</tr>
<tr>
  <th width="18%" nowrap colspan=2><label for="biztypeCd">業種ID&nbsp;&nbsp;
        <font color="#FF6600">*</font></label></th>
    <td th:class="${#fields.hasErrors('biztypeCd')} ? 'error' : 'none'">  <!-- ←14->
      <select class="form-control" th:field="*{biztypeCd}">
          <option th:each="option : *{publicDbELEMENTS('biztypeCd', '---未選択---')}"
            th:value="${option.key}"
            th:text="${option.value}">
          </option>
      </select>
    <div th:if="${#fields.hasErrors('biztypeCd')}" th:errors="*{biztypeCd}" 
            th:errorclass="font-s-red-form"></div>
    </td> 
</tr>

■src\main\resources\static\css\reset.css リセット、フォント関連、リンクカラーを設定します。

@import "reset.css";の内容です。

@charset "UTF-8";
/* ブラウザ初期設定解除CSS */
/* ================= 目次 =====================
【1】リセット
【2】フォント関連
【3】リンクカラー
============================================ */
       ・・・(省略)

■src\main\resources\static\css\layout.css レイアウトを設定します。

@import "layout.css";の内容です。

@charset "UTF-8";
/* TOPページ基本レイアウト用CSS */
/* ================= 目次 =====================
【1】wrapper
【2】header
【3】login
【4】contents
【5】footer
============================================ */
       ・・・(省略)

■src\main\resources\static\css\common.css サイト共通使用CSSを設定します。

@import "common.css";の内容です。

@charset "UTF-8";
/* サイト共通使用CSS */
/* ================= 目次 =====================
【1】配置関連
【2】float 関連
【3】margin 関連
【4】hack 関連
【5】form 関連
【6】noscript
============================================ */
       ・・・(省略)

■src\main\resources\static\css\parts.css サイト内全ページで共通して使うCSSを設定します。

@import "parts.css";の内容です。
【6】Datepicker関連に、カレンダー利用のDatepicker関連のCSSを書きました。

@charset "UTF-8";
/* サイト内全ページで共通して使うCSS */
/* ================= 目次 =====================
【1】タイトル関連
【2】リストマーク関連
【3】囲み関連
【4】フォント関連
【5】テーブル関連
【6】Datepicker関連
============================================ */
       ・・・(省略)
/* =============================================================
 ■□■ 6. Datepicker関連 ■□■
============================================================= */
/* 祝祭日のカラー設定 */
/* ※ クラスが設定される要素の子要素に表示用のスタイリングを付与する */
.is-holiday > .ui-state-default {
  background-color: #ffecec;   /* 背景色を設定 */
  color: #f00!important;       /* 文字色を設定 */
}
/* 日曜日のカラー設定 */
.is-sunday > .ui-state-default {
  background-color: #ffecec;   /* 背景色を設定 */
  color: #f00!important;       /* 文字色を設定 */
}
/* 土曜日のカラー設定 */
.is-saturday > .ui-state-default {
  background-color: #eaeaff;   /* 背景色を設定 */
  color: #00f!important;       /* 文字色を設定 */
}
/* 平日のカラー設定 */
.is-weekday > .ui-state-default {
  color: #000!important;       /* 文字色を設定 */
}
/* ホバー時の動作 */
td.ui-datepicker-week-end a.ui-state-hover{
  opacity: 0.8;
}
/* 当日を示す色はそのままにするため「!important」を指定する */
td.ui-datepicker-week-end a.ui-state-highlight{
  background-color: #fffa90!important;
}
/* 入力日付のborderはそのままにするため「!important」を指定する */
td.ui-datepicker-week-end a.ui-state-active{
  border:1px solid #00f!important;
}