Search code examples
springjpathymeleafselected

Sorting\Ordering data using thymeleaf


I'm developing a virtual learning platform with courses. Currently, I have some courses and now I want to sort them for example by title in ascending or descending order. Now I'm not able to connect the thymeleaf with my sorting functions.

Courses Html Page code:

    <!-- Courses -->

    <div class="courses">
        <div class="container">
            <div class="select">
                <select id="orderSelect" class="form-control input-sm">
                    <option selected value="ratingAsc">Order by Rating Ascending</option>
                    <option value="ratingDesc">Order by Rating Descending</option>
                    <option value="titleAsc"> Order by Title Ascending</option>
                    <option value="titleDesc">Order by Title Descending</option>
                    <option value="totalVotesAsc">Order by Total Votes Ascending</option>
                    <option value="totalVotesDesc">Order by Total Votes Descending</option>
                    <option value="membersAsc">Order by Members Amount Ascending</option>
                    <option value="membersDesc">Order by Members Amount Descending</option>
                </select>
            </div>
            <div class="row courses_row">





                <!-- Course -->
                <div class="col-lg-4 course_col" th:each="course: ${allCourses}">
                    <div class="course">
                        <div class="course_image"><img src="../static/img/course_6.jpg" th:src="@{/img/course_6.jpg}"
                                                       alt=""></div>
                        <div class="course_body">
                            <div class="course_title">
                                <a href="course.html" th:href="@{'/courses/'+${course.id}}" th:text="${course.title}"
                                   th:alt="CourseTitle">
                                    Course title
                                </a>
                            </div>
                            <div class="course_info">
                                <ul>
                                    <li><a href="instructors.html" th:href="@{instructors}">Sarah Parker</a></li>
                                    <li><a href="#" th:alt="Topic">English</a></li>
                                </ul>
                            </div>
                            <div class="course_text">
                                <p th:text="${course.description}" th:alt="Description">Lorem ipsum dolor sit amet,
                                    consectetur adipiscing elit. Fusce enim nulla.</p>
                            </div>
                        </div>
                        <div class="course_footer d-flex flex-row align-items-center justify-content-start">
                            <div class="course_students"><i class="fa fa-user" aria-hidden="true"></i><span>10</span>
                            </div>
                            <div class="course_rating ml-auto"><i class="fa fa-star"
                                                                  aria-hidden="true"></i><span>4,5</span></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="row flex-row">
                <ul class="nav-pills">
                    <li class="col-lg-4 mt-auto"
                        th:each="i:${#numbers.sequence(0,allCourses.totalPages -1)}">
                        <a th:href="@{/courses(page=${i})}" th:text="${i+1}" class="nav-link"
                           th:classappend="${currentPage}==${i}?'active':''"></a>
                    </li>
                </ul>
            </div>
        </div>
    </div>

Here is the controller:

@Controller
public class CoursesController {
    private CourseService courseService;

    @Autowired
    public CoursesController(CourseService courseService) {
        this.courseService = courseService;
    }

    @GetMapping("courses")
    public String showCoursesPageSorted(Model model,@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "titleAsc") String sortBy){
        model.addAttribute("allCourses", courseService.findBy(page,sortBy));
        model.addAttribute("currentPage",page);
        model.addAttribute("currentSort",sortBy);
        return "courses";
    }

The only solution that I figured out so far was to create a new HTML page for every single filter but it doesn't look right..


Solution

  • I see you have planned for database pagination and sorting support. That's a solid approach for growing data.

    Repository

    First of all, you don't need separate repository methods (as you have shown in a comment) to sort courses by different fields. Instead, have one method that takes filter criteria (if any) and Pageable. Pageable has page (int), size(int), and sort(object) fields.

    Page<Course> findCourses(Pageable pageable); 
    

    Controller

    Pageable is manually created

    In the Controller method, take individual parameters: page, size, sort OR Pageable. In case you have a Pageable object, use the same to call the repository method. In case you have individual params from the front end, construct Pageable yourself:

    public String getCourses(Model model, @RequestParam(defaultValue = "0") int
        page, @RequestParam(defaultValue = "0") int size, 
        @RequestParam(defaultValue = "title") String sortBy){
        // Some code
        Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy).ascending());
        repository.findCourses(pageable);
        // Rest of the code
    }
    

    Pageable is made available in the Controller

    If you want to rely on Spring to get the Pageable object directly in the controller ensure these request parameters are passed - page, size, and sort (example: page=0&size=10&sort=rating,asc). The sort parameter may be passed multiple times to sort by multiple fields if needed.

    public String getCourses(Model model, Pageable pageable){
      // Some code
      repository.findCourses(pageable);
      // Rest of the code
    }
    

    Now, to show courses in a paginated manner and sorted by various fields you need to have only one HTML page and one single grid.

    Future Extension

    In the future, you may add search courses as well. In that case, you have to add search criteria field in the controller and the repository.