CustomMenu

Showing posts with label Bootstrap. Show all posts
Showing posts with label Bootstrap. Show all posts

Wednesday, April 30, 2014

Spring Thymeleaf CRUD User Maintenance - Part 3

This post builds on the code from part 2 of this series,Spring Thymeleaf CRUD User Maintenance - Part 2.  In this post, we will create the Thymeleaf CRUD pages, update the menus, and update the Twitter Bootstrap functionality (associated with alerts and actions menus).

The basic flow of the user pages will be the same as the flow of the strategy pages.  Selecting the user page link will take you to the user list page.  This page will display all users in the system and contain a form at the bottom of the page to add new users.  Each row in the list, will provide an action menu to edit or delete a user.  Success and failure of the three actions (add, edit, or delete) will return the user to the list page with a message displaying the action status.  Besides the list page, we will create edit and delete pages as well, and update the common Thymeleaf fragment page.

1. Let's start by updating the Thymeleaf fragment.
<!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" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.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]-->

     <title >SB-Admin</title>   
</head>

<body>
    <div id="wrapper">

        <nav data-th-fragment="top-nav" class="navbar navbar-default navbar-static-top" style="margin-bottom: 0">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#" data-th-href="@{/}">Option Algo System</a>
            </div>
            <!-- /.navbar-header -->

            <ul class="nav navbar-top-links navbar-right" data-th-with="currentUser=${#httpServletRequest.userPrincipal?.name}">
                    <li><a href="#" data-th-href="@{/}"            >Home</a></li>
                    <li><a href="#" data-th-href="@{/trading}"     >Trading</a></li>
                    <li><a href="#" data-th-href="@{/backtesting}" >Backtesting</a></li>
                    <li><a href="#" data-th-href="@{/admin}"       >Admin</a></li>
                    <li class="dropdown" data-th-if="${currentUser != null}">
                            <a class="dropdown-toggle" data-toggle="dropdown" href="#">
                                <i class="fa fa-user fa-fw"></i>
                                <font color="#049cbd" th:text="'&nbsp;' + ${currentUser} + '&nbsp;&nbsp;'">&nbsp;Dave&nbsp;&nbsp;</font>
                                <i class="fa fa-caret-down"></i>
                            </a>
                            <ul class="dropdown-menu dropdown-user">
                                <li><a href="#"><i class="fa fa-user fa-fw"></i>User Profile</a></li>
                                <li><a href="#"><i class="fa fa-gear fa-fw"></i>Settings</a></li>
                                <li class="divider"></li>
                                <li>
                                    <form class="navbar-form" data-th-action="@{/logout}" method="post">
                                       <label for="mySubmit" class="btn"><i class="fa fa-sign-out fa-fw"></i>Log Out</label>
                                       <input id="mySubmit" type="submit" value="Go" class="hidden" />
                                    </form>
                                </li>
                            </ul>
                            <!-- /.dropdown-user -->
                    </li>
            </ul>  <!-- /.navbar-top-links -->
        </nav>     <!-- /.navbar-static-top -->

        <nav data-th-fragment="vert-nav" class="navbar-default navbar-static-side" >
            <div class="sidebar-collapse">
                <ul class="nav" id="side-menu">
                    <li th:class="${(#httpServletRequest.requestURL.toString().contains('user'))} or
                                    ${(#httpServletRequest.requestURL.toString().contains('role'))} or
                                    ${(#httpServletRequest.requestURL.toString().contains('permission'))} ? 'active'">
                        <a href="#" data-th-href="@{/user/list}"><i class="fa fa-lock fa-fw"></i> Security<span class="fa arrow"></span></a>
                        <ul class="nav nav-second-level">
                            <li>
                                <a href="#" data-th-href="@{/user/list}">User List</a>
                            </li>
                            <li>
                                <a href="#" data-th-href="@{/role/list}">Role List</a>
                            </li>
                            <li>
                                <a href="#" data-th-href="@{/permission/list}">Permission List</a>
                            </li>
                        </ul>
                        <!-- /.nav-second-level -->
                    </li>
                    <li th:class="${(#httpServletRequest.requestURL.toString().contains('account'))} ? 'active'">
                        <a href="#" data-th-href="@{/account/list}"><i class="fa fa-user fa-fw"></i> Accounts<span class="fa arrow"></span></a>
                        <ul class="nav nav-second-level">
                            <li>
                                <a href="#" data-th-href="@{/account/list}">Account List</a>
                            </li>
                            <li>
                                <a href="#" data-th-href="@{/account-allocation/list}">Allocation List</a>
                            </li>
                        </ul>
                        <!-- /.nav-second-level -->
                    </li>
                    <li th:class="${(#httpServletRequest.requestURL.toString().contains('strategy'))} ? 'active'">
                        <a href="/strategy"><i class="fa fa-gears fa-fw"></i> Strategies<span class="fa arrow"></span></a>
                        <ul class="nav nav-second-level">
                            <li>
                                <a href="#" data-th-href="@{/strategy/list}">Strategy List</a>
                            </li>
                            <li>
                                <a href="/strategy/settings">Strategy Settings</a>
                            </li>
                            <li>
                                <a href="/strategy/run">Strategy Run</a>
                            </li>
                            <li>
                                <a href="/strategy/account">Strategy Account</a>
                            </li>
                        </ul>
                        <!-- /.nav-second-level -->
                    </li>
                    <li th:class="${(#httpServletRequest.requestURL.toString().contains('details/details'))} ? 'active'">
                        <a href="/trade/details"><i class="fa fa-tachometer fa-fw"></i> Trade Details<span class="fa arrow"></span></a>
                        <ul class="nav nav-second-level">
                            <li>
                                <a href="#">Orders</a>
                            </li>
                            <li>
                                <a href="#">Fills</a>
                            </li>
                            <li>
                                <a href="#">Transactions</a>
                            </li>
                            <li>
                                <a href="#">Positions</a>
                            </li>
                            <li>
                                <a href="#">Position Details</a>
                            </li>
                        </ul>
                        <!-- /.nav-second-level -->
                    </li>
                    <li th:class="${(#httpServletRequest.requestURL.toString().contains('summaries'))} ? 'active'">
                        <a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Trade Summaries<span class="fa arrow"></span></a>
                        <ul class="nav nav-second-level">
                            <li>
                                <a href="#">Trade Summary</a>
                            </li>
                            <li>
                                <a href="#">Trade Details</a>
                            </li>
                        </ul>
                        <!-- /.nav-second-level -->
                    </li>
                </ul> <!-- /#side-menu -->
            </div>    <!-- /.sidebar-collapse -->
        </nav>        <!-- /.navbar-static-side -->

        <div id="page-wrapper">
            <div class="row">
                <div class="col-lg-12">
                    <h1 class="page-header">Blank</h1>
                </div>
                <!-- /.col-lg-12 -->
            </div>
            <!-- /.row -->
        </div>
        <!-- /#page-wrapper -->

    </div>
    <!-- /#wrapper -->

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="../../../resources/js/jquery-1.11.0.min.js" 
        data-th-href="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="../../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-href="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script src="../../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-href="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script src="../../../resources/js/sb-admin.js" 
        data-th-href="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
The changes to this page are in the left navigation bar. The pattern in the highlighted code above is repeated for each of the sections in the nav. In lines 89 - 91 there is a conditional block to select this section of the nav if the page including this fragment has a URL that matches the pattern in the conditional. This conditional will cause the "security" section of the nav to open if we are on one of the pages associated with users, roles, or permissions.  Also, the links in the nav sections have been updated to the working links for the strategy and user pages.  This Thymeleaf fragment as displayed in a browser is shown below.


2. Next we create the user list page. Notice the lines highlighted below.
<!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" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.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>
    <style>
        .dropdown-menu {
          min-width: 0px;
        }    
    </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]-->

    <title data-th-text="#{user.list.page.title}">Title</title>
</head>

<body>

<div id="wrapper">                <!-- /#wrapper -->

    <div data-th-replace="fragments/sb-admin :: top-nav"></div>
    
    <div data-th-replace="fragments/sb-admin :: vert-nav"></div>

    <div id="page-wrapper">
        <div class="row">
            <div class="col-xs-12">
            
                <h4 class="page-header" data-th-text="#{user.list.table.title}">Configured Users</h4>
                <div class="table responsive">
                <table class="table table-striped table-bordered table-hover">
                    <thead>
                        <tr>
                            <th class="col-xs-1" data-th-text="#{user.list.id.label}">Id</th>
                            <th class="col-xs-3" data-th-text="#{user.list.user.label}">Username</th>
                            <th class="col-xs-2" data-th-text="#{user.list.pass.label}">Password</th>
                            <th class="col-xs-2" data-th-text="#{user.list.enabled.label}">Enabled</th>
                            <th class="col-xs-3" data-th-text="#{user.list.role.label}">Role</th>
                            <th class="col-xs-1" data-th-text="#{user.list.actions.label}">Action</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr data-th-each="user : ${users}">
                            <td data-th-text="${user.id}">1</td>
                            <td data-th-text="${user.username}">user1</td>
                            <td data-th-text="${user.password}">password1</td>
                            <td data-th-text="${user.enabled}">true</td>
                            <td data-th-text="${user.role.rolename}">ROLE_TEST1</td>
                            <td>
                                <div class="btn-group">
                                  <button class="btn btn-warning btn-xs dropdown-toggle" type="button" data-toggle="dropdown" data-th-text="#{user.list.actions.label}">
                                    Actions<span class="caret"></span>
                                  </button>
                                  <ul class="dropdown-menu">
                                    <li>
                                        <a href="#" data-th-href="@{/user/edit(id=${user.id})}">
                                            <span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;
                                            <span data-th-text="#{button.label.edit}">edit</span>
                                        </a>
                                    </li>
                                    <li>
                                        <a href="#" data-th-href="@{/user/delete(id=${user.id},phase=stage)}">
                                           <span class="glyphicon glyphicon-trash"></span>&nbsp;&nbsp;
                                           <span data-th-text="#{button.label.delete}">delete</span>
                                        </a>
                                    </li>
                                  </ul>
                                </div>                          
                            </td>
                        </tr>
                        <tr data-th-remove="all">
                            <td>2</td>
                            <td>user2</td>
                            <td>password2</td>
                            <td>true</td>
                            <td>ROLE_TEST2</td>
                            <td>
                                <div class="btn-group">
                                  <button class="btn btn-warning btn-xs dropdown-toggle" type="button" data-toggle="dropdown" 
                                        data-th-text="#{user.list.actions.label}">
                                    Actions<span class="caret"></span>
                                  </button>
                                  <ul class="dropdown-menu">
                                    <li>
                                        <a href="#">
                                            <span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;
                                            <span data-th-text="#{button.label.edit}">edit</span>
                                        </a>
                                    </li>
                                    <li>
                                        <a href="#">
                                           <span class="glyphicon glyphicon-trash"></span>&nbsp;&nbsp;
                                           <span data-th-text="#{button.label.delete}">delete</span>
                                        </a>
                                    </li>
                                  </ul>
                                </div>                          
                            </td>
                        </tr>
                    </tbody>
                </table>
                </div>
                
                <br />
                
                <form class="form" action="#" data-th-action="@{/user/add}" data-th-object="${userDTO}" method="post">
                <div class="table responsive">
                    <table class="no-border-on-me table ">
                        <thead>
                            <tr>
                                <th class="col-xs-1"></th>
                                <th class="col-xs-3" data-th-text="#{user.list.user.label}">Username</th>
                                <th class="col-xs-2" data-th-text="#{user.list.pass.label}">Password</th>
                                <th class="col-xs-2" data-th-text="#{user.list.enabled.label}">Enabled</th>
                                <th class="col-xs-3" data-th-text="#{user.list.role.label}">Role</th>
                                <th class="col-xs-1" data-th-text="#{user.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="*{username}" placeholder="Username"></input></td>
                                <td><input class="form-control" type="text" data-th-field="*{password}" placeholder="Password"></input></td>
                                <td>
                                    <select class="form-control" th:field="*{enabled}">
                                      <option th:each="type : ${enabledOptions}" th:value="${type}" th:text="${type}">Dropdown</option>
                                    </select>
                                </td>
                                <td>
                                    <select class="form-control" th:field="*{roleId}">
                                      <option th:each="role : ${allRoles}" th:value="${role.id}" th:text="${role.rolename}">Dropdown</option>
                                    </select>
                                </td>
                                <td>
                                    <button type="submit" class="btn btn-primary" data-th-text="#{button.label.add}">Add User</button>
                                </td>
                            </tr>
                            <tr>
                                <td class="col-xs-1"></td>
                                <td class="col-xs-3 text-danger" data-th-if="${#fields.hasErrors('username')}" data-th-errors="*{username}">username error</td>
                                <td class="col-xs-2 text-danger" data-th-if="${#fields.hasErrors('password')}" data-th-errors="*{password}">password error</td>
                                <td class="col-xs-2 text-danger" data-th-if="${#fields.hasErrors('enabled')}" data-th-errors="*{enabled}">enabled error</td>
                                <td class="col-xs-3 text-danger" data-th-if="${#fields.hasErrors('roleId')}" data-th-errors="*{roleId}">role error</td>
                                <td class="col-xs-1"></td>
                            </tr>
                        </tbody>
                    </table>
                </div>
                </form>
                 <div class="alert alert-danger alert-dismissable" th:if="${error != null}">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
                    <h4 data-th-text="${error}">Error!</h4>
                </div>
                <div class="alert alert-success alert-dismissable" th:if="${message != null}">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
                    <h4 data-th-text="${message}">Success!</h4>
                </div>              
                
                
            </div>  <!-- /.col-lg-12 -->                
        </div>      <!-- /.row -->              
    </div>          <!-- page wrapper -->
</div>              <!-- /#wrapper -->

        
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script type="text/javascript" src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-src="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script type="text/javascript" src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-src="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-src="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/sb-admin.js" 
        data-th-src="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
There are a number of lines highlighted above. We will go through each one:
  • The new style block in the head section controls the size of our new action dropdown menu and makes the dropdown fit better on the page.
  • The th-each loop is responsible for displaying each of the users in the system on their own row in table.  Notice that the object being displayed is a user from the "users" object bound to the model in the controller.  The "users" object is a list of User Entity objects...not DTO's.
  • Lines 82 - 100 contain the new action dropdown menu.  This menu contains links to the edit and delete functionality that prior to this change were separate buttons.
  • Line 139 is the first line of the "add" form.  Notice that the target object in the form is the User DTO that was bound to the model by the controller.
  • The two selelct blocks are the dropdown menus for a user to be assigned an "enabled" status and a role.  Note the use of our two @ModelAttributes bound by the controllers: "enabledOptions" and "allRoles".  Let's look at the role dropdown in a little more detail:
    • The target field of this dropdown is the roleId field on our DTO.  
    • The option section contains a th:each to loop through all of the roles in the "allRoles" list.
    • The value in the dropdown is the role id, but the role name is actually displayed.
    • When the user selects an item in the dropdown, the value of the item is assigned to the roleId field in the DTO.
  • Finally, at the bottom of the page we have two conditional display blocks.  One block is responsible for displaying messages sent from the controller associated with the attribute "error", and the other block is responsible for displaying messages sent from the controller that are associated with the attribute "message".

This Thymeleaf fragment as displayed in a browser is shown below.


3. We will now move on to the edit page.
<!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" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.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]-->

    <title data-th-text="#{user.edit.page.title}">Title</title>
</head>

<body>

<div id="wrapper">                <!-- /#wrapper -->

    <div data-th-replace="fragments/sb-admin :: top-nav"></div>
    
    <div data-th-replace="fragments/sb-admin :: vert-nav"></div>

    <div id="page-wrapper">
        <div class="row">
            <div class="col-lg-12">

                <h4 class="page-header" data-th-text="#{user.edit.head.title}">Edit User</h4>
                <div class="col-sm-2"></div>
                <div class="col-sm-6">
                    <form class="form-horizontal" action="#" data-th-action="@{/user/edit}" data-th-object="${userDTO}" method="post">
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{user.list.user.label}">Username</label>
                            <div class="col-sm-7">
                                <input type="text" hidden="hidden" data-th-value="*{id}" data-th-field="*{id}" ></input>
                                <input type="text" class="form-control" data-th-value="*{username}" data-th-field="*{username}" ></input>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{user.list.pass.label}">User Password</label>
                            <div class="col-sm-7">
                                <input type="text" class="form-control" data-th-value="*{password}" data-th-field="*{password}" ></input>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{user.list.enabled.label}">Enabled</label>
                            <div class="col-sm-7">
                                <select class="form-control" th:id="*{enabled}" th:field="*{enabled}">
                                  <option th:each="type : ${enabledOptions}" th:value="${type}" th:text="${type}">Dropdown</option>
                                </select>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{user.list.role.label}">Role</label>
                            <div class="col-sm-7">
                                <select class="form-control" th:id="*{roleId}" th:field="*{roleId}">
                                      <option th:each="role : ${allRoles}" th:value="${role.id}" th:text="${role.rolename}">Dropdown</option>
                                </select>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-sm-offset-5 col-sm-7" >
                                <button type="submit" class="btn btn-primary"        name="action" data-th-value="#{button.action.save}"   data-th-text="#{button.label.save}"  >Save</button>
                                <button type="submit" class="btn btn-default active" name="action" data-th-value="#{button.action.cancel}" data-th-text="#{button.label.cancel}">Cancel</button>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-sm-offset-5 col-sm-7" >
                                <p class="text-danger" data-th-if="${#fields.hasErrors('username')}" data-th-errors="*{username}">type error</p> 
                                <p class="text-danger" data-th-if="${#fields.hasErrors('password')}" data-th-errors="*{password}">name error</p> 
                            </div>
                        </div>
                    </form>
                </div>
                <div class="col-sm-4"></div>
                    
            </div>  <!-- /.col-lg-12 -->                
        </div>      <!-- /.row -->              
    </div>          <!-- page wrapper -->
</div>              <!-- /#wrapper -->

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script type="text/javascript" src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-src="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script type="text/javascript" src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-src="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-src="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/sb-admin.js" 
        data-th-src="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
You will notice in the code above several highlighted lines.
  • The first highlighted line shows that the object we are using in this form is the User DTO object. We could potentially be passing back role changes and these need to be represented by a roleId variable rather than a Role object. 
  • The first select block looks similar to the select blocks in the add form on the list page. 
    • In this code, we use th:id="*{enabled}" to indicate which "enabled" value should be pre-selected from the "enabledOptions" list (a ModelAttribute from the controller) on page load.  This represents the current value in the User DTO used to populate this page.
    • On submit, the value selected is assigned to th:field="*{enabled}", the "enabled" field in the User DTO.
  • The second select block is associated with a user role, and looks similar to both the add form and the prior select block. A couple of points to note:
    • The "select" block contains source and target variables :"roleId" from the User DTO.
    • The "option" block uses the array of Role objects (contained in the ModelAttribute "allRoles") for the "value" and "text" variable values.

A screenshot of the Thymleaf fragement displayed in a browser is shown below:


4. Lastly, we have the user delete page. This page displays our User Entity object prior to deletion, and gives the end user the opportunity to confirm that this user should be deleted from the system. The username, role, etc are displayed in disabled form fields.
<!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" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.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]-->

    <title data-th-text="#{user.delete.page.title}">Title</title>
</head>

<body>

<div id="wrapper">                <!-- /#wrapper -->

    <div data-th-replace="fragments/sb-admin :: top-nav"></div>
    
    <div data-th-replace="fragments/sb-admin :: vert-nav"></div>

    <div id="page-wrapper">
        <div class="row">
            <div class="col-lg-12">

                <h4 class="page-header" data-th-text="#{user.delete.head.title}">Delete Strategy</h4> 
                <div class="col-sm-2"></div>
                <div class="col-sm-6">
                    <form class="form-horizontal" action="#" method="get">
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{user.list.id.label}">User Id</label>
                            <div class="col-sm-7">
                                <input type="text" class="form-control" data-th-value="${user.id}" disabled="disabled" ></input>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{user.list.user.label}">Username</label>
                            <div class="col-sm-7">
                                <input type="text" class="form-control" data-th-value="${user.username}" disabled="disabled" ></input>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{user.list.pass.label}">User Password</label>
                            <div class="col-sm-7">
                                <input type="text" class="form-control" data-th-value="${user.password}" disabled="disabled" ></input>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{user.list.enabled.label}">Enabled</label>
                            <div class="col-sm-7">
                                <input type="text" class="form-control" data-th-value="${user.enabled}" disabled="disabled" ></input>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{user.list.role.label}">Role</label>
                            <div class="col-sm-7">
                                <input type="text" class="form-control" data-th-value="${user.role.rolename}" disabled="disabled" ></input>
                            </div>
                        </div>
                    </form>
                    
                    <div class="form-horizontal">
                        <div class="form-group">
                            <label class="col-sm-5 control-label"></label>
                            <div class="col-sm-7" >
                                <a href="#" data-th-href="@{/user/delete(id=${user.id},phase=#{button.action.delete} )}">
                                <button type="button" class="btn btn-primary" data-th-text="#{button.label.delete}">Delete</button></a>
                                
                                <a href="#" data-th-href="@{/user/delete(id=${user.id},phase=#{button.action.cancel} )}">
                                <button type="button" class="btn btn-default active" data-th-text="#{button.label.cancel}">Cancel</button></a>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-sm-4"></div>

            </div>  <!-- /.col-lg-12 -->                
        </div>      <!-- /.row -->              
    </div>          <!-- page wrapper -->
</div>              <!-- /#wrapper -->

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script type="text/javascript" src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-src="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script type="text/javascript" src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-src="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-src="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/sb-admin.js" 
        data-th-src="@{/resources/js/sb-admin.js}"></script>

</body>
</html>


5. The application now has working user CRUD functionality. Here are a few screenshots of the running application.
The User List Page

Successfully Adding a User

The Action Menu on the Uer List Page

We now have working CRUD functionality for our User Entity.

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

Thursday, February 27, 2014

Spring MVC 4 & Spring Security 3.2 - Part 1

In this series of posts, will add Spring Security to our application.  We will start by adding very simple authentication functionality driven by usernames and passwords contained in configuration files.  Once that functionality is operating correctly, we will move this authentication information into MySQL.  The last step will be to add role authorization to our application.

In part 1, we will follow along with the Spring Security presentation by Rob Winch/Spring.io:

1. We will first create two new configuration files, and modify our web application config file.
package com.dtr.oas.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityInitializer extends  AbstractSecurityWebApplicationInitializer {
}
package com.dtr.oas.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**", "/signup").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
        //.and().httpBasic();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().
        withUser("user").password("password").roles("USER").and().
        withUser("trader").password("password").roles("USER","TRADER").and().
        withUser("admin").password("password").roles("USER", "TRADER", "ADMIN");
    }
}
package com.dtr.oas.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("com.dtr.oas")
public class WebAppConfig extends WebMvcConfigurerAdapter {
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }

    // Maps resources path to webapp/resources
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }

    @Bean
    public UrlBasedViewResolver setupViewResolver() {
        UrlBasedViewResolver resolver = new UrlBasedViewResolver();
        resolver.setPrefix("/WEB-INF/pages/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }

    // Provides internationalization of messages
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename("messages");
        return source;
    }

}
When you are finished, your configuration directory structure should look like the image below.


