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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | <!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 = "' ' + ${currentUser} + ' '" > Dave </ 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 > |
2. Next we create the user list page. Notice the lines highlighted below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | <!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 > < 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 > < 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 > < span data-th-text = "#{button.label.edit}" >edit</ span > </ a > </ li > < li > < a href = "#" > < span class = "glyphicon glyphicon-trash" ></ span > < span data-th-text = "#{button.label.delete}" >delete</ span > </ a > </ li > </ ul > </ div > </ td > </ tr > </ tbody > </ table > </ div > < 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" >×</ 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" >×</ 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 > |
- 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | <!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> |
- 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | <!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