Статьи

Реализовать нумерацию страниц с помощью Spring Data и Thymeleaf

Twitter Bootstrap имеет очень приятный пользовательский интерфейс для разбивки на страницы , и здесь я покажу вам, как реализовать его с помощью функции нумерации веб-страниц Spring Data и функций условной оценки Thymeleaf .

Стандартная нумерация страниц в Bootstrap

Простая нумерация страниц, вдохновленная Rdio, отлично подходит для приложений и результатов поиска. Большой блок трудно пропустить, он легко масштабируется и обеспечивает большие области щелчков.

фильм

Исходный исходный код для отображения нумерации страниц из документа Bootstrap очень прост:

01
02
03
04
05
06
07
08
09
10
11
<div class='pagination'>
  <ul>
    <li><a href='#'>Prev</a></li>
    <li><a href='#'>1</a></li>
    <li><a href='#'>2</a></li>
    <li><a href='#'>3</a></li>
    <li><a href='#'>4</a></li>
    <li><a href='#'>5</a></li>
    <li><a href='#'>Next</a></li>
  </ul>
</div>

Вы можете видеть, что это всего лишь макет кода, и чтобы он динамически отображал номер страницы с правильным URL-адресом гиперссылки, мне нужно сделать много изменений в моем существующем коде. Итак, давайте начнем снизу вверх, сначала изменим уровень домена, затем уровень обслуживания приложений, уровень представления. Наконец, конфигурация, чтобы склеить их.

Изменения уровня домена

Единственное изменение в доменном слое — в BlogPostRepository . Прежде, чем у него есть метод для получения списка опубликованных BlogPost отсортированных по publishedTime :

1
2
3
4
5
public interface BlogPostRepository extends MongoRepository<BlogPost, String>{
...
    List<BlogPost> findByPublishedIsTrueOrderByPublishedTimeDesc();
...
}

Теперь нам нужно получить список результатов, разбитых на страницы. На странице данных Spring мы вернем Page<BlogPost> вместо List<BlogPost> и Pageable параметр Pageable :

1
2
3
4
5
public interface BlogPostRepository extends MongoRepository<BlogPost, String>{
...
    Page<BlogPost> findByPublishedIsTrueOrderByPublishedTimeDesc(Pageable pageable);
...
}

Изменения уровня обслуживания приложений:

Смена уровня приложения также очень проста, просто используя новую функцию из BlogPostRepository :

Интерфейс BlogService

1
2
3
4
5
public interface BlogService {
...
    Page<BlogPost> getAllPublishedPosts(Pageable pageable);
...
}

Класс BlogServiceImpl

01
02
03
04
05
06
07
08
09
10
11
public class BlogServiceImpl implements BlogService {
...
   private final BlogPostRepository blogPostRepository;
...
    @Override
    public Page<BlogPost> getAllPublishedPosts(Pageable pageable) {
        Page<BlogPost> blogList = blogPostRepository.findByPublishedIsTrueOrderByPublishedTimeDesc(pageable);
        return blogList;
    }
...
}

Изменения в уровне презентации:

Интерфейс Spring Data Page имеет много приятных функций для получения номера текущей страницы, получения общего количества страниц и т. Д. Но по-прежнему не хватает способов, позволяющих мне отображать только частичный диапазон страниц с общей нумерацией страниц. Поэтому я создал класс адаптера, чтобы обернуть интерфейс страницы данных Sprng дополнительными функциями.

01
02
03
04
05
06
07
08
09
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
public class PageWrapper<T> {
    public static final int MAX_PAGE_ITEM_DISPLAY = 5;
    private Page<T> page;
    private List<PageItem> items;
    private int currentNumber;
    private String url;
 
    public String getUrl() {
        return url;
    }
 
    public void setUrl(String url) {
        this.url = url;
    }
 
    public PageWrapper(Page<T> page, String url){
        this.page = page;
        this.url = url;
        items = new ArrayList<PageItem>();
 
        currentNumber = page.getNumber() + 1; //start from 1 to match page.page
 
        int start, size;
        if (page.getTotalPages() <= MAX_PAGE_ITEM_DISPLAY){
            start = 1;
            size = page.getTotalPages();
        } else {
            if (currentNumber <= MAX_PAGE_ITEM_DISPLAY - MAX_PAGE_ITEM_DISPLAY/2){
                start = 1;
                size = MAX_PAGE_ITEM_DISPLAY;
            } else if (currentNumber >= page.getTotalPages() - MAX_PAGE_ITEM_DISPLAY/2){
                start = page.getTotalPages() - MAX_PAGE_ITEM_DISPLAY + 1;
                size = MAX_PAGE_ITEM_DISPLAY;
            } else {
                start = currentNumber - MAX_PAGE_ITEM_DISPLAY/2;
                size = MAX_PAGE_ITEM_DISPLAY;
            }
        }
 
        for (int i = 0; i<size; i++){
            items.add(new PageItem(start+i, (start+i)==currentNumber));
        }
    }
 
    public List<PageItem> getItems(){
        return items;
    }
 
    public int getNumber(){
        return currentNumber;
    }
 
    public List<T> getContent(){
        return page.getContent();
    }
 
    public int getSize(){
        return page.getSize();
    }
 
    public int getTotalPages(){
        return page.getTotalPages();
    }
 
    public boolean isFirstPage(){
        return page.isFirstPage();
    }
 
    public boolean isLastPage(){
        return page.isLastPage();
    }
 