2. With these three files in place (containing sample users and roles), our simple security is almost complete.  You will notice in the WebAppConfig.java file we added configuration for a controller to direct requests for "/login" to a custom login page.  This login page is called login.html and will need to be in the root of our WEB-INF/views directory.  This is the same directory containing our other strategy HTML pages.  The login page is shown below.
<!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" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.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]-->
    
    <title data-th-text="#{login.page.title}">Login Page</title>
</head>

<body>

    <div class="container">
        <div class="row">
            <div class="col-md-4 col-md-offset-4">
                <div class="login-panel panel panel-default">
                    <div class="panel-body">
                        <form name="f" data-th-action="@{/login}" method="post">
                            <fieldset>
                    <legend>Please Login</legend>
                    <div data-th-if="${param.error}" class="alert alert-danger">
                        Invalid username and password.
                    </div>
                    <div data-th-if="${param.logout}" class="alert alert-success">
                        You have been logged out.
                    </div>
                                <div class="form-group">
                                    <label for="username">Username</label>
                                    <input class="form-control" placeholder="User ID" type="text" id="username" name="username"></input>
                                </div>
                                <div class="form-group">
                                    <label for="password">Password</label>
                                    <input class="form-control" placeholder="Password" type="password" id="password" name="password"></input>
                                </div>
                                <div class="form-actions">
                                    <button type="submit" class="btn btn-lg btn-success btn-block">Login</button>
                                </div>
                            </fieldset>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-href="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-href="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-href="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script src="../../resources/js/sb-admin.js" 
        data-th-href="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
