Browse Source

Re-format web forms: add authentication functionality, add Thymeleaf

security, introduce fragments
Signed-off-by: Pekka Helenius <fincer89@hotmail.com>
v0.0.3-alpha
Pekka Helenius 4 years ago
parent
commit
845d2d80a5
7 changed files with 378 additions and 444 deletions
  1. +5
    -0
      bookstore/src/main/resources/messages.properties
  2. +20
    -210
      bookstore/src/main/resources/templates/bookadd.html
  3. +19
    -223
      bookstore/src/main/resources/templates/bookedit.html
  4. +41
    -11
      bookstore/src/main/resources/templates/booklist.html
  5. +204
    -0
      bookstore/src/main/resources/templates/fragments/bookfields.html
  6. +45
    -0
      bookstore/src/main/resources/templates/fragments/devusers.html
  7. +44
    -0
      bookstore/src/main/resources/templates/fragments/loginout.html

+ 5
- 0
bookstore/src/main/resources/messages.properties View File

@ -59,6 +59,8 @@ page.text.list.actions = Actions
page.text.list.delete = Delete
page.text.list.edit = Edit
page.text.list.json = Get
page.text.list.authenticated = Logged in as
page.text.list.anon.info = Nothing but empty abyss here. Sign in to see some content.
page.text.apiref.warning.a = NOTE\:\ these direct JSON keys may differ in name \&\ count when comparing to publicly exposed JSON book data due to custom JSON serializers used in Java code.
page.text.apiref.warning.b = NOTE\:\ You may need to escape \&\ symbol when using multiple parameters with curl command in a shell environment. Altenatively, use quotes.
@ -96,6 +98,9 @@ button.book.edit = Update book
button.page.list.return = Return to book list page
button.page.list.json = Get list as JSON
button.page.login = Sign in
button.page.logout = Sign out
button.page.apiref = How to: REST API link references
page.symbols.currency = \u20AC

+ 20
- 210
bookstore/src/main/resources/templates/bookadd.html View File

