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.
Related
We have not been able to find an example of how to do this anywhere.
It seems the way to send data from a controller to a view is using respond. Usually respond takes one argument. We want to send many.
E.g. if we have a control panel, with many different datas being displayed (users account balances, recent transactions, messages etc).
Our controller looks like this:
def index() {
User user = User.find (session.getAttribute("user"))
def accounts = Accounts.findAllByUser(user)
def messages = Messages.findAllByUser(user)
// this doesn't work, but we guess something like this might be possible.
respond [accounts:accounts, messages:messages]
}
Additionally, findAllBy documentation doesn't say what it returns. It might be an array, a map, a list.
Then in our views we should be able to do something like:
${accounts[0].balance}
or maybe
${accounts.size()}
if findBy... returns a list.
thanks to Mike W, the answer is:
def index() {
User user = User.find (session.getAttribute("user"))
def accounts = Accounts.findAllByUser(user)
def messages = Messages.findAllByUser(user)
[accounts:accounts, messages:messages]
}
Its a shame this is not in the examples in the docs, its a very useful feature
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.
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 } ]
I was following this tutorial http://www.asp.net/mvc/tutorials/mvc-music-store
when I stumbled on this piece of code.
public ActionResult AddToCart(int id)
{
// Retrieve the album from the database
var addedAlbum = storeDB.Albums
.Single(album => album.AlbumId == id);
// Add it to the shopping cart
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.AddToCart(addedAlbum);
// Go back to the main store page for more shopping
return RedirectToAction("Index");
}
I don't understand two things:
1)
var addedAlbum = storeDB.Albums
.Single(album => album.AlbumId == id);
What is this code doing? I don't know what the operator => does. Also I guess .Single is some function for the database?
2)
This function is having a call to itself? I don't see how it adds the album to the cart this way. Wouldn't this cause a function to go into an infinite loop?
It seems there are a lot of core C# that you aren't quite familiar with yet.
the => operator is the lambda operator, which is a succinct way of writing an inline function.
The Single function is an extension method which in this case is makes a call to the database. This method makes use of a neat feature known as expression trees to convert the strongly typed C# comparison into the corresponding SQL code. How it works is a pretty advanced topic, so for now just consider it "magic".
The AddToCart method of the cart object is different from the AddToCart controller action method the code is currently in. I don't have a link for that, since that's fairly basic object-oriented programming.
I would assume that cart.AddToCart will actually update the database.
Also read up on LINQ for a better understanding. This is most likely either Linq To Sql or LINQ to Entities using the Entity Framework.
The Action is being passed the ID of a album which can then be retrieved from the database with storeDB.Albums.Single() call. (the lambda is saying "find the entry in the database where the value in the AlbumId column matches the ID passed to the controller.") Think of .Single as the LINQ facsimile of:
SELECT TOP(1) *
FROM Albums
WHERE Albums.AlbumId = <id>
It's then grabbing the user's shopping cart and adding that fetched album object to the cart.
Then you're redirected to the index where it can list all entries in the cart.
I have a view that calls on four different partial views (.ascx) to populate a portion of the view via RenderAction call. Each of the partial views use the same view model, but each returns a different set of data via their own EF query in the underlying model. As you would assume from the sharing of the viewmodel, the partial views all return pretty much the same type of information -- the difference is in the filtering. E.g. "New Products" vs. "Popular Products" vs. "Recommended Products", etc.
I'm getting the data that I want, but I need to address the structure because my performance is pretty poor. The performance of each individual query doesn't seem too bad (I've used LinqPad and tested the generated SQL in SQL Server and the performance is great). However, altogether, the page load time is pretty poor as I switch categories and reload the page.
Rather than calling 4 queries against the SQL server, can I call one that pulls everything (for all 4) and then filter the results into the individual partial views? Would this be better?
Many thanks for your suggestions/advice.
Yes. Doing one query and filtering would be much better.
List<Widgets> widgetsFromQuery = (from w in db.Widgets
where w.Name.EndsWith("-sprocket") ||
w.Name.EndsWith("-seat") ||
w.Name == "toaster"
select c).ToList();
Calling ToList() at the end, forces Linq to perform the query immediately.
Then you use widgetsFromQuery as your Model, and in each view, filter like this:
var filteredModel = Model.Select(w => w.Name.EndsWith("-sprocket"));
var filteredModel = Model.Select(w => w.Name.EndsWith("-seat"));
var filteredModel = Model.Select(w => w.Name == "toaster"));
Further performance enhancements would be something like:
1) Cache the output of the query in the session (if it's user specific) or application cache if not.
2) Make each view tied to an action, load with AJAX, and use output cache attributes on the actions.