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

7 comments:

Unknown said...

Thanks for nice tutorials.I run this example with database but can't login?Can u tell me what i do for login?

Dave R. said...

Hi Rejwan,

I need a bit more information. Can you share the error that you are receiving?

Thanks,
Dave

Unknown said...

Hi Dave thank you for the tutorials, all is explained but when I runned the application ex17 I only see listStrategy where no one of USER , ADMIN, OR TRADER have acces so it s denied

I setted up all the database , and i even added :
@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");
}
to spring security to login
any idea why i don't see the complete sidebar and topbar? thanks

Dave R. said...

Hi Labihi,

You shouldn't need to update the configureGlobal method with user names. I just this morning migrated from Eclipse Kepler to Eclipse Luna, and this fresh environment is working fine with the imported project.

Did you update your database.properties to match your local MySQL database? Did you run the three .sql scripts to populate your database?

Can you share the error message that you receive when your tomcat installation starts?

Thanks,
Dave

Unknown said...

Hi, I don't have any error and yes i runned the application as it should, but when I sign in with the three users I don't find the strategy list as example, and all the elements of the side and top bar

I installed the latest code source from github ex16 and ex17 , but the same result thanks

Dave R. said...

Without an error message, it's a bit difficult to help. You will need to remove the users you created in the configureGlobal method.

Did you log into your MySQL database and confirm that all of the tables, constraints, and data are present...including the users?

What versions of Eclipse, MySQL, and Tomcat are you using? Have you configured your Eclipse instance to use your non-embedded Tomcat instance?

My guess is that you need to make some change(s) to your environment. I just downloaded the code from the repository and configured it with a new Eclipse version (Luna) and it all works.

Thanks,
Dave

Dave R. said...

Just realized that you didn't answer whether you updated your database.properties to match your local MySQL database? Did you run the three .sql scripts to populate your database?

Post a Comment