@ -12,222 +12,32 @@
</head>
<body>
<div class="col-md-4 mb-3">
<div style="display: flex;">
<div class="col-md-4 mb-3">
<h1 th:text="${#messages.msgOrNull('page.title.webform.add')} ?: 'page.title.webform.add'">
page.title.webform.add
</h1>
<h1 th:text="${#messages.msgOrNull('page.title.webform.add')} ?: 'page.title.webform.add'">
page.title.webform.add
</h1>
<form th:object="${book}" action="#" th:action="@{__${addpage}__}" method="post">
<form th:object="${book}" action="#" th:action="@{__${addpage}__}" method="post">
<th:block th:replace="fragments/bookfields :: bookfields"/>
<div class="bookform-section">
<div>
<h3 th:text="${#messages.msgOrNull('book.author')} ?: 'book.author'">
book.author
</h3>
</div>
<div class="form-group row">
<div class="col">
<label for="BookAuthorFirstName"
th:text="${#messages.msgOrNull('book.author.firstName')} ?: 'book.author.firstName'">
book.author.firstName
</label>
<input class="form-control" type="text" th:field="*{author.firstName}" placeholder="Book author first name"/>
<button class="btn btn-primary" type="submit"
th:text="${#messages.msgOrNull('button.book.add')} ?: 'button.book.add'">
button.book.add
</button>
</form>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.author.firstname')} ?: 'book.desc.set.author.firstname'">
book.desc.set.author.firstname
</small>
<br>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.author.firstname')} ?: 'book.desc.example.author.firstname'">
book.desc.example.author.firstname
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author.firstName')}"
th:errors="*{author.firstName}">Invalid author first name value</div>
</div>
<div class="col">
<label for="BookAuthorLastName"
th:text="${#messages.msgOrNull('book.author.lastName')} ?: 'book.author.lastName'">
book.author.lastName
</label>
<input class="form-control" type="text" th:field="*{author.lastName}" placeholder="Book author last name"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.author.lastname')} ?: 'book.desc.set.author.lastname'">
book.desc.set.author.lastname
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.author.lastname')} ?: 'book.desc.example.author.lastname'">
book.desc.example.author.lastname
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author.lastName')}"
th:errors="*{author.lastName}">Invalid author last name value</div>
</div>
</div>
</div>
<div class="form-group bookform-section">
<label for="BookTitle"
th:text="${#messages.msgOrNull('book.title')} ?: 'book.title'">
book.title
</label>
<input class="form-control" type="text" th:field="*{title}" placeholder="Book title"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.title')} ?: 'book.desc.set.title'">
book.desc.set.title
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.title')} ?: 'book.desc.example.title'">
book.desc.example.title
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('title')}"
th:errors="*{title}">Invalid title value</div>
</div>
<div class="form-group bookform-section">
<label for="BookISBN"
th:text="${#messages.msgOrNull('book.isbn')} ?: 'book.isbn'">
book.isbn
</label>
<input class="form-control" type="text" th:field="*{isbn}" placeholder="Book ISBN code"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.isbn')} ?: 'book.desc.set.isbn'">
book.desc.set.isbn
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.isbn')} ?: 'book.desc.example.isbn'">
book.desc.example.isbn
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('isbn')}"
th:errors="*{isbn}">Invalid ISBN code</div>
</div>
<div class="form-group bookform-section">
<label for="BookYear"
th:text="${#messages.msgOrNull('book.year')} ?: 'book.year'">
book.year
</label>
<input class="form-control" type="text" th:field="*{year}" placeholder="Book publication year (YYYY)"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.year')} ?: 'book.desc.set.year'">
book.desc.set.year
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.year')} ?: 'book.desc.example.year'">
book.desc.example.year
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('year')}"
th:errors="*{year}">Invalid year value</div>
</div>
<div class="form-group bookform-section">
<label for="BookPrice"
th:text="${#messages.msgOrNull('book.price')} ?: 'book.price'">
book.price
</label>
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"
th:text="${#messages.msgOrNull('page.symbols.currency')} ?: 'page.symbols.currency'">
page.symbols.currency
</div>
</div>
<input class="form-control" type="text" th:field="*{price}" placeholder="0.00"/>
</div>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.price')} ?: 'book.desc.set.price'">
book.desc.set.price
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.price')} ?: 'book.desc.example.price'">
book.desc.example.price
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('price')}"
th:errors="*{price}">Invalid price value</div>
</div>
<div class="form-group bookform-section">
<label for="BookCategory"
th:text="${#messages.msgOrNull('book.category')} ?: 'book.category'">
book.category
</label>
<select class="form-control" th:field="*{category}">
<option
th:each="category : ${categories}"
th:value="${category.id}"
th:text="${category.name}"
>(obj) category.name</option>
<option value=""
th:text="${#messages.msgOrNull('book.null.category')} ?: 'book.null.category'">
book.null.category
</option>
</select>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.category')} ?: 'book.desc.set.category'">
book.desc.set.category
</small>
</div>
<button class="btn btn-primary" type="submit"
th:text="${#messages.msgOrNull('button.book.add')} ?: 'button.book.add'">
button.book.add
</button>
</form>
<br>
<a class="btn btn-success" th:href="@{../__${listpage}__}"
th:text="${#messages.msgOrNull('button.page.list.return')} ?: 'button.page.list.return'">
button.page.list.return
</a>
<a class="btn btn-success" th:href="@{../__${listpage}__}"
th:text="${#messages.msgOrNull('button.page.list.return')} ?: 'button.page.list.return'">
button.page.list.return
</a>
</div>
<th:block th:replace="fragments/loginout :: loginout"/>
</div>
</body>
</html>

+ 19
- 223
bookstore/src/main/resources/templates/bookedit.html View File

