Grails g:paginate - Passing search parameters back to Controller - grails

I have built an advanced search option that allows users to search a multitude of different fields - 38 possible fields in total.
Once the initial search has been submitted, 10 results are displayed with pagination options at the bottom (using grails' <g:paginate> tag).
I think I have a few problems here.
1) I need to somehow pass the fields back to the controller using the params attribute of the g:paginate tag, but I don't really want to create a params map of 40 something parameters.
2) I pass back to the page from the Controller a map of search parameters (so that they can have a dismissable list of parameters that perform the search again without the dismissed parameter). I can pass this back in the params attribute, but it's passed back to the controller as a String and not a map so I don't know how to iterate through it (I realised this after it iterated through each separate character!).
3) The URL of the links that the g:paginate tag creates could potentially be massive, depending on how much of the search fields the user enters. Could there be a danger of the URL exceeding it's maximum amount?
I'm contemplating changing from the built in grails paginate functionality, and creating a form which I can POST. I'm not sure if this is the best way though, and I may be missing some alternative way to do this that is much better.
Any help greatly received!

You can pass all your parameters to action using pageScope.variables like
<g:paginate params="${pageScope.variables}" total=.../>

index.gsp
<div class="pagination">
<g:paginate total="${personCount ?:0}" max="5" params="${params}" />
</div>
Controller.groovy
def index(Integer max)
{
def c=Person.createCriteria()
def results
int count
params.max = Math.min(max ?: 5, 100)
if(params.name != null)
{
params.max = 4
if(!params.offset)
{
params.offset = 0;
}
count = Person.countByNameLike("%"+params.name+"%")
results=c.list{
like("name","%"+params.name+"%")
maxResults(params.max)
firstResult(params.getInt("offset"))
}
}
else
{
results = Person.list(params)
count = Person.count()
}
respond results, model:[personCount: count]
}

I'd put your search params into session. Thus you can use the very basic grails g.paginate tag w/o polluting it. Each time the user changes his search, the parameters should get updated.

1 and 2) The best solution I found:
for the action:
def list() {
... //use params to filter you query and drop results to resultList of type PagedResultList
return [resultList: resultList, resultTotal: resultList.getTotalCount(), filterParams: params]
}
for the view:
<g:paginate total="${resultTotal}" action="list" params="${filterParams}"/>
See a complete example.
3) The url (and get parameters) limit size is 2000 (see why). If that is a problem to you, you'd better switch to an ajax solution or minimize the url size filtering only the filled params. You could accomplish that replacing this line:
return [resultList: resultList, resultTotal: resultList.getTotalCount(), filterParams: params.findAll{ it.value } ]

Related

Retain Sorting After Specific Action - Grails

I have a gsp form which displays the list of employees with the details (ie., Employee Name,Designation,Department,Status). All these columns are sortable. After calling a specific action in my controller class (ie., Changing the status of the employee from active to inactive and vice versa) the sorting gets disturbed. I am using the following code to sort while retrieving from DB
String strSort = params.sort ?: "empId";
strSort += " "
strSort += params.order?: "asc";
Is there any way I can retain the sort order which was there before posting a "Status change" action? If it is how it can be achieved?
As suggested by rvargas, it is possible through a variety of methods. queuekit plugin isn't released properly as yet so you could clone grails 3 / grails2 branch depending on which it is you are working with and also clone the test site to go with it to mess with this concept within the plugin:
In short You need to separate out your search feature and you can do this via a session value or send it as a subset list iteration.
I decided to not use sessions. Then when I click delete The bean is bound back in with the request sent (which be the id to delete)
At the very end it relists so no need to do any other here:
The most important bit being when I call the ajax reloadPage or even further postAction used by delete function is that I serialize search form. The actual search object is kept in a tidy manner here
But if this is too complex then in the very controller link the session search was commented out. I think you could just enable that forget all this complication and have a searchAgain() feature which renders the _list template like it does if it is xhr in my controller and rather than binding bean it binds the session.search map instead and if you did go down this route you probably want to change from g:render to g:include action="searchAgain"
Hope that helps you understand better
I can think of two ways to do it:
Pass your sort and order parameters to your action and send them back
with the result.
Store in session both parameters every time you update them.
To store and retrive from session use something like this:
private DEFAULT_SORT = 'myDefaultSort'
def myAction() {
if (params.sort && params.sort != session.getAttribute('sort-' + actionName)) {
session.setAttribute('sort-' + actionName, params.sort)
}
params.sort = session.getAttribute('sort-' + actionName)?:DEFAULT_SORT
...
//Your existing logic
}
If you receive a new/different sort parameter you save it into session. Then you try to load existing parameter from session, if you dont have any value stored, you get a default value.

