Pagination Thymeleaf 3.0 Spring MVC - spring-mvc

I would like to find a complete solution to Thymeleaf pagination using Spring PagingAndSortingRepository DAO. I got a partial solution working, but I can not get the final one. I think it would be interesting for everyone else as a code snipet, so I will ask for the whole thing. I could not find a solution on the web which is kind of strange for me (since I think it could be a quite common problem).
The final solution should behave like Google's pagination: with arrows on both sides only if it makes sense; with a maximun of 10 numbers (for example), and it should move from 1..10 --> 11..20 and so on, when you click the right arrow; and move to 5..15 when you click on 10. Just like google, you know. The size controls the number of items in each page, not the number of blocks or links in the pagination bar.
I have a DAO repository in Spring that extends PagingAndSortingRepository ...
package music.bolo.domain.repository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import music.bolo.domain.entity.Announcement;
#Repository public interface AnnouncementDao extends
PagingAndSortingRepository {
Announcement findFirstByOrderByDateDesc(); }
So my service can make a request and each page will get the totalPageNumbers (http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Page.html)...
private final static int PAGESIZE = 2;
.. .. #Autowired annotations...
public Page<Announcement> readAnnouncementPage (int pageNumber){
PageRequest request = new PageRequest(pageNumber-1, PAGESIZE, Sort.Direction.DESC, "date");
return announcementDao.findAll(request); }
My Controller uses the data to send all the information to Thymeleaf
#RequestMapping(value = "/viewannouncements", method = RequestMethod.GET)
ModelAndView viewAnnouncements(ModelAndView modelAndView, #RequestParam(name = "p", defaultValue = "1") int pageNumber) {
Page<Announcement> page = announcementService.readAnnouncementPage(pageNumber);
modelAndView.getModel().put("page2th", page);
modelAndView.setViewName("viewannouncements");
return modelAndView; }
My solution is partial, it shows all the pages all the time, no arrow control (actually useless) and without any other funcionality, but it is the most I could make it work without bugs.
<div class="tag-box tag-box-v7 text-justify">
<!-- <h2>Pagination Centered</h2>-->
<div class="text-center">
<ul class="pagination">
<li>«</li>
<!--<li>«</li>-->
<li th:each="i : ${#numbers.sequence( 1, page2th.TotalPages)}">
<a th:href="#{'/viewannouncements?p='}+${ i }" th:text="${ i }">1</a></li>
<!--<li>»</li>-->
<li>»</li>
</ul>
</div>
</div>

This is an example of pagination with SpringBoot and Thymeleaf templates, you can try it.
It is self-explanatory.
Following you can find link to GitHub repo -
https://github.com/karelp90/control_asp

<div class="tag-box tag-box-v7 text-justify">
<div class="text-center">
<ul class="pagination" th:with="elementsperpage=2, blocksize=10, pages=${page2th.Number}/${elementsperpage}, wholepages=${format.format(pages)},
whole=(${page2th.Number}/${blocksize})+1, wholex=${format.format(whole)}, startnlockpage=${wholepages}*${blocksize+1}, endblockpage=${wholepages}*${blocksize+1},
startpage=${wholex-1}*${blocksize}, endpage=(${wholex}*${blocksize})+1">
<li>
<a th:if="${startpage gt 0}" th:href="#{${'/viewannouncements?p='}+${startpage}}"><<</a>
<a th:if="${startpage eq 0}" href="javascript:void(0);"><<</a>
</li>
<li th:each="pageNo : ${#numbers.sequence(endpage-11, (endpage lt page2th.TotalPages)? endpage-2 : page2th.TotalPages-1)}"
th:class="${page2th.Number eq pageNo}? 'active' : ''">
<a th:if="${page2th.Number eq pageNo}" href="javascript:void(0);">
<span th:text="${pageNo + 1}"></span>
</a>
<a th:if="${not (page2th.Number eq pageNo)}" th:href="#{${'/viewannouncements?p='}+${pageNo+1}}">
<span th:text="${pageNo + 1}"></span>
</a>
</li>
<li>
<a th:if="${(endpage*elementsperpage) le (page2th.TotalElements)}" th:href="#{${'/viewannouncements?p='}+${endpage}}">>></a>
<a th:if="${(endpage*elementsperpage) le (page2th.TotalElements)}" href="javascript:void(0);"></a>
</li>
</ul>
</div>
</div>