The head section of this file looks the same as our other Thymeleaf files, and the same goes for the script inclusion in the footer.  In between these sections is our login form using the HTTP-POST method, and Thymeleaf error reporting tags.


3. Our last step is to update our Thymeleaf horizontal navbar fragment to display the username and provide logout functionality.  The updated navbar fragment is shown below.
<nav data-th-fragment="top-nav" class="navbar navbar-default navbar-static-top" style="margin-bottom: 0">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">Option Algo System</a>
            </div>
            <!-- /.navbar-header -->

            <ul class="nav navbar-top-links navbar-right" data-th-with="currentUser=${#httpServletRequest.userPrincipal?.name}">
                    <li><a href="/"            >Home</a></li>
                    <li><a href="/trading"     >Trading</a></li>
                    <li><a href="/backtesting" >Backtesting</a></li>
                    <li><a href="/admin"       >Admin</a></li>
                    <li class="dropdown" data-th-if="${currentUser != null}">
                      <a class="dropdown-toggle" data-toggle="dropdown" href="#">
                          <i class="fa fa-user fa-fw"></i>
                          <font color="#049cbd" th:text="'&nbsp;' + ${currentUser} + '&nbsp;&nbsp;'">&nbsp;Dave&nbsp;&nbsp;</font>
                          <i class="fa fa-caret-down"></i>
                      </a>
                            <ul class="dropdown-menu dropdown-user">
                       <li><a href="#"><i class="fa fa-user fa-fw"></i>User Profile</a></li>
                       <li><a href="#"><i class="fa fa-gear fa-fw"></i>Settings</a></li>
                       <li class="divider"></li>
                       <li>
                                 <form class="navbar-form" data-th-action="@{/logout}" method="post">
                                 <label for="mySubmit" class="btn"><i class="fa fa-sign-out fa-fw"></i>Log Out</label>
                                 <input id="mySubmit" type="submit" value="Go" class="hidden" />
                                 </form>
                             </li>
                            </ul>
                      <!-- /.dropdown-user -->
              </li>
            </ul>  <!-- /.navbar-top-links -->
  </nav>     <!-- /.navbar-static-top -->