@ -12,237 +12,33 @@
</head>
<body>
<div class="col-md-4 mb-3">
<div style="display:flex;">
<div class="col-md-4 mb-3">
<h1 th:text="${#messages.msgOrNull('page.title.webform.edit')} ?: 'page.title.webform.edit'">
page.title.webform.edit
</h1>
<h1 th:text="${#messages.msgOrNull('page.title.webform.edit')} ?: 'page.title.webform.edit'">
page.title.webform.edit
</h1>
<form th:object="${book}" action="#" th:action="@{{hash_id}(hash_id=${book.bookHash.hashId})}" method="post">
<form th:object="${book}" action="#" th:action="@{{hash_id}(hash_id=${book.bookHash.hashId})}" method="post">
<div class="bookform-section">
<div>
<h3 th:text="${#messages.msgOrNull('book.author')} ?: 'book.author'">
book.author
</h3>
</div>
<div class="form-group row">
<div class="col">
<label for="BookAuthor"
th:text="${#messages.msgOrNull('book.author.firstName')} ?: 'book.author.firstName'">
book.author.firstName
</label>
<input class="form-control" type="text" th:field="*{author.firstName}" placeholder="Book author first name"/>
<th:block th:replace="fragments/bookfields :: bookfields"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.author.firstname')} ?: 'book.desc.set.author.firstname'">
book.desc.set.author.firstname
</small>
<button class="btn btn-primary" type="submit"
th:text="${#messages.msgOrNull('button.book.edit')} ?: 'button.book.edit'">
button.book.edit
</button>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext
</small>
</form>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.author.firstname')} ?: 'book.desc.example.author.firstname'">
book.desc.example.author.firstname
</small>
<br>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author.firstName')}"
th:errors="*{author.firstName}">
Invalid author first name value
</div>
</div>
<div class="col">
<label for="BookAuthor"
th:text="${#messages.msgOrNull('book.author.lastName')} ?: 'book.author.lastName'">
book.author.lastName
</label>
<input class="form-control" type="text" th:field="*{author.lastName}" placeholder="Book author last name"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.author.lastname')} ?: 'book.desc.set.author.lastname'">
book.desc.set.author.lastname
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.author.lastname')} ?: 'book.desc.example.author.lastname'">
book.desc.example.author.lastname
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author.lastName')}"
th:errors="*{author.lastName}">
Invalid author last name value
</div>
</div>
</div>
</div>
<div class="form-group bookform-section">
<label for="BookName"
th:text="${#messages.msgOrNull('book.title')} ?: 'book.title'">
book.title
</label>
<input class="form-control" type="text" th:field="*{title}" placeholder="Book title"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.title')} ?: 'book.desc.set.title'">
book.desc.set.title
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.title')} ?: 'book.desc.example.title'">
book.desc.example.title
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('title')}"
th:errors="*{title}">
Invalid title
</div>
</div>
<div class="form-group bookform-section">
<label for="BookISBN"
th:text="${#messages.msgOrNull('book.isbn')} ?: 'book.isbn'">
book.isbn
</label>
<input class="form-control" type="text" th:field="*{isbn}" placeholder="Book ISBN code"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.isbn')} ?: 'book.desc.set.isbn'">
book.desc.set.isbn
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.isbn')} ?: 'book.desc.example.isbn'">
book.desc.example.isbn
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('isbn')}"
th:errors="*{isbn}">
Invalid ISBN code value
</div>
</div>
<div class="form-group bookform-section">
<label for="BookYear"
th:text="${#messages.msgOrNull('book.year')} ?: 'book.year'">
book.year
</label>
<input class="form-control" type="text" th:field="*{year}" placeholder="Book publication year (YYYY)"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.year')} ?: 'book.desc.set.year'">
book.desc.set.year
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.year')} ?: 'book.desc.example.year'">
book.desc.example.year
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('year')}"
th:errors="*{year}">
Invalid year value
</div>
</div>
<div class="form-group bookform-section">
<label for="fname"
th:text="${#messages.msgOrNull('book.price')} ?: 'book.price'">
book.price
</label>
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"
th:text="${#messages.msgOrNull('page.symbols.currency')} ?: 'page.symbols.currency'">
page.symbols.currency
</div>
</div>
<input class="form-control" type="text" th:field="*{price}" placeholder="Book price"/>
</div>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.price')} ?: 'book.desc.set.price'">
book.desc.set.price
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.price')} ?: 'book.desc.example.price'">
book.desc.example.price
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('price')}"
th:errors="*{price}">
Invalid price value
</div>
</div>
<div class="form-group bookform-section">
<label for="BookCategory"
th:text="${#messages.msgOrNull('book.category')} ?: 'book.category'">
book.category
</label>
<select class="form-control" th:field="*{category}" th:selected="*{category}">
<option
th:each="category : ${categories}"
th:value="${category.id}"
th:text="${category.name}"
>(obj) category.name</option>
<option value=""
th:text="${#messages.msgOrNull('book.null.category')} ?: 'book.null.category'">
book.null.category
</option>
</select>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.category')} ?: 'book.desc.set.category'">
book.desc.set.category
</small>
</div>
<button class="btn btn-primary" type="submit"
th:text="${#messages.msgOrNull('button.book.edit')} ?: 'button.book.edit'">
button.book.edit
</button>
</form>
<br>
<a class="btn btn-success" th:href="@{/__${listpage}__}"
th:text="${#messages.msgOrNull('button.page.list.return')} ?: 'button.page.list.return'">
button.page.list.return
</a>
<a class="btn btn-success" th:href="@{/__${listpage}__}"
th:text="${#messages.msgOrNull('button.page.list.return')} ?: 'button.page.list.return'">
button.page.list.return
</a>
</div>
<th:block th:replace="fragments/loginout :: loginout"/>
</div>
</body>
</html>