Related

Calling an action with parameter .Net Core

I have a MyMessages model that I put data into when the user logs in. it includes all the data needed.
for every message that they have I do the following in view
for (int i = 0; i < Model.MyMessagesCount; i++)
{
<li class="list-group-item d-flex align-items-center mt-2">
<a>#Model.MessagesTitles[i]</a>
</li>
When the user clicks on each of the <a>, I want to show them that message in more details in a separate view where I pass MessageID to.
How can I achieve that? How can I have all the <a> call the same action but with different MessageID as a parameter? (Something like this /User/Usermessages?MessageID=20)
You can use anchor tag helper
<li>
<a asp-controller="user"
asp-action="messages"
asp-area=""
asp-route-messageId="#Model.MessagesTitles[i].MessageId">
#Model.MessagesTitles[i]
</a>
</li>
asp-route-{parameter}: the parameter there is the name of the parameter you define in your action.
You can read more on https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/built-in/anchor-tag-helper?view=aspnetcore-5.0
In the Index.cshtml page, you could directly set the a tag href attribute.
Code sample as below:
public IActionResult Index()
{
List<MyMessages> messages = new List<MyMessages>()
{
new MyMessages(){ MessageId=1001, MessageTitile="AA"},
new MyMessages(){MessageId =1002, MessageTitile="BB"},
new MyMessages(){MessageId=1003, MessageTitile="CC"}
};
return View(messages);
}
public IActionResult Details(int messageId)
{
return View();
}
Index.cshtml page:
#model List<SignalRChatSample.Models.MyMessages>
<ul>
#for (int i = 0; i < Model.Count(); i++)
{
<li class="list-group-item d-flex align-items-center mt-2">
#Model[i].MessageTitile
</li>
}
</ul>
Then, the result like this (The url: Https://localhost:5001/Home/Details?messageId=1002):
Besides, as David said, you could also send the parameters via the route.

Sending constant parameters to change a model field in dukescript

How can I send any parameter to change something in the model?
I tried with setters but failed, and when tried to do it from the view I got a strange behaviour.
This is as far as I'm now.
HTML:
Selected name: <span data-bind="text: selectedName"></span>
<ul data-bind="foreach: names">
<li>
<a data-bind="text: $data, click: $root.nameSelected" href="#"></a>
</li>
</ul>
<hr>
<a data-bind="text:'hi',click: $root.nameSelected('hi')" href="#"></a>
Java:
package dew.demo.namesmodel;
import net.java.html.json.Model;
import net.java.html.json.Property;
import net.java.html.json.Function;
#Model(className="Names", properties={
#Property(name = "selectedName", type=String.class),
#Property(name = "names", type=String.class, array = true)
})
class NamesModel {
#Function static void nameSelected(Names myModel, String data) {
myModel.setSelectedName(data);
}
static {
Names pageModel = new Names(
"---", "Jarda", "Pepa", "Honza", "Jirka", "Tomáš"
);
pageModel.applyBindings();
}
}
you can see the example running on this fiddle.
It is possible to workaround the problem by using with binding:
<div data-bind="with: 'hi'">
<a data-bind="text: $data,click: $root.nameSelected" href="#"></a>
</div>
as your updated DEW shows.
The example seems to fail only in DEW. I created a normal DukeScript project, where it works fine, also when running in a browser. So I assume this is simply a bug in DEW.

Issue with pagination in PagedList MVC