4. With these files in place, we can now attempt to login.  The first image below shows our login page as it is initially served up by Tomcat.  The second image shows the result of an incorrect password.



5. After successful login and navigation from the home page to the list page, you should see the screen in the image below.
The update to the navbar fragement, has given us a dropdown with the title containing the username of our logged in user.  The profile and settings links are stubs, but the logout button is functional.


6. After pressing the logout button, you should be taken back to the login screen, but with a logout message.


If you want more information on this topic, the following pages on the Spring Security site go over in a lot of detail, the steps above.

In the next post, we will move the user information from configuration files to a database.

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

Wednesday, February 26, 2014

Spring MVC 4 - Bootstrap Navigation and Thymeleaf Fragments

In this post, I am going to use Thymeleaf fragments and a Bootstrap based theme to add horizontal and vertical navbars to the example.  In addition, I've cleaned up the controller code a bit to only use Model objects where needed.

1. The cleaned up controller is shown below.
package com.dtr.oas.controller;

import java.util.List;
import java.util.Locale;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.dtr.oas.model.Strategy;
import com.dtr.oas.service.StrategyService;

@Controller
@RequestMapping(value = "/strategy")
public class StrategyController {
    static Logger logger = LoggerFactory.getLogger(StrategyController.class);

    @Autowired
    private StrategyService strategyService;

