CustomMenu

Friday, February 21, 2014

Spring MVC 4 - Thymeleaf and Bootstrap

In this post, we build on the example that we finished in the last post, Spring MVC 4 - Thymeleaf CRUD - Part 4.  We will integrate Twitter Bootstrap into our Spring/Thymeleaf CRUD application.

1. The first step is to download both Twitter Bootstrap and jQuery.  I installed versions 3.1.1 and 1.11.0 respectively.  The download links are:

2. Extract the artifacts from the zip files and copy them to the "resources" directory under "webapp".  Also, delete the old css file, style.css.  Your directory structure should look like the image below, with the directories "css", "fonts", and "js" populated.

3. Now we will need to modify each of our view pages.  Besides adding the css and js file references to our Thymeleaf files, we ill need to rework the html to include the div tags commonly used by Bootstrap.


4. The reworked home.html page is show below.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:th="http://www.thymeleaf.org">

<head data-th-fragment="header">
 <meta charset="utf-8" />
 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title data-th-text="#{strategy.list.page.title}">Title</title>
 
 <!-- Bootstrap -->
 <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" th:href="@{/resources/css/bootstrap-3.1.1.min.css}" />

 <title th:text="#{user.page.title}">Title</title>
 
 <!-- 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="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
       <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
     <![endif]-->
</head>

<body>
 <h1 data-th-text="#{home.page.title}">Title</h1>
 <p>
 <a href="strategy/list.html">Strategy list</a><br/>
 </p>

 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
 <script type="text/css" src="../../resources/js/jquery-1.11.0.min.js" th:href="@{/resources/js/jquery-1.11.0.min.js}"></script>
 <!-- Include all compiled plugins (below), or include individual files as needed -->
 <script type="text/css" src="../../resources/js/bootstrap-3.3.3.min.js" th:href="@{/resources/js/bootstrap-3.3.3.min.js}"></script>
</body>
</html>
In this page, we included the Bootstrap Template components, as we will on the other three html pages.  The head section of the page is entirely the Bootstrap Template, other than the title and Thymeleaf references.  Just before the body tag is closed, the two Bootstrap required js references are included, as suggested in teh Bootstrap Template.


5. The reworked strategy-list.html page is show below.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:th="http://www.thymeleaf.org">

<head data-th-fragment="header">
 <meta charset="utf-8" />
 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title data-th-text="#{strategy.list.page.title}">Title</title>
 
 <!-- Bootstrap -->
 <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" th:href="@{/resources/css/bootstrap-3.1.1.min.css}" />
 
 <style>
  .no-border-on-me>thead>tr>th,
  .no-border-on-me>tbody>tr>th,
  .no-border-on-me>tfoot>tr>th,
  .no-border-on-me>thead>tr>td,
  .no-border-on-me>tbody>tr>td,
  .no-border-on-me>tfoot>tr>td
  {
   border-top-style: none;
   border-bottom-style: none;
  }
 </style>
 <!-- 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="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
       <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
     <![endif]-->
</head>
<body>
 <div class="container-fluid">
  <div class="row">
   <div class="main">
    <h3 data-th-text="#{strategy.list.table.title}">Configured Strategies</h3>
    <div class="table responsive">
    <table class="table table-striped table-bordered table-hover">
     <thead>
      <tr>
       <th class="col-sm-1" data-th-text="#{strategy.list.id.label}">Id</th>
       <th class="col-sm-4" data-th-text="#{strategy.list.type.label}">Strategy Type</th>
       <th class="col-sm-4" data-th-text="#{strategy.list.name.label}">Strategy Name</th>
       <th class="col-sm-2" data-th-text="#{strategy.list.actions.label}">Action</th>
      </tr>
     </thead>
     <tbody>
      <tr data-th-each="strategy : ${strategies}">
       <td data-th-text="${strategy.id}">1</td>
       <td data-th-text="${strategy.type}">Iron Butterfly</td>
       <td data-th-text="${strategy.name}">Triple Butter</td>
       <td style="text-align: center;">
        <a href="#" data-th-href="@{/strategy/edit(id=${strategy.id})}">
         <button type="button" class="btn btn-default btn-xs">
          <span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;Edit
         </button></a> &nbsp; 
        <a href="#" data-th-href="@{/strategy/delete(id=${strategy.id},phase=stage)}">
         <button type="button" class="btn btn-default btn-xs">
          <span class="glyphicon glyphicon-trash"></span>&nbsp;&nbsp;Delete
         </button></a>
       </td>
      </tr>
      <tr data-th-remove="all">
       <td>2</td>
       <td>Iron Condor</td>
       <td>High Prob Hedged</td>
       <td style="text-align: center;">
        <a href="#">
         <button type="button" class="btn btn-default btn-xs">
          <span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;Edit
         </button></a>&nbsp; 
        <a href="#">
         <button type="button" class="btn btn-default btn-xs">
          <span class="glyphicon glyphicon-trash"></span>&nbsp;&nbsp;Delete
         </button></a>
       </td>
      </tr>
     </tbody>
    </table>
    </div>

    <form class="form" action="#" data-th-action="@{/strategy/add}" data-th-object="${strategyDTO}" method="post">
    <div class="table responsive">
     <table class="no-border-on-me table ">
      <thead>
       <tr>
        <th class="col-sm-1"></th>
        <th class="col-sm-4" data-th-text="#{strategy.list.type.label}">Strategy Type</th>
        <th class="col-sm-4" data-th-text="#{strategy.list.name.label}">Strategy Name</th>
        <th class="col-sm-2" data-th-text="#{strategy.list.actions.label}">Action</th>
       </tr>
      </thead>
      <tbody>
       <tr>
        <td><input type="text" hidden="hidden" data-th-field="*{id}"></input></td>
        <td><input class="form-control" type="text" data-th-field="*{type}" placeholder="Type"></input></td>
        <td><input class="form-control" type="text" data-th-field="*{name}" placeholder="Name"></input></td>
        <td>
         <button type="submit" class="btn btn-primary" data-th-text="#{add.strategy.button.label}">Add Strategy</button>
        </td>
       </tr>
      </tbody>
     </table>
    </div>
    </form>
    
   </div>  <!-- END MAIN TAG -->
  </div>  <!-- END ROW TAG -->
 </div> <!-- END CONTAINER TAG -->

 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
 <script type="text/css" src="../../resources/js/jquery-1.11.0.min.js" th:href="@{/resources/js/jquery-1.11.0.min.js}"></script>
 <!-- Include all compiled plugins (below), or include individual files as needed -->
 <script type="text/css" src="../../resources/js/bootstrap-3.3.3.min.js" th:href="@{/resources/js/bootstrap-3.3.3.min.js}"></script>