I am using MVC PagedList to handle pagination in my project. Everything works fine except that in my controller I have to initialize pageNumber parameter with "1" (Default page number). The problem is the blue activated button on page 1 that remains there no matter which page button link you click. Moreover this button is also missing the actionLink.
One last thing I am integrating my search with pagination as you can see in the controller code.
public PartialViewResult List_Items(string keyword, int? page)
{
var newlists = from s in db.TrMs
orderby s.S1
select s;
int pageNumber = (page ?? 1);
int pageSize = 10;
var data = newlists.Where(f => f.S1.ToString().Contains(keyword) && f.S100 == vt).ToPagedList(pageNumber, pageSize);
return PartialView(data);
}
Here is the generated Html for PagedList in my view
<div class="pagination-container">
<ul class="pagination">
<li class="active">
<a>1</a>
</li>
<li>
<a data-ajax="true" data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#searchResult" href="/TrM/Search_Items?keyword=&page=2">2</a>
</li>
<li>
<a data-ajax="true" data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#searchResult" href="/TrM/Search_Items?keyword=&page=3">3</a>
</li>
<li>
<a data-ajax="true" data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#searchResult" href="/TrM/Search_Items?keyword=&page=4">4</a>
</li>
<li class="PagedList-skipToNext">
<a data-ajax="true" data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#searchResult" href="/TrM/Search_Items?keyword=&page=2" rel="next">»</a>
</li>
</ul>
</div>
Here is how I am using the pagination helper.
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of #Model.PageCount
#Html.PagedListPager(Model, page => Url.Action("List_Items", "TrM", new {keyword="", page }), PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing(new AjaxOptions() { HttpMethod = "GET", UpdateTargetId = "searchResult" }))
Your paging controls are most likely outside of the searchResult container. When you click on a page you're replacing the table but not the paging controls. Move your paging controls inside of your searchResult container!
Hope this helps.

How can I elegantly preserve filter parameters when paginating with Thymeleaf?