    @Autowired
    private MessageSource messageSource;

    @RequestMapping(value = {"/", "/list"}, method = RequestMethod.GET)
    public String listOfStrategies(Model model) {
        logger.info("IN: Strategy/list-GET");

        List<Strategy> strategies = strategyService.getStrategies();
        model.addAttribute("strategies", strategies);

        // if there was an error in /add, we do not want to overwrite
        // the existing strategy object containing the errors.
        if (!model.containsAttribute("strategy")) {
            logger.info("Adding Strategy object to model");
            Strategy strategy = new Strategy();
            model.addAttribute("strategy", strategy);
        }
        return "strategy-list";
    }              
    
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String addingStrategy(@Valid @ModelAttribute Strategy strategy,
            BindingResult result, RedirectAttributes redirectAttrs) {

        logger.info("IN: Strategy/add-POST");

        if (result.hasErrors()) {
            logger.info("Strategy-add error: " + result.toString());
            redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.strategy", result);
            redirectAttrs.addFlashAttribute("strategy", strategy);
            return "redirect:/strategy/list";
        } else {
            strategyService.addStrategy(strategy);
            String message = "Strategy " + strategy.getId() + " was successfully added";
            redirectAttrs.addFlashAttribute("message", message);
            return "redirect:/strategy/list";
        }
    }

    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    public String editStrategyPage(@RequestParam(value = "id", required = true) Integer id, Model model) {
        logger.info("IN: Strategy/edit-GET:  ID to query = " + id);

        if (!model.containsAttribute("strategy")) {
            logger.info("Adding Strategy object to model");
            Strategy strategy = strategyService.getStrategy(id);
            logger.info("Strategy/edit-GET:  " + strategy.toString());
            model.addAttribute("strategy", strategy);
        }

        return "strategy-edit";
    }
        