Reusing json/model object to avoid making extra calls to controller

I've got a groovy userController and a _listMyUsers.gsp.
The _listMyUsers.gsp is using a
<g:dojoRemoteForm
formName="userSearchForm"
id="userSearchForm"
url="[controller:'user',action:'search']"
update="[success:'content']">
The method in the userController (search) is a simple criteria builder which returns the following back to the gsp, You can use controls in the gsp to customize the search criteria parameters (passed to the controller as param.field_name):
render (template:"listUsers",
model:[
users:users,
userTypes:UserTypeLookup.list(),
sortby:params.sortby,
direction:nextDirection,
currentDirection:sortDirection,
pager:pager,
organizations:orgs,
userType:userSearchTypes
])
Now this all works great and the model is then used to build out my usersList table. My problem comes in when I click on one of the users in the results to edit said users data, I then save. The save completes and returns to the main listUsers table. But it re-runs the search method with all searchCriteria wild carded as 'ALL' in the selections (so all users in the DB are returned).
My question is, how can I preserve the initial "custom" search returned so that when I get done editing my user, the original "search" is still there so my UI users don't have to go back and re-do their userSearch criteria again?
Thanks,
The Grails Cache Plugin might help you here. You could cache the output form the search action, using the user's query parameters as method arguments (so they can be used as keys to the cache).
#Cacheable('searchResults')
def search(String sortBy, String sortDirection /* other parameters */) {
// render the output
}
Then in your save action, you can use the CacheEvict annotation to clear the searchResults cache, so the search action will return the latest data:
#CacheEvict(value='searchResults', allEntries=true)
def saveUser() {
//save the user and return a response code
}
See the plugin documentation for details on specifying which items in the cache to evict, etc.

I am getting null values for the search parameters in my controller while clicking Forward button of Pagination in my GSP page

I am using < g:paginate > tag for pagination functionality in my GSP page as given below:
<g:paginate next="Forward" prev="Back" maxsteps="0" max="${max}"
offset ="${offset}" controller="job" action="getEmployee"
total="${employeeCount}"/>
Here value of offset, max and employeeCount is acquired from controller. In my GSP page, I have search fields like Name, Age etc in my form. When I click search button with some value Name or Age, I get data with multiple page displayed in my screen. When I click "Forward" button, the value for the search parameter (Name, Age) is null when I debugged and saw in jobController. So, I am getting exception.How can I pass my search parameters to the controller on clicking Forward button so that I can add these values in search criteria? Please help me with this condition. Currently I am accessing the parameters in controller by using code:
def name = params.name // this gives me null value
def age = params.age // this gives me null value
First, take a look at the documentation for the pagination tag.
You'll notice that there is this:
params (optional) - A Map of request parameters
Now, if you simply want to include the parameters that were used when the GSP was rendered you could try adding the params attribute like this:
<g:paginate params="${params}" next="Forward" prev="Back" maxsteps="0" max="${max}"
offset ="${offset}" controller="job" action="getEmployee"
total="${employeeCount}"/>
Of course, this all depends on your requirements which you didn't make very clear.

Grails : Updating Index.gsp from another controller