    public boolean isHasPreviousPage(){
        return page.hasPreviousPage();
    }
 
    public boolean isHasNextPage(){
        return page.hasNextPage();
    }
 
    public class PageItem {
        private int number;
        private boolean current;
        public PageItem(int number, boolean current){
            this.number = number;
            this.current = current;
        }
 
        public int getNumber(){
            return this.number;
        }
 
        public boolean isCurrent(){
            return this.current;
        }
    }
}

С помощью этого PageWrapper мы можем обернуть Page<BlogPost> возвращенную из BlogService и поместить ее в модель пользовательского интерфейса SpringMVC. Смотрите код контроллера для страницы блога:

01
02
03
04
05
06
07
08
09
10
11
12
@Controller
public class BlogController
...
    @RequestMapping(value = '/blog', method = RequestMethod.GET)
    public String blog(Model uiModel, Pageable pageable) {
        PageWrapper<BlogPost> page = new PageWrapper<BlogPost>
            (blogService.getAllPublishedPosts(pageable), '/blog');
        uiModel.addAttribute('page', page);
        return 'blog';
    }
...
}

Pageable передается из PageableArgumentResolver , что я объясню позже. Еще одна хитрость заключается в том, что я также PageWrapper URL представления в PageWrapper , и его можно использовать для создания гиперссылок Thymeleaf на панели разбивки на страницы.

Так как мой PageWrapper очень универсален, я создал HTML-фрагмент для панели разбивки на страницы, так что я могу использовать его в любом месте на страницах моего приложения, когда потребуется разбивка на страницы. Этот фрагмент HTML использует Thymeleaf- th:if динамически переключаться между статическим текстом или гиперссылкой в ​​зависимости от того, отключена ссылка или нет. И он использует th:href для создания URL с правильным номером страницы и размером страницы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- Pagination Bar -->
<div th:fragment='paginationbar'>
  <div class='pagination pagination-centered'>
    <ul>
      <li th:class='${page.firstPage}? 'disabled' : '''>
        <span th:if='${page.firstPage}'>← First</span>
        <a th:if='${not page.firstPage}' th:href='@{${page.url}(page.page=1,page.size=${page.size})}'>← First</a>
      </li>
      <li th:class='${page.hasPreviousPage}? '' : 'disabled''>
        <span th:if='${not page.hasPreviousPage}'>«</span>
        <a th:if='${page.hasPreviousPage}' th:href='@{${page.url}(page.page=${page.number-1},page.size=${page.size})}' title='Go to previous page'>«</a>
      </li>
      <li th:each='item : ${page.items}' th:class='${item.current}? 'active' : '''>
        <span th:if='${item.current}' th:text='${item.number}'>1</span>
        <a th:if='${not item.current}' th:href='@{${page.url}(page.page=${item.number},page.size=${page.size})}'><span th:text='${item.number}'>1</span></a>
      </li>
      <li th:class='${page.hasNextPage}? '' : 'disabled''>
        <span th:if='${not page.hasNextPage}'>»</span>
        <a th:if='${page.hasNextPage}' th:href='@{${page.url}(page.page=${page.number+1},page.size=${page.size})}' title='Go to next page'>»</a>
      </li>
      <li th:class='${page.lastPage}? 'disabled' : '''>
        <span th:if='${page.lastPage}'>Last →</span>
        <a th:if='${not page.lastPage}' th:href='@{${page.url}(page.page=${page.totalPages},page.size=${page.size})}'>Last →</a>
      </li>
    </ul>
  </div>
</div>

Изменение конфигурации Spring

Последний шаг — собрать их всех вместе. К счастью, я провел некоторое исследование, прежде чем обновил свой код. Даг Хабер написал очень хороший пост Pagination с Spring MVC, Spring Data и Java Config . В своем блоге Дуг упомянул несколько ошибок, особенно параметр Pageable нуждается в некоторой магии конфигурации:

Чтобы Spring знал, как преобразовать параметр в объект Pageable, вам необходимо настроить HandlerMethodArgumentResolver. Spring Data предоставляет PageableArgumentResolver, но использует старый интерфейс ArgumentResolver вместо нового (Spring 3.1) интерфейса HandlerMethodArgumentResolver. Конфигурация XML может справиться с этим расхождением для нас, но, поскольку мы используем Java Config, нам нужно сделать это немного больше вручную. К счастью, это может быть легко решено, если вы знаете правильное магическое заклинание …
Даг Хабер

С помощью Дуга я добавил этот преобразователь аргументов в свой класс WebConfig :

01
02
03
04
05
06
07
08
09
10
11
12
13
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = 'com.jiwhiz.blog.web')
public class WebConfig extends WebMvcConfigurerAdapter {
...
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        PageableArgumentResolver resolver = new PageableArgumentResolver();
        resolver.setFallbackPagable(new PageRequest(1, 5));
        argumentResolvers.add(new ServletWebArgumentResolverAdapter(resolver));
    }
...
}

После всех этих изменений мой список блогов будет иметь панель нумерации страниц вверху и внизу страницы и всегда будет иметь не более 5 номеров страниц, с текущим номером посередине и отключенным. На панели разбиения на страницы также есть первая и предыдущая ссылки в начале, следующая и последняя ссылки в конце. Я также использовал его на своих страницах администратора, для списка пользователей и списка комментариев, и это работает очень хорошо.

Ссылка: Реализация нумерации страниц с помощью Spring Data и Thymeleaf от нашего партнера JCG Юаня Цзи в блоге Jiwhiz .