</body>
</html>
A lot of changes were made to this page, with many of the changes a result of trial-and-error.  At the top, you will notice a custom css style.  This was added to modify the appearance of the table responsible for adding a strategy.

In the tables you will notice the addition of glyphicons, to make button functionality more clear.  You will also see the inclusion of div tags and liberal references to class attributes on many of the tags.  These two items are responsible for the sizing and appearance of the page.  In order to better understand how much space a given div occupied, I would add the following attribute inside the div tag in question: style="background:#888888;"


6. The reworked strategy-edit.html page is show below.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:th="http://www.thymeleaf.org">

<head data-th-fragment="header">
 <meta charset="utf-8" />
 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title data-th-text="#{strategy.list.page.title}">Title</title>
 
 <!-- Bootstrap -->
 <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" th:href="@{/resources/css/bootstrap-3.1.1.min.css}" />
 
 <style>
  .no-border-on-me>thead>tr>th,
  .no-border-on-me>tbody>tr>th,
  .no-border-on-me>tfoot>tr>th,
  .no-border-on-me>thead>tr>td,
  .no-border-on-me>tbody>tr>td,
  .no-border-on-me>tfoot>tr>td
  {
   border-top-style: none;
   border-bottom-style: none;
  }
 </style>
 <!-- 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="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
       <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
     <![endif]-->
</head>
<body>
 <div class="container-fluid">
  <div class="row">
   <div class="main">
   
    <div class="col-sm-3"></div>
    <div class="col-sm-6">
     <h3 data-th-text="#{strategy.edit.head.title}">Edit Strategy</h3>
     <form class="form-horizontal" action="#" data-th-action="@{/strategy/edit}" data-th-object="${strategyDTO}" method="post">
      <div class="form-group">
       <label class="col-sm-4 control-label" data-th-text="#{strategy.list.type.label}">Strategy Type</label>
       <div class="col-sm-8">
        <input type="text" hidden="hidden" data-th-value="*{id}" data-th-field="*{id}" ></input>
        <input type="text" class="form-control" data-th-value="*{type}" data-th-field="*{type}" ></input>
       </div>
      </div>
      <div class="form-group">
       <label class="col-sm-4 control-label" data-th-text="#{strategy.list.name.label}">Strategy Name</label>
       <div class="col-sm-8">
        <input type="text" class="form-control" data-th-value="*{name}" data-th-field="*{name}" ></input>
       </div>
      </div>
      <div class="form-group">
       <div class="col-sm-offset-4 col-sm-8" >
        <button type="submit" class="btn btn-primary" name="action" value="save" data-th-text="#{update.button.label}">Save</button>
        <button type="submit" class="btn btn-default active" name="action" value="cancel" data-th-text="#{cancel.button.label}">Cancel</button>
       </div>
      </div>
     </form>
    </div>
    <div class="col-sm-3"></div>
    
   </div>  <!-- END MAIN TAG -->
  </div>  <!-- END ROW TAG -->
 </div> <!-- END CONTAINER TAG -->

 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
 <script type="text/css" src="../../resources/js/jquery-1.11.0.min.js" th:href="@{/resources/js/jquery-1.11.0.min.js}"></script>
 <!-- Include all compiled plugins (below), or include individual files as needed -->
 <script type="text/css" src="../../resources/js/bootstrap-3.3.3.min.js" th:href="@{/resources/js/bootstrap-3.3.3.min.js}"></script>