My Grails app is using version 2.3.6
In this app there's a Controller class:
class UserOrderController {
def index(Integer max) {
params.max = Math.min(max ?: 20, 100)
respond UserOrder.list(params), model:[userOrderInstanceCount: WorkOrder.count()]
}
}
Now in another Controller i am accessing the UserOrder objects and filtering based on product ID. Product ID is a string property in UserOrder domain class.
The other controller is:
class UserOrderFilterController {
def getUOBasedOnID () {
// Here i get a new list for **UserOrder**
// Now i want to draw the UserOrderController page with the new list
}
}
I am pretty new to Grails and not sure how to do this.
Should i be creating a new INDEX function in UserOrderController class and pass the new list?
Like shown below:
class UserOrderController {
def index(Integer max) {
params.max = Math.min(max ?: 20, 100)
respond UserOrder.list(params), model:[userOrderInstanceCount: userOrder.count()]
}
def index(List UserOrder) {
// create page???
}
}
UPDATE:
The UserOrderFilterController has it's own index.gsp file.
What am doing is: Accessing all the objects for UserOrder domain class and filter them based on a property.
Now in the index.gsp of UserOrderFilterController i will show the total number of objects/orders found. This number will be shown with a hyperlink using href and when the user clicks on it, it will go to index.gsp page of UserOrderController with only the filtered UserOder's displayed.
So what am expecting is:
<a href='${createLink(controller:'UserOrder', action:'index')}'>%s</a>
A href like shown above with a params field that will have the filtered list of UserOrder's.
I have no idea how to add the params/list to href. Is this possible?
Each action in Grails has a matching view. Duplicating actions is asking for trouble. If you have some new functionality that deserves a page of its own, then you should create a new action and a new view.
If it belongs on the home page, then put it through the same index method.
Note that in grails, you simply pass the values to the .gsp page and the .gsp page is what handles the formatting of the data. Your action should have absolutely 0 knowledge of the structure of the view. This is a core concept in MVC.
In this case, you can redirect to a controller, as per the docs. The one that should interest you the most is this:
redirect(controller: "yourController", action:"index", params=[filteredList: filteredList]);
This will redirect to the existing index action in your UserOrderController, and pass in the filtered list. From there, you can have something like..
if(params.filteredList) {
// You know this came from the filtered controller, so display this and don't
// make a DB call.
}
Point to Note
The structure of your grails application worries me. Why have you got an entirely separate controller for simply filtering data?
Edit
Taking the createLink approach can be improved a bit. Instead of..
<a href='${createLink(controller:'UserOrder', action:'index')}'>%s</a>
You can use the g:link functionality:
<g:link controller="UserOrder" action="index" params=['filteredList':filteredList]>%s</g:link>
If you are trying to create new controller for filter UserOrderFilterController, you can do
class UserOrderFilterController {
def getUOBasedOnID () {
//get filtered list
respond filteredList, [view:'userOrder/index',model:[userOrderInstanceCount: filteredListCount]]
}
}
You can see more about respond here.

paginate a list

I have a service which returns a list of domain class objects to a controller. I'm looking for something to take pagination parameters and paginate this list like MyDomain.list(params) does. Any suggestions?
Pass pagination params to your service method. If you do pagination after retrieving objects from database (in controller in your case), your query will still return many objects you don't need and it can become a performance issue.
Otherwise if you still want to have pagination without gorm features, then you can just slice your list i.e. def sliceList = list[5..10]
Do you want the pagination to take place in your controller, or in your service?
If you want to paginate in controller, it seems easy to do:
def myList = service.listItems()
if (params.sort)
myList = myList.sort {it."${params.sort}"}
if (params.order == "desc")
myList = myList.reverse()
int from = params.offset ?: 0
int to = from + (params.max ?: DEFAULT_SIZE)
myList = myList.subList(from, to)
If you want to paginate in service (for example, to not have to fetch all the rows from the service every time), you would have to move the pagination logic there. How would you implement it is dependent of what the service does; if it fetches the data by way of SQL statements, you would convert pagination params to directives like 'limit' and 'order by', etc.

Resources