+ 41
- 11
bookstore/src/main/resources/templates/booklist.html View File

@ -1,5 +1,8 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
>
<!--/*
@ -47,16 +50,24 @@ Idea of the following syntax used in this and other HTML document:
<body>
<div>
<h1 th:text="${#messages.msgOrNull('page.title.webform.list')} ?: 'page.title.webform.list'">
page.title.webform.list
</h1>
<div id="bookstore-topform-base">
<div>
<h1
th:text="${#messages.msgOrNull('page.title.webform.list')} ?: 'page.title.webform.list'">
page.title.webform.list
</h1>
</div>
<th:block th:replace="fragments/loginout :: loginout"/>
</div>
<!--/*
TODO: Use proper object notation here?
For example syntax, see
https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#order-details
*/-->
<table class="table table-striped">
<table class="table table-striped" sec:authorize="isAuthenticated()">
<tr>
<th th:text="${#messages.msgOrNull('book.author')} ?: 'book.author'">book.author</th>
@ -65,9 +76,17 @@ Idea of the following syntax used in this and other HTML document:
<th th:text="${#messages.msgOrNull('book.year')} ?: 'book.year'">book.year</th>
<th th:text="${#messages.msgOrNull('book.category')} ?: 'book.category'">book.category</th>
<th th:text="${#messages.msgOrNull('book.price')} ?: 'book.price'">book.price</th>
<th th:text="${#messages.msgOrNull('page.text.list.actions')} ?: 'page.text.list.actions'">
<th
th:text="${#messages.msgOrNull('page.text.list.actions')} ?: 'page.text.list.actions'"
th:if="${#authorization.expression('hasAuthority(''MARKETING'') or hasAuthority(''HELPDESK'')')}"
>
page.text.list.actions
</th>
<th th:unless="${#authorization.expression('hasAuthority(''MARKETING'') or hasAuthority(''HELPDESK'')')}">
</th>
<th></th>
<th th:text="${#messages.msgOrNull('book.json')} ?: 'book.json'">book.json</th>
</tr>
@ -127,8 +146,8 @@ Idea of the following syntax used in this and other HTML document:
0.00 page.symbols.currency
</td>
<td>
<a class="btn btn-danger"
<td sec:authorize="hasAuthority('ADMIN')">
<a class="btn btn-danger"
th:attr="onclick='javascript:return confirm(\'' + 'Delete book: ' + ${book.title} + '?' + '\');'"
th:href="@{__${deletepage}__/{hash_id}(hash_id=${book.bookHash.hashId})}"
th:text="${#messages.msgOrNull('page.text.list.delete')} ?: 'page.text.list.delete'">
@ -137,13 +156,16 @@ Idea of the following syntax used in this and other HTML document:
</td>
<td>
<a class="btn btn-warning"
<a class="btn btn-warning" sec:authorize="hasAuthority('MARKETING') or hasAuthority('HELPDESK')"
th:href="@{__${editpage}__/{hash_id}(hash_id=${book.bookHash.hashId})}"
th:text="${#messages.msgOrNull('page.text.list.edit')} ?: 'page.text.list.edit'">
page.text.list.edit
</a>
</td>
<td th:unless="${#authorization.expression('hasAuthority(''ADMIN'')')}">
</td>
<td>
<a class="btn btn-info"
th:href="@{__${restpage}__/book/{hash_id}(hash_id=${book.bookHash.hashId})}"
@ -156,7 +178,7 @@ Idea of the following syntax used in this and other HTML document:
<tr>
<td>
<a class="btn btn-success" th:href="@{__${addpage}__}"
<a class="btn btn-success" th:href="@{__${addpage}__}" sec:authorize="hasAuthority('MARKETING')"
th:text="${#messages.msgOrNull('button.book.add')} ?: 'button.book.add'">
button.book.add
</a>
@ -178,7 +200,7 @@ Idea of the following syntax used in this and other HTML document:
<tr style="background-color: #FFF !important;">
<td>
<a class="btn btn-info" th:href="@{__${apirefpage}__}"
<a class="btn btn-info" th:href="@{__${apirefpage}__}" sec:authorize="hasAuthority('ADMIN')"
th:text="${#messages.msgOrNull('button.page.apiref')} ?: 'button.page.apiref'">
button.page.apiref
</a>
@ -194,6 +216,14 @@ Idea of the following syntax used in this and other HTML document:
</tr>
</table>
<div sec:authorize="isAnonymous()">
<p th:text="${#messages.msgOrNull('page.text.list.anon.info')} ?: 'page.text.list.anon.info'">
page.text.list.anon.info
</p>
<img src="http://dreamicus.com/data/abyss/abyss-08.jpg" style="max-width: 600px; height: auto;" alt="abyss">
</div>
</div>
</body>
</html>