</body>
</html>
As with the list page, the edit page makes heavy use of div tags and the class attribute.  Many of the class attributes are used for sizing, with the sizing values looking like "col-sm-#".  With nearly all Bootstrap pages you will notice a common page structure inside the html body tag:

   div class = container
      div class = row
         div class = main
            ... content here
         div
      div
   div

The page above is no exception.  Also notice the button class attribute values and the class attribute value of "form-group" in the page.  The latter does exactly what it sounds like, grouping functionality within a form.


7. The reworked strategy-delete.html page is show below.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:th="http://www.thymeleaf.org">

<head data-th-fragment="header">
 <meta charset="utf-8" />
 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title data-th-text="#{strategy.list.page.title}">Title</title>
 
 <!-- Bootstrap -->
 <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" th:href="@{/resources/css/bootstrap-3.1.1.min.css}" />
 
 <style>
  .no-border-on-me>thead>tr>th,
  .no-border-on-me>tbody>tr>th,
  .no-border-on-me>tfoot>tr>th,
  .no-border-on-me>thead>tr>td,
  .no-border-on-me>tbody>tr>td,
  .no-border-on-me>tfoot>tr>td
  {
   border-top-style: none;
   border-bottom-style: none;
  }
 </style>
 <!-- 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="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
       <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
     <![endif]-->
</head>
<body>
 <div class="container-fluid">
  <div class="row">
   <div class="main">

    <div class="col-sm-3"></div>
    <div class="col-sm-6">
     <h3 data-th-text="#{strategy.delete.head.title}">Delete Strategy</h3>
     <form class="form-horizontal" action="#" method="get">
      <div class="form-group">
       <label class="col-sm-4 control-label" data-th-text="#{strategy.list.id.label}">Strategy Id</label>
       <div class="col-sm-8">
        <input type="text" class="form-control" data-th-field="${strategyDTO.id}" disabled="disabled"></input>
       </div>
      </div>
      <div class="form-group">
       <label class="col-sm-4 control-label" data-th-text="#{strategy.list.type.label}">Strategy Type</label>
       <div class="col-sm-8">
        <input type="text" class="form-control" data-th-field="${strategyDTO.type}" disabled="disabled"></input>
       </div>
      </div>
      <div class="form-group">
       <label class="col-sm-4 control-label" data-th-text="#{strategy.list.name.label}">Strategy Name</label>
       <div class="col-sm-8">
        <input type="text" class="form-control" data-th-field="${strategyDTO.name}" disabled="disabled"></input>
       </div>
      </div>
     </form>
     <div class="form-horizontal">
      <div class="form-group">
       <label class="col-sm-4 control-label"></label>
       <div class="col-sm-8" >
        <a href="#" data-th-href="@{/strategy/delete(id=${strategyDTO.id},phase=confirm)}">
                 <button type="button" class="btn btn-primary" data-th-text="#{delete.button.label}">Delete</button></a>
                 
        <a href="#" data-th-href="@{/strategy/delete(id=${strategyDTO.id},phase=cancel)}">
                 <button type="button" class="btn btn-default active" data-th-text="#{cancel.button.label}">Cancel</button></a>
                </div>
      </div>
     </div>
    </div>
    <div class="col-sm-3"></div>
   </div>  <!-- END MAIN TAG -->
  </div>  <!-- END ROW TAG -->
 </div> <!-- END CONTAINER TAG -->

 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
 <script type="text/css" src="../../resources/js/jquery-1.11.0.min.js" th:href="@{/resources/js/jquery-1.11.0.min.js}"></script>
 <!-- Include all compiled plugins (below), or include individual files as needed -->
 <script type="text/css" src="../../resources/js/bootstrap-3.3.3.min.js" th:href="@{/resources/js/bootstrap-3.3.3.min.js}"></script>
</body>
</html>
There are two key items to note on this page.  We used an input field for a consistent look-and-feel even though we do not allow input on this page.  In order to make the fields read-only, we used the Bootstrap input tag attribute of disabled="disabled".

The other item to note is how a button was used with the "a href=..." tag/attribute.  In order to maintain a consistent look-and-feel, the following button class attributes were used:
  • class="btn btn-primary"
  • class="btn btn-default active"

8. Now, let's take a look at our new pages, starting with the new home.html page.

9. Here is our new strategy-list.html page.

10. And our strategy-edit.html page.

11. And finally our strategy-delete.html page.

In future posts we will look at input validation, modal dialogues, navigation/themes/Thymeleaf templates, integrating Spring Security, JPA/connection pooling, and unit testing.  I changed this list slightly after working on this exercise.  After these steps are complete, we should be ready to start building out the trading system core.

Code at GitHub: https://github.com/dtr-trading/spring-ex05

1 comment:

Unknown said...

Hi:

Nice tutorial!

Can you show your servlet-context file??

I have this:



and a folder called css as you show in your tutorial but styles are not applied on my html file :(

Thanks!

Post a Comment