Skip to content

Commit

Permalink
Merge pull request #31 from saneci/feature/sorting_books_by_year
Browse files Browse the repository at this point in the history
Add sorting books by year
  • Loading branch information
saneci authored Feb 6, 2024
2 parents a22d079 + c6d48ed commit b916c08
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
Expand Down Expand Up @@ -72,9 +71,10 @@ public String addNewBook(@ModelAttribute("book") @Valid Book book, BindingResult

@GetMapping
public String getAllBooks(@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "10") int size, Model model) {
@RequestParam(value = "size", defaultValue = "10") int size,
@RequestParam(value = "sort_by_year", defaultValue = "false") boolean sortByYear, Model model) {
log.debug("getAllBooks: start processing");
Page<Book> bookPage = bookService.findAll(PageRequest.of(page, size));
Page<Book> bookPage = bookService.findAll(page, size, sortByYear);

if (bookPage.getTotalPages() > 1) {
// add attributes for pagination
Expand All @@ -87,6 +87,7 @@ public String getAllBooks(@RequestParam(value = "page", defaultValue = "0") int

model.addAttribute("bookList", bookPage.getContent());
model.addAttribute("size", size);
model.addAttribute("sortByYear", sortByYear);
log.debug("getAllBooks: finish processing");

return BOOK_LIST_VIEW;
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/ru/saneci/booklibrary/service/BookService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.saneci.booklibrary.domain.Book;
Expand All @@ -25,8 +27,10 @@ public BookService(BookRepository bookRepository, PersonRepository personReposit
this.personRepository = personRepository;
}

public Page<Book> findAll(Pageable pageable) {
return bookRepository.findAll(pageable);
public Page<Book> findAll(int page, int size, boolean sortByYear) {
return sortByYear
? bookRepository.findAll(PageRequest.of(page, size, Sort.by(Direction.ASC, "publishYear")))
: bookRepository.findAll(PageRequest.of(page, size, Sort.by(Direction.DESC, "publishYear")));
}

public Optional<Book> findById(Long id) {
Expand Down
25 changes: 21 additions & 4 deletions src/main/webapp/WEB-INF/views/books/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,34 @@
</ol>
</section>
<section id="buttonBlock" class="mb-3 gap-2 d-lg-flex justify-content-md-between">
<div th:replace="~{fragments/pagination :: itemsPerPage(@{/books}, ${currentPage}, ${size})}"></div>
<div th:replace="~{fragments/pagination :: itemsPerPage(@{/books})}"></div>
<a class="btn btn-outline-success" role="button" href="/books/new">Добавить новую книгу</a>
</section>
<section id="bookList" class="table-responsive">
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
<symbol th:replace="~{fragments/svg :: #sortAsc}"></symbol>
<symbol th:replace="~{fragments/svg :: #sortDesc}"></symbol>
</svg>
<table class="table table-striped table-hover table-bordered">
<thead class="text-center">
<tr>
<th scope="col">Название</th>
<th scope="col">Автор</th>
<th scope="col">Год публикации</th>
<th scope="col" class="d-flex gap-2 justify-content-center">
<a class="icon-link link-dark icon-link-hover icon-link-hover-down" th:if="${sortByYear}"
th:href="@{/books(page=${currentPage}, size=${size}, sort_by_year=false)}">
<svg class="bi" width="16" height="16">
<use xlink:href="#sortDesc"></use>
</svg>
</a>
<a class="icon-link link-dark icon-link-hover icon-link-hover-down" th:if="${!sortByYear}"
th:href="@{/books(page=${currentPage}, size=${size}, sort_by_year=true)}">
<svg class="bi" width="16" height="16">
<use xlink:href="#sortAsc"></use>
</svg>
</a>
<span>Год публикации</span>
</th>
</tr>
</thead>
<tbody>
Expand All @@ -32,8 +50,7 @@
</tbody>
</table>
<div th:if="${lastPage > 0}">
<nav th:replace="~{fragments/pagination :: tableNavigation(@{/books}, ${previousPage}, ${currentPage},
${nextPage}, ${lastPage}, ${pageNumbers}, ${size})}"></nav>
<nav th:replace="~{fragments/pagination :: tableNavigation(@{/books})}"></nav>
</div>
</section>
</body>
Expand Down
21 changes: 11 additions & 10 deletions src/main/webapp/WEB-INF/views/fragments/pagination.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,60 @@
<title>Pagination</title>
</head>
<body>
<div id="itemsPerPageSelection" th:fragment="itemsPerPage(path, currentPage, size)">
<div id="itemsPerPageSelection" th:fragment="itemsPerPage(path)">
<ul class="pagination mb-0">
<li class="page-item">
<span class="page-link link-secondary disabled bg-white">Строк в таблице</span>
</li>
<li class="page-item">
<a class="page-link link-secondary" th:classappend="${size == 5} ? disabled"
th:href="@{{url}(url=${path}, page=${currentPage}, size=5)}">5</a>
th:href="@{{url}(url=${path}, page=${currentPage}, size=5, sort_by_year=${sortByYear})}">5</a>
</li>
<li class="page-item">
<a class="page-link link-secondary" th:classappend="${size == 10} ? disabled"
th:href="@{{url}(url=${path}, page=${currentPage}, size=10)}">10</a>
th:href="@{{url}(url=${path}, page=${currentPage}, size=10, sort_by_year=${sortByYear})}">10</a>
</li>
<li class="page-item">
<a class="page-link link-secondary" th:classappend="${size == 20} ? disabled"
th:href="@{{url}(url=${path}, page=${currentPage}, size=20)}">20</a>
th:href="@{{url}(url=${path}, page=${currentPage}, size=20, sort_by_year=${sortByYear})}">20</a>
</li>
</ul>
</div>
<nav id="tableNavigation"
th:fragment="tableNavigation(path, previousPage, currentPage, nextPage, lastPage, pageNumbers, size)">
th:fragment="tableNavigation(path)">
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
<symbol th:replace="~{fragments/svg :: #arrowLeftShort}"></symbol>
<symbol th:replace="~{fragments/svg :: #arrowRightShort}"></symbol>
</svg>
<ul class="pagination justify-content-center">
<li id="firstPage" class="page-item">
<a class="page-link link-secondary" th:classappend="${currentPage == 0} ? 'disabled bg-white'"
th:href="@{{url}(url=${path}, page=0, size=${size})}">
th:href="@{{url}(url=${path}, page=0, size=${size}, sort_by_year=${sortByYear})}">
<svg class="bi" width="16" height="16">
<use xlink:href="#arrowLeftShort"></use>
</svg>
</a>
</li>
<li id="previousPage" class="page-item">
<a class="page-link link-secondary" th:classappend="${currentPage == previousPage} ? 'disabled bg-white'"
th:href="@{{url}(url=${path}, page=${previousPage}, size=${size})}">
th:href="@{{url}(url=${path}, page=${previousPage}, size=${size}, sort_by_year=${sortByYear})}">
<span aria-hidden="true">&#171;</span>
</a>
</li>
<li class="page-item" th:attr="id='pageNumber' + ${number}" th:each="number : ${pageNumbers}">
<a class="page-link link-secondary" th:classappend="${currentPage == number} ? disabled"
th:href="@{{url}(url=${path}, page=${number}, size=${size})}" th:text="${number} + 1">1</a>
th:href="@{{url}(url=${path}, page=${number}, size=${size}, sort_by_year=${sortByYear})}"
th:text="${number} + 1">1</a>
</li>
<li id="nextPage" class="page-item">
<a class="page-link link-secondary" th:classappend="${currentPage == nextPage} ? 'disabled bg-white'"
th:href="@{{url}(url=${path}, page=${nextPage}, size=${size})}">
th:href="@{{url}(url=${path}, page=${nextPage}, size=${size}, sort_by_year=${sortByYear})}">
<span aria-hidden="true">&#187;</span>
</a>
</li>
<li id="lastPage" class="page-item">
<a class="page-link link-secondary" th:classappend="${currentPage == lastPage} ? 'disabled bg-white'"
th:href="@{{url}(url=${path}, page=${lastPage}, size=${size})}">
th:href="@{{url}(url=${path}, page=${lastPage}, size=${size}, sort_by_year=${sortByYear})}">
<svg class="bi" width="16" height="16">
<use xlink:href="#arrowRightShort"></use>
</svg>
Expand Down
6 changes: 6 additions & 0 deletions src/main/webapp/WEB-INF/views/fragments/svg.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
<symbol id="arrowRightShort" viewBox="0 0 16 16">
<path d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8"/>
</symbol>
<symbol id="sortAsc" viewBox="0 0 16 16">
<path d="M3.5 3.5a.5.5 0 0 0-1 0v8.793l-1.146-1.147a.5.5 0 0 0-.708.708l2 1.999.007.007a.497.497 0 0 0 .7-.006l2-2a.5.5 0 0 0-.707-.708L3.5 12.293zm4 .5a.5.5 0 0 1 0-1h1a.5.5 0 0 1 0 1zm0 3a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1zm0 3a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1zM7 12.5a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 0-1h-7a.5.5 0 0 0-.5.5"/>
</symbol>
<symbol id="sortDesc" viewBox="0 0 16 16">
<path d="M3.5 2.5a.5.5 0 0 0-1 0v8.793l-1.146-1.147a.5.5 0 0 0-.708.708l2 1.999.007.007a.497.497 0 0 0 .7-.006l2-2a.5.5 0 0 0-.707-.708L3.5 11.293zm3.5 1a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5M7.5 6a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1z"/>
</symbol>
</svg>
</body>
</html>
4 changes: 4 additions & 0 deletions src/main/webapp/resources/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@
width: 1rem;
height: 1rem;
}

.icon-link-hover-down {
--bs-icon-link-transform: translate3d(0, .125rem, 0);
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,34 @@ void whenGetAllBooksWithPageAndSizeParams_thenReturnCorrespondingPageAndPaginati
);
}

@Test
@Sql(scripts = "/sql/book_controller/create-three-books.sql")
void whenGetAllBooksWithSortByYearParamEqualsToTrue_thenReturnRowsSortedByYearInAscendingOrder() throws Exception {
mockMvc.perform(get("/books").queryParam("sort_by_year", "true"))
.andExpect(status().isOk())
.andExpectAll(
xpath("//*[@id='bookList']/table/thead/tr/th[3]/a/@href").string("/books?page=&size=10&sort_by_year=false"),
xpath("//*[@id='bookList']/table/thead/tr/th[3]/a/svg/use/@href").string("#sortDesc"),
xpath("//*[@id='bookList']/table/tbody/tr[1]/td[3]").number(1234d),
xpath("//*[@id='bookList']/table/tbody/tr[2]/td[3]").number(1235d),
xpath("//*[@id='bookList']/table/tbody/tr[3]/td[3]").number(1236d)
);
}

@Test
@Sql(scripts = "/sql/book_controller/create-three-books.sql")
void whenGetAllBooksWithSortByYearParamEqualsToFalse_thenReturnRowsSortedByYearInDescendingOrder() throws Exception {
mockMvc.perform(get("/books").queryParam("sort_by_year", "false"))
.andExpect(status().isOk())
.andExpectAll(
xpath("//*[@id='bookList']/table/thead/tr/th[3]/a/@href").string("/books?page=&size=10&sort_by_year=true"),
xpath("//*[@id='bookList']/table/thead/tr/th[3]/a/svg/use/@href").string("#sortAsc"),
xpath("//*[@id='bookList']/table/tbody/tr[1]/td[3]").number(1236d),
xpath("//*[@id='bookList']/table/tbody/tr[2]/td[3]").number(1235d),
xpath("//*[@id='bookList']/table/tbody/tr[3]/td[3]").number(1234d)
);
}

@Test
void whenGetBookById_thenBookItemViewShouldContainRightLayout() throws Exception {
mockMvc.perform(get("/books/{id}", 1))
Expand Down
4 changes: 2 additions & 2 deletions src/test/resources/sql/book_controller/create-three-books.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
truncate book;

insert into book (id, title, author, publish_year) values (1, 'Test Book 1', 'Test Author', 1234);
insert into book (id, title, author, publish_year) values (2, 'Test Book 2', 'Test Author', 1234);
insert into book (id, title, author, publish_year) values (3, 'Test Book 3', 'Test Author', 1234);
insert into book (id, title, author, publish_year) values (2, 'Test Book 2', 'Test Author', 1236);
insert into book (id, title, author, publish_year) values (3, 'Test Book 3', 'Test Author', 1235);

0 comments on commit b916c08

Please sign in to comment.