+ 204
- 0
bookstore/src/main/resources/templates/fragments/bookfields.html View File

@ -0,0 +1,204 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<body>
<th:block th:fragment="bookfields">
<div class="bookform-section">
<div>
<h3 th:text="${#messages.msgOrNull('book.author')} ?: 'book.author'">
book.author
</h3>
</div>
<div class="form-group row">
<div class="col">
<label for="BookAuthorFirstName"
th:text="${#messages.msgOrNull('book.author.firstName')} ?: 'book.author.firstName'">
book.author.firstName
</label>
<input class="form-control" type="text" th:field="*{author.firstName}" placeholder="Book author first name"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.author.firstname')} ?: 'book.desc.set.author.firstname'">
book.desc.set.author.firstname
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.author.firstname')} ?: 'book.desc.example.author.firstname'">
book.desc.example.author.firstname
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author.firstName')}"
th:errors="*{author.firstName}">Invalid author first name value</div>
</div>
<div class="col">
<label for="BookAuthorLastName"
th:text="${#messages.msgOrNull('book.author.lastName')} ?: 'book.author.lastName'">
book.author.lastName
</label>
<input class="form-control" type="text" th:field="*{author.lastName}" placeholder="Book author last name"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.author.lastname')} ?: 'book.desc.set.author.lastname'">
book.desc.set.author.lastname
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.author.lastname')} ?: 'book.desc.example.author.lastname'">
book.desc.example.author.lastname
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author.lastName')}"
th:errors="*{author.lastName}">Invalid author last name value</div>
</div>
</div>
</div>
<div class="form-group bookform-section">
<label for="BookTitle"
th:text="${#messages.msgOrNull('book.title')} ?: 'book.title'">
book.title
</label>
<input class="form-control" type="text" th:field="*{title}" placeholder="Book title"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.title')} ?: 'book.desc.set.title'">
book.desc.set.title
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.title')} ?: 'book.desc.example.title'">
book.desc.example.title
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('title')}"
th:errors="*{title}">Invalid title value</div>
</div>
<div class="form-group bookform-section">
<label for="BookISBN"
th:text="${#messages.msgOrNull('book.isbn')} ?: 'book.isbn'">
book.isbn
</label>
<input class="form-control" type="text" th:field="*{isbn}" placeholder="Book ISBN code"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.isbn')} ?: 'book.desc.set.isbn'">
book.desc.set.isbn
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.isbn')} ?: 'book.desc.example.isbn'">
book.desc.example.isbn
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('isbn')}"
th:errors="*{isbn}">Invalid ISBN code</div>
</div>
<div class="form-group bookform-section">
<label for="BookYear"
th:text="${#messages.msgOrNull('book.year')} ?: 'book.year'">
book.year
</label>
<input class="form-control" type="text" th:field="*{year}" placeholder="Book publication year (YYYY)"/>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.year')} ?: 'book.desc.set.year'">
book.desc.set.year
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.year')} ?: 'book.desc.example.year'">
book.desc.example.year
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('year')}"
th:errors="*{year}">Invalid year value</div>
</div>
<div class="form-group bookform-section" th:if="${#authorization.expression('hasAuthority(''MARKETING'')')}">
<label for="BookPrice"
th:text="${#messages.msgOrNull('book.price')} ?: 'book.price'">
book.price
</label>
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"
th:text="${#messages.msgOrNull('page.symbols.currency')} ?: 'page.symbols.currency'">
page.symbols.currency
</div>
</div>
<input class="form-control" type="text" th:field="*{price}" placeholder="0.00"/>
</div>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.price')} ?: 'book.desc.set.price'">
book.desc.set.price
</small>
<small style="display: inline-block;" class="form-text text-info"
th:text="(${#messages.msgOrNull('book.desc.example.headertext')} ?: 'book.desc.example.headertext') + ': '">
book.desc.example.headertext:
</small>
<small style="display: inline-block;" class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.example.price')} ?: 'book.desc.example.price'">
book.desc.example.price
</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('price')}"
th:errors="*{price}">Invalid price value</div>
</div>
<div class="form-group bookform-section">
<label for="BookCategory"
th:text="${#messages.msgOrNull('book.category')} ?: 'book.category'">
book.category
</label>
<select class="form-control" th:field="*{category}">
<option
th:each="category : ${categories}"
th:value="${category.id}"
th:text="${category.name}"
>(obj) category.name</option>
<option value=""
th:text="${#messages.msgOrNull('book.null.category')} ?: 'book.null.category'">
book.null.category
</option>
</select>
<small class="form-text text-muted"
th:text="${#messages.msgOrNull('book.desc.set.category')} ?: 'book.desc.set.category'">
book.desc.set.category
</small>
</div>
</th:block>
</body>
</html>

+ 45
- 0
bookstore/src/main/resources/templates/fragments/devusers.html View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="devusers">
<style>
#devtable th, td {
padding: 5px;
}
</style>
<div style="position: absolute; margin-right: 1em;" class="mt-5">
<p>Login examples (development only):</p>
<table id="devtable" border="1">
<tbody>
<tr>
<th>User name</th>
<th>Password</th>
<th>Permissions</th>
</tr>
<tr>
<td>admin</td>
<td>admin</td>
<td>All (delete books, view REST API help page, etc.)</td>
</tr>
<tr>
<td>helpdesk</td>
<td>helpdesk</td>
<td>Book edits (no price edits)</td>
</tr>
<tr>
<td>salesmanager</td>
<td>salesmanager</td>
<td>Book edits and additions</td>
</tr>
<tr>
<td>user</td>
<td>user</td>
<td>View books</td>
</tr>
</tbody>
</table>
</div>
</th:block>
</body>
</html>

+ 44
- 0
bookstore/src/main/resources/templates/fragments/loginout.html View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<body>
<th:block th:fragment="loginout">
<div id="bookstore-loginout">
<div sec:authorize="isAuthenticated()">
<div>
<form th:action="@{__${logoutpage}__}" method="post">
<button type="submit" class="btn btn-info"
th:href="@{__${logoutpage}__}"
th:text="${#messages.msgOrNull('button.page.logout')} ?: 'button.page.logout'">
button.page.logout
</button>
</form>
</div>
<p class="mt-2"
th:text="${#httpServletRequest.remoteUser} ? (${#messages.msgOrNull('page.text.list.authenticated')} ?: 'page.text.list.authenticated') + ' ' + ${#httpServletRequest.remoteUser} : 'httpServletRequest.remoteUser'">
page.text.list.authenticated httpServletRequest.remoteUser
</p>
</div>
<div sec:authorize="isAnonymous()">
<form th:action="@{__${loginpage}__}" method="post">
<input type="text" id="username" name="username" placeholder="User name">
<input type="password" id="password" name="password" placeholder="Password">
<div class="mt-2">
<button type="submit" class="btn btn-info"
name="login-submit" id="login-submit"
th:text="${#messages.msgOrNull('button.page.login')} ?: 'button.page.login'">
button.page.login
</button>
</div>
</form>
<th:block th:replace="fragments/devusers :: devusers"/>
</div>
</div>
</th:block>
</body>
</html>

Loading…
Cancel
Save