    @RequestMapping(value = "/edit", method = RequestMethod.POST)
    public String editingStrategy(@Valid @ModelAttribute Strategy strategy,
            BindingResult result, RedirectAttributes redirectAttrs,
            @RequestParam(value = "action", required = true) String action) {

        logger.info("IN: Strategy/edit-POST: " + action);

        if (action.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
            String message = "Strategy " + strategy.getId() + " edit cancelled";
            redirectAttrs.addFlashAttribute("message", message);
        } else if (result.hasErrors()) {
            logger.info("Strategy-edit error: " + result.toString());
            redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.strategy", result);
            redirectAttrs.addFlashAttribute("strategy", strategy);
            return "redirect:/strategy/edit?id=" + strategy.getId();
        } else if (action.equals(messageSource.getMessage("button.action.save",  null, Locale.US))) {
            logger.info("Strategy/edit-POST:  " + strategy.toString());
            strategyService.updateStrategy(strategy);
            String message = "Strategy " + strategy.getId() + " was successfully edited";
            redirectAttrs.addFlashAttribute("message", message);
        }

        return "redirect:/strategy/list";
    }

    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    public String deleteStrategyPage(
            @RequestParam(value = "id", required = true) Integer id,
            @RequestParam(value = "phase", required = true) String phase,
            Model model) {

        Strategy strategy = strategyService.getStrategy(id);
        logger.info("IN: Strategy/delete-GET | id = " + id + " | phase = " + phase + " | " + strategy.toString());

        if (phase.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
            String message = "Strategy delete was cancelled.";
            model.addAttribute("message", message);
            return "redirect:/strategy/list";
        } else if (phase.equals(messageSource.getMessage("button.action.stage", null, Locale.US))) {
            String message = "Strategy " + strategy.getId() + " queued for display.";
            model.addAttribute("strategy", strategy);
            model.addAttribute("message", message);
            return "strategy-delete";
        } else if (phase.equals(messageSource.getMessage("button.action.delete", null, Locale.US))) {
            strategyService.deleteStrategy(id);
            String message = "Strategy " + strategy.getId() + " was successfully deleted";
            model.addAttribute("message", message);
            return "redirect:/strategy/list";
        }

        return "redirect:/strategy/list";
    }
}