On many of my pages, I have several options to sort or filter a long listing of results.
I'd like to have those selections preserved properly when someone pages through the data. For example:
Start looking at the list of all tickets. (/tickets)
Search for all tickets like "foo." (/tickets?query=foo)
Go to the next page. (/tickets?query=foo&page=2)
See the next page of tickets like "foo", not the 2nd page of "all tickets." (e.g. not /tickets?page=2)
In JSP, I came up with a solution like this:
<c:if test="${page.hasNext()}">
<spring:url value="" var="nextLink">
<c:forEach items="${param}" var="curParam">
<c:if test="${curParam.key != 'page'}">
<spring:param name="${curParam.key}" value="${curParam.value}"/>
</c:if>
</c:forEach>
<spring:param name="page" value="${page.nextPageable().pageNumber}"/>
</spring:url>
<li>
<a href="${nextLink}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</c:if>
That basically constructs a new URL pointing to the same path as the current page, but adds/replaces the "path" parameter to it's query parameters.
It's pretty ugly in JSP, but I don't see a way to really do this at all in Thymeleaf?
Or, perhaps there's some better way to do this using some Spring or Thymeleaf feature I haven't encountered yet?
My hope is to have a concise Thymeleaf fragment I can reuse everywhere I need pagination. So far, this is all I have, but it ends up missing missing the "query" parameter, etc. (I don't want to hard-code the query parameter here, because that would limit the reusability of this pagination code)
<li th:if="${page.hasNext()}">
<a href="pagination.html" th:href="#{''(page=${page.nextPageable().pageNumber})}" aria-label="next">
<span aria-hidden="true">»</span>
</a>
</li>
Maybe this will be usefull for someone using HTML and Spring with Thymeleaf.
In my case I use thymeleaf with spring. I have a DTO as a filter that is used in the controller to do the filtering.
public class Controller {
private final int BUTTONS_TO_SHOW = 5;
public String home(FilterDTO filterDTO #PageableDefault(size = 50) Pageable pageable, Model model){
List<YourEntity> rows = YourEntityService.findAll();
Pager pager = new Pager(rows.getTotalPages(), rows.getNumber(), BUTTONS_TO_SHOW);
model.addAttribute("rows", rows);
model.addAttribute("pager", pager);
model.addAttribute("filterDTO", filterDTO);
return "your-template";
}
}
and the paginator like this:
<nav style="display: inline-block">
<ul class="pagination">
<li th:class="page-item" th:classappend="${rows.number == 0} ? ''">
<a class="page-link" th:href="#{${#httpServletRequest.requestURI+'?'+filterDTO.toQueryString()}(page=0)}">«</a>
</li>
<li th:class="page-item" th:classappend="${rows.number == 0} ? ''">
<a class="page-link" th:href="#{${#httpServletRequest.requestURI+'?'+filterDTO.toQueryString()}(page=${rows.number - 1})}">←</a>
</li>
<li th:class="page-item" th:classappend="${rows.number == (page - 1)} ? 'active pointer-disabled'"
th:each="page : ${#numbers.sequence(pager.startPage, pager.endPage)}">
<a class="page-link" th:href="#{${#httpServletRequest.requestURI+'?'+filterDTO.toQueryString()}(page=${page - 1})}" th:if="${page != 0}"
th:text="${page}"></a>
</li>
<li th:class="page-item" th:classappend="${rows.number + 1 == rows.totalPages or pager.endPage == 0} ? ''">
<a class="page-link"
th:href="#{${#httpServletRequest.requestURI+'?'+filterDTO.toQueryString()}(page=${rows.number + 1})}">→</a>
</li>
<li th:class="page-item" th:classappend="${rows.number + 1 == rows.totalPages or pager.endPage == 0} ? ''">
<a class="page-link"
th:href="#{${#httpServletRequest.requestURI+'?'+filterDTO.toQueryString()}(page=${rows.totalPages - 1})}">»</a>
</li>
</ul>
</nav>
and in the FilterDTO class I implemented the method: toQueryString() that returns the query string with your applied filters.
Dandelion Datatables is project that can help you not reinvent the wheel.
http://dandelion.github.io/components/datatables/

Multiple 'foreach' loops in one knockout JS

I'll start by saying that I am working within the context of DotNetNuke7, which is essentially ASP.net based framework, and that i am fairly new to KO.
I am trying to have one ko viewmodel and have two foreach loops in it. Each loop renders an array which is part of the view model definition like so:
//We build two arrays: one for the users that are in the group
//and one for the users that are not in the group
var nonGroupMembers = $.map(initialData.NonGroupUsers, function (item) { return new Member(item); });
var groupMembers = $.map(initialData.GroupUsers, function (item) { return new Member(item); });
//The members we start with before we added new members
self.SearchTerm = ko.observable('');
self.CircleMembers = ko.observableArray(groupMembers);
self.NonCircleMembers = ko.observableArray(nonGroupMembers);
In the html context (or the asp user control) i placed the following code
<div id="socialDirectory" class="dnnForm dnnMemberDirectory">
<ul id="mdMemberList" class="mdMemberList dnnClear" style="display:none"
data-bind="foreach: { data: NonCircleMembers, afterRender: handleAfterRender },
css: { mdMemberListVisible : Visible }, visible: HasMembers()">
<li class="memberItem">
<div data-bind="visible: $parent.isEven($data)">
<%=MemberItemTemplate %>
</div>
<div data-bind="visible: !$parent.isEven($data)">
<%=MemberAlternateItemTemplate %>
</div>
</li>
</ul>
</div>
<div class="circleDirectory" id="circleDirectory" >
<ul id="cdMembersList" data-bind =" foreach: {data: CircleMembers, afterRender: handleAfterRender}">
<li class="memberItem">
<div class="mdMemberDetails">
<a href="" class="mdMemberImg" data-bind="attr: { title: DisplayName, href: ProfileUrl }">
<span><img data-bind="attr: { src: getProfilePicture(50,50), title: DisplayName }" /></span>
</a>
<ul class="MdMemberInfo">
<li class="mdDisplayName" >
<a href="" title="" class="mdMemberTitle"
data-bind="attr: { title: DisplayName, href: ProfileUrl },
event: { mouseover: $parent.showPopUp }">
<span data-bind="text: DisplayName"></span>
</a>
</li>
<li class="mdTitle"><p><span data-bind="text: Title"></span></p></li>
<li class="mdLocation"><p><span data-bind="text: Location()"></span></p></li>
</ul>
</div>
</li>
</ul>
</div>
Each one of the DIVs which contain the foreach binding loop in them works perfectly well without the other. For instance, the bottom div (id= cdMembersList) will work fine but when I add the upper div with the binding markups it will stop working. The same thing happens vise verse.
Does anybody have a clue why it might happen? Can i not have 2 loops in one view model?
looking forward to solving this mystery.
thanks,
David
Ok, I hate to say it but the answer is very simple as always. I didn't add to my view model the Visible property for
css: { mdMemberListVisible : Visible }
When I created a new script file I simply skipped this property. A few lessons:
You can run more than one loop in one view model.
Always check that you have all the properties defined in the view model.
Also, apparently it helps creating a question on this board since it makes you think clearly about the problem and revisit your actions. I had spent 2 hours chasing this problem before i posted my question, and then it took me 15 minutes to solve it after I posted it.

Resources