2. Next, we are going to use the SB-Admin theme based on Bootstrap.  Download the theme, sb-admin-v2.zip, at: http://startbootstrap.com/sb-admin-v2


3. After you unzip the theme, review the file "blank.html" highlighted in the image below.  We will need to recreate the sections of this page in our four Thymeleaf html pages.  Also, notice the directory structure that we will need to reproduce in our Eclipse project.




4. The original directory/file structure under webapp/resources looked like the first image below.  We will need to copy the files from the sb-admin-v2 template to create the directory/file structure shown in the second image.
Original Directory Structure

New Directory Structure
 You will notice that we only copied over the files from the template that we needed to recreate the navbars.  The files and directories include:
  • resources/css/sb-admin.css
  • resources/font-awsome/
  • resources/js/sb-admin.css
  • resources/js/plugins/metisMenu/

5. Next, copy the file "blank.html" to a new directory named "fragments" under our WEB-INF/views directory.  Rename this file to "sb-admin.html".  We are going to convert this file to a Thymeleaf file, containing fragments that we can reuse in our other four Thymeleaf HTML files.  After removing all of the dummy data in the original file, and using Thymeleaf tags, we now have the file below.
<!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" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.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]-->
        
     <title >SB-Admin</title>   
</head>

<body>
    <div id="wrapper">

        <nav data-th-fragment="top-nav" class="navbar navbar-default navbar-static-top" style="margin-bottom: 0">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">Option Algo System</a>
            </div>
            <!-- /.navbar-header -->

            <ul class="nav navbar-top-links navbar-right">
                    <li><a href="/"            >Home</a></li>
                    <li><a href="/trading"     >Trading</a></li>
                    <li><a href="/backtesting" >Backtesting</a></li>
                    <li><a href="/admin"       >Admin</a></li>
                    <li><a href="#"><font color="#049cbd">Dave</font></a></li>
                    <li><a href="#"><button type="button" class="btn btn-default btn-xs btn-primary">
                                        <span class="glyphicon glyphicon-off"></span></button>
                       </a></li>
            </ul>  <!-- /.navbar-top-links -->
        </nav>     <!-- /.navbar-static-top -->

        <nav data-th-fragment="vert-nav-admin" class="navbar-default navbar-static-side" >
            <div class="sidebar-collapse">
                <ul class="nav" id="side-menu">
                    <li>
                        <a href="/roles"><i class="fa fa-lock fa-fw"></i> Roles</a>
                    </li>
                    <li>
                        <a href="/users"><i class="fa fa-user fa-fw"></i> Users</a>
                    </li>
                    <li>
                        <a href="/accounts"><i class="fa fa-tasks fa-fw"></i> Accounts</a>
                    </li>
                    <li>
                        <a href="/strategy"><i class="fa fa-gears fa-fw"></i> Strategies<span class="fa arrow"></span></a>
                        <ul class="nav nav-second-level">
                            <li>
                                <a href="/strategy/list">Strategy List</a>
                            </li>
                            <li>
                                <a href="/strategy/settings">Strategy Settings</a>
                            </li>
                            <li>
                                <a href="/strategy/run">Strategy Run</a>
                            </li>
                            <li>
                                <a href="/strategy/account">Strategy Account</a>
                            </li>
                        </ul>
                        <!-- /.nav-second-level -->
                    </li>
                    <li>
                        <a href="/trade/details"><i class="fa fa-tachometer fa-fw"></i> Trade Details<span class="fa arrow"></span></a>
                        <ul class="nav nav-second-level">
                            <li>
                                <a href="#">Orders</a>
                            </li>
                            <li>
                                <a href="#">Fills</a>
                            </li>
                            <li>
                                <a href="#">Transactions</a>
                            </li>
                            <li>
                                <a href="#">Positions</a>
                            </li>
                            <li>
                                <a href="#">Position Details</a>
                            </li>
                        </ul>
                        <!-- /.nav-second-level -->
                    </li>
                    <li class="active">
                        <a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Trade Summaries<span class="fa arrow"></span></a>
                        <ul class="nav nav-second-level">
                            <li>
                                <a href="#">Trade Summary</a>
                            </li>
                            <li>
                                <a href="#">Trade Details</a>
                            </li>
                        </ul>
                        <!-- /.nav-second-level -->
                    </li>
                </ul> <!-- /#side-menu -->
            </div>    <!-- /.sidebar-collapse -->
        </nav>        <!-- /.navbar-static-side -->

        <div id="page-wrapper">
            <div class="row">
                <div class="col-lg-12">
                    <h1 class="page-header">Blank</h1>
                </div>
                <!-- /.col-lg-12 -->
            </div>
            <!-- /.row -->
        </div>
        <!-- /#page-wrapper -->

    </div>
    <!-- /#wrapper -->

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="../../../resources/js/jquery-1.11.0.min.js" 
        data-th-href="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="../../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-href="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script src="../../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-href="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script src="../../../resources/js/sb-admin.js" 
        data-th-href="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
There are a few points to notice in the file above:
  • With the excpetiion of including new css files, the head section of the file looks exactly like our Thymeleaf HTML files.
  • The first tag inside the body tag is a div tag with an id="wrapper.  This will need to be the first tag inside the body of our four HTML files.
  • The first nav block is our horizontal navbar.  I've modifed it slightly to look more like the header that will be in the trading application.  This first nav block is also identified as a Thymeleaf fragment with the tag data-th-fragment="top-nav".
  • The second nav block, at the same level as the prior bullet, is the vertical navbar.  This has also been modified to list trading related functions, but not necessarily the final layout.  This nav block is also identified as a Thymeleaf fragment with the tag data-th-fragment="vert-nav-admin".
  • The last block is the placeholder for our actual pages, and starts with the tag div id="page-wrapper".
  • At the end of the file we again include the javascript files required for Bootstrap and the SB-Admin theme.
  • Notice that all of the source paths in this file have three levels of ../ rather than two levels.  This is because our template is included one level lower than our view files.  These references will be two levels deep in our view files.

6. If you open this template file in a browser, it should look like the one below.  It is tricky to get all of the tags and paths correct, so this might take a bit of troubleshooting.

7. Now, in each of the four HTML files (home.html, strategy-delete.html. strategy-edit.html, strategy-list.html), we will need to update the header and footer, and include fragment references to our nav bars.  The home.html file should look like the file below.  The other updated files are in the GitHub repository for this post.
<!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" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.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]-->

    <title data-th-text="#{strategy.list.page.title}">Title</title>
</head>

<body>

<div id="wrapper">                <!-- /#wrapper -->

    <div data-th-replace="fragments/sb-admin :: top-nav"></div>
    
    <div data-th-replace="fragments/sb-admin :: vert-nav-admin"></div>

    <div id="page-wrapper">
        <div class="row">
            <div class="col-lg-12">
            
    <h4 class="page-header" data-th-text="#{strategy.list.table.title}">Configured Strategies</h4>
    <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>
    
    <br />
    
    <form class="form" action="#" data-th-action="@{/strategy/add}" data-th-object="${strategy}" 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="#{button.label.add.strategy}">Add Strategy</button>
        </td>
       </tr>
       <tr>
        <td class="col-sm-1"></td>
        <td class="col-sm-4 text-danger" data-th-if="${#fields.hasErrors('type')}" data-th-errors="*{type}">type error</td>
        <td class="col-sm-4 text-danger" data-th-if="${#fields.hasErrors('name')}" data-th-errors="*{name}">name error</td>
        <td class="col-sm-2"></td>
       </tr>
      </tbody>
     </table>
    </div>
    </form>
    
            </div>  <!-- /.col-lg-12 -->    
        </div>      <!-- /.row -->    
    </div>      <!-- page wrapper -->
</div>              <!-- /#wrapper -->

        
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script type="text/javascript" src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-src="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script type="text/javascript" src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-src="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-src="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/sb-admin.js" 
        data-th-src="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
This files follows the same structure as the "sb-admin.html" structure, the same div tags outlining the main body of the page.  At the top of this page, the navbars from the "sb-admin.html" file are included with the th:replace tags.


8. Screenshots of the home.html and strategy-list.html files are shown below.



In future posts we will look at integrating Spring Security, JPA/connection pooling, and unit testing.  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-ex07-navigation