How to sort columns in Grails view - sorting

In the index method, I list records for a particular player.
e.g.
def index() {
Player player = Player.get( params.playerId)
def records = Record.findAllByPlayer(player, params:params)
[records: records, playerInstance: player, params: params]
}
This works, but none of the sortable columns work in the view.
How do I look up which column I need to sort, and how to I then use this information in findAllByXXX? I have scoured Google but have not found any examples which don't involve the simple "Books.list(params)" pattern.
In the view, I have sortableColumns:
<g:sortableColumn property="createdAt" title="${message(code: 'paymentRecord.created.label', default: 'Created at')} "
params="${params}" style="width: 15%;"/>
and I see the parameters at the end of the URL, e.g.:
/index/1?sort=credit&order=desc

Pagination and sorting parameters which are available on list method can also be used with dynamic finders.
So you can do it like this, if you have sort and order available in params.
Record.findAllByPlayer(player, params)
Or
Record.findAllByPlayer(player, [max: xx, offset: xx, sort: "name", order: "desc"])

You need to do Record.findAllByPlayer(player, params) instead of Record.findAllByPlayer(player, params:params).
You can see examples here in grails documentation. One example from the documentation is
def results = Book.findAllByTitle("The Shining",
[max: 10, sort: "title", order: "desc", offset: 100])

Related

Ruby: Paginate and sort across a large number of records

When simply displaying large amounts of data (over 100k records) my code works well, and I paginate on the server.
However, when I need to sort this data I'm stuck. I'm only sorting on the page, and NOT sorting on ALL the records related to this one customer.
How can I paginate but also sort across all the records of my customer and NOT simply sort the records returned from the server side pagination?
I'm also using BootStrap Table to display all my data.
Here is my code that gets all the customers:
def get_customers
#data_to_return = []
#currency = current_shop.country_currency
customers = current_shop.customers.limit(records_limit).offset(records_offset)#.order("#{sort_by}" " " "#{sort_order}")
customers.each do |customer|
#data_to_return.push(
state: false,
id: customer.id,
email: customer.email,
accepts_marketing: customer.accepts_marketing,
customer_status: customer.customer_status,
tags: customer.tags)
end
sort_customers
end
And then this is the sort_customers method:
def sort_customers
fixed_data = data_to_return.sort_by {|hsh| hsh[sort_by]}
customer_size = current_shop.customers.length
if sort_order == "ASC"
fixed_data
else
fixed_data.reverse!
end
render json: {"total": customer_size, "rows": fixed_data}
end
In the above code you can see that data_to_return is coming from get_customers and its limited. But I don't want to return ALL the customers for many reasons.
How can I sort across all the records, but only return the paginated subset?
You should actually sort at the model/query level, not at the ruby level.
The difference is basically:
# sort in ruby
relation.sort_by { |item| foo(item) }
# sort in database - composes with pagination
relation.order('column_name ASC/DESC')
In the first case, the relation is implicitly executed, enumerated and converted to array before calling sort_by. If you did pagination (manually or with kaminari), you will get just that page of data.
In the second case, you are actually composing the limit, offset and where (limit and offset are anyways used under the hood by kaminari, where is implicit when you use associations) with a order so your database would execute
SELECT `customers`.`*` FROM `customers`
WHERE ...
OFFSET ...
LIMIT ...
ORDER BY ...
which will return the correct data.
A good option is to define scopes in the model, like
class Customer < ApplicationRecord
scope :sorted_by_email, ->(ascending = true) { order("email #{ascending ? 'ASC' : 'DESC'}") }
end
# in controller
customers = current_shop.customers.
limit(records_limit).
offset(records_offset).
sorted_by_email(false)
You can resolve sorting and paginate issue using Data Tables library, which is client side. It's a Jquery library. Using this you need to load all data into page, then it would work very well.
Below are the references please check.
Data tables jquery libray
Data tables gem for rails
You can try these, they will work very well. You can customise it as well
If the answer is helpful, you can accept it.

CouchDB 2.0 - How to autoincrement keys in a View?

In CouchDB 2.0, I'm trying to create an ordered list as the keys from a View, but it doesn't work.
My code for the View document:
var i = 0;
function (doc) {
if (doc.type === "comment") {
emit(i++, doc.webpages);
}
}
The result is that all keys are equal to 0. How can I make it so that each document gets an autoincremented key?
Thanks!
A sequential ID probably isn't the best choice for most real applications. For example, if you were to build a commenting system I would approach it like this (there's a similar example in the couch docs):
Comments would be docs with a structure like this:
{
"_id": "comment_id",
"parent":"comment_id, or article_id if a top level comment"
"timestamp" : "iso datetime populated by the server",
"user_id": "the person who wrote the comment",
"content": "content of the comment"
}
To display all the top level comments of a given parent (either article or parent comment), you could use a view like this:
def function(doc){
emit([doc.parent, doc.timestamp, doc.user_id], doc._id)
}
To query this efficiently, you'd could use the following query options to grab the first twenty:
{
"startkey": ["parent_id"],
"endkey": ["parent_id", {}],
"limit": 20,
"skip": 0,
"include_docs": true
}
The comments will automatically be sorted by the date they were posted because the view is ordered by [parent, datetime, and then user]. You don't have the pass a value for anything other than parent with your key for benefit from this.
Another thing of note is by not passing the content of the comment to the view and instead using include_docs, your index will remain as slim as possible.
To expand on this:
If you want to show replies to a base comment, you can just change
the start and end keys to that comment's id.
If you want to show the next 20 comments, just change skip to 20.
If you want more comments shown initially, just up the limit value.
In answer to your comment, if you had an array or parents in your document like:
"parents" : ["a100", "a101", "a102"]
Everything else would remain the same, except you would emit a row for each parent.
def function(doc){
doc.parents.map( function (parent){
emit([doc.parent, doc.timestamp, doc.user_id], doc._id)
});
}

Is it possible to filter the descendant elements returned from an XPath query?

At the moment, I'm trying to scrape forms from some sites using the following query:
select * from html
where url="http://somedomain.com"
and xpath="//form[#action]"
This returns a result like so:
{
form: {
action: "/some/submit",
id: "someId",
div: {
input: [
... some input elements here
]
}
fieldset: {
div: {
input: [
... some more input elements here
]
}
}
}
}
On some sites this could go many levels deep, so I'm not sure how to begin trying to filter out the unwanted elements in the result. If I could filter them out here, then it would make my back-end code much simpler. Basically, I'd just like the form and any label, input, select (and option) and textarea descendants.
Here's an XPath query I tried, but I realised that the element hierarchy would not be maintained and this might cause a problem if there are multiple forms on the page:
//form[#action]/descendant-or-self::*[self::form or self::input or self::select or self::textarea or self::label]
However, I did notice that the elements returned by this query were no longer returned under divs and other elements beneath the form.
I don't think it will be possible in a plain query as you have tried.
However, it would not be too much work to create a new data table containing some JavaScript that does the filtering you're looking for.
Data table
A quick, little <execute> block might look something like the following.
var elements = y.query("select * from html where url=#u and xpath=#x", {u: url, x: xpath}).results.elements();
var results = <url url={url}></url>;
for each (element in elements) {
var result = element.copy();
result.setChildren("");
result.normalize();
for each (descendant in y.xpath(element, filter)) {
result.node += descendant;
}
results.node += result;
}
response.object = results;
» See the full example data table.
Example query
use "store://VNZVLxovxTLeqYRH6yQQtc" as example;
select * from example where url="http://www.yahoo.com"
» See this query in the YQL console
Example results
Hopefully the above is a step in the right direction, and doesn't look too daunting.
Links
Open Data Tables Reference
Executing JavaScript in Open Data Tables
YQL Editor
This is how I would filter specific nodes but still allow the parent tag with all attributes to show:
//form[#name]/#* | //form[#action]/descendant-or-self::node()[name()='input' or name()='select' or name()='textarea' or name()='label']
If there are multiple form tags on the page, they should be grouped off by this parent tag and not all wedged together and unidentifiable.
You could also reverse the union if it would help how you'd like the nodes to appear:
//form[#action]/descendant-or-self::node()[name()='input' or name()='select' or name()='textarea' or name()='label'] | //form[#name]/#*

Grails Sorting Issue

I have a grails app developed.
Herein, I have a page which accepts data and as per the data . goes to the list action, fires an sql, populates the data into reconciliationInstance object and displays it in list.gsp.
In my list.gsp, I have,
<g:sortableColumn property="access"
title="${message(code: 'reconciliationInstance.access.label', default: 'Access')}"
style="width: 10px" defaultOrder="desc"/>
However, when I click on the "Amount" heading, it takes me back to the list action again.
I have around 15 columns in the page and want to have sorting for all of them.
Am I missing something here??
To rectify this issue ,I wrote the below code.
Redirected to action sort. But theres something wrong here I believe.
def sort = {
if (!params.sort) params.sort = "title"
if (!params.order) params.order = "asc"
def reconciliationInstanceList = new ArrayList<Reconciliation>()
reconciliationInstanceList=session["reconciliationInstanceList"]
order(params.sort, params.order)
[reconciliationInstanceList: reconciliationInstanceList]
}
I have saved reconciliationInstanceList in a session.
Any Advice/Inputs?
My list action code is as below.
def list ={
//Taking parameters entered in the previous page
def odcNum=params.odcNum
def odcDate=params.odcDate
def date=null
def reconciliationInstance = new Reconciliation()
reconciliationInstance.properties=params
//Validation if all parameters have been entered by the user
if (reconciliationInstance.validate()) {
def results
SimpleDateFormat sdfSource = new SimpleDateFormat("dd-MMM-yyyy")
if(odcDate instanceof Date) {
date = sdfSource.format(odcDate);
}else{
date = odcDate
}
//Query to be fired. I have altered this query a bit. My actual query returns around 15 parameters
String odcData="select odc_access from odc_manager where odc_date=to_char('" + date + "') and odc_num like trim('" + odcNum + "')"
def reconciliationInstanceList = new ArrayList<Reconciliation>()
Sql sql = new Sql(dataSource)
results = sql.eachRow (odcData)
{
def reconciliation = new Reconciliation()
reconciliation.setAccess it.access
reconciliationInstanceList.add reconciliation
session["reconciliationInstanceList"]=reconciliationInstanceList
}
[reconciliationInstanceList: reconciliationInstanceList]
}
else {
render(view: "search", model: [reconciliationInstance: reconciliationInstance])
}
}
BTW I am a novice at grails. Therefore, you would find a bit of java in my code.
Somethings missing in my code? Therefore sort doesnt work. Inputs?
it should take you back to the list action, but the params passed to the action will let it know how to sort the resulting model.
the behavior is correct, I assume your code in the list action is not coded properly... You might want to include that code if you want additional guidance.
See sample list action
http://www.grails.org/GSP+Tag+-+sortableColumn
Found a work around for this. Passed the sort order (params.order) to the sql query and let the query do the sorting. Displayed the results on the gsp then.
If there's any other way, do let me know.

CouchDB Views: remove duplicates *and* order by time

Based on a great answer to my previous question, I've partially solved a problem I'm having with CouchDB.
This resulted in a new view.
Now, the next thing I need to do is remove duplicates from this view while ordering by date.
For example, here is how I might query that view:
GET http://scoates-test.couchone.com/follow/_design/asset/_view/by_userid_following?endkey=[%22c988a29740241c7d20fc7974be05ec54%22]&startkey=[%22c988a29740241c7d20fc7974be05ec54%22,{}]&descending=true&limit=3
Resulting in this:
HTTP 200 http://scoates-test.couchone.com/follow/_design/asset/_view/by_userid_following
http://scoates-test.couchone.com > $_.json.rows
[ { id: 'c988a29740241c7d20fc7974be067295'
, key:
[ 'c988a29740241c7d20fc7974be05ec54'
, '2010-11-26T17:00:00.000Z'
, 'clementine'
]
, value:
{ _id: 'c988a29740241c7d20fc7974be062ee8'
, owner: 'c988a29740241c7d20fc7974be05f67d'
}
}
, { id: 'c988a29740241c7d20fc7974be068278'
, key:
[ 'c988a29740241c7d20fc7974be05ec54'
, '2010-11-26T15:00:00.000Z'
, 'durian'
]
, value:
{ _id: 'c988a29740241c7d20fc7974be065115'
, owner: 'c988a29740241c7d20fc7974be060bb4'
}
}
, { id: 'c988a29740241c7d20fc7974be068026'
, key:
[ 'c988a29740241c7d20fc7974be05ec54'
, '2010-11-26T14:00:00.000Z'
, 'clementine'
]
, value:
{ _id: 'c988a29740241c7d20fc7974be063b6d'
, owner: 'c988a29740241c7d20fc7974be05ff71'
}
}
]
As you can see, "clementine" shows up twice.
If I change the view to emit the fruit/asset name as the second key (instead of the time), I can change the grouping depth to collapse these, but that doesn't solve my order-by-time requirement. Similarly, with the above setup, I can order by time, but I can't collapse duplicate asset names into single rows (to allow e.g. 10 assets per page).
Unfortunately, this is not a simple question to explain. Maybe this chat transcript will help a little.
Please help. I'm afraid that what I need to do is still not possible.
S
You can do this using list function. Here is an example to generate a really simple list containing all the owner fields without dupes. You can easily modify it to produce json or xml or anything you want.
Put it into your assets design doc inside the lists.nodupes and use like this:
http://admin:123#127.0.0.1:5984/follow/_design/assets/_list/nodupes/by_userid_following_reduce?group=true
function(head, req) {
start({
"headers": {
"Content-Type": "text/html"
}
});
var row;
var dupes = [];
while(row = getRow()) {
if (dupes.indexOf(row.key[2]) == -1) {
dupes.push(row.key[2]);
send(row.value[0].owner+"<br>");
}
}
}
Ordering by one field and uniquing on another isn't something the basic map reduce can do. All it can do is sort your data, and apply reduce rollups to dynamic key-ranges.
To find the latest entry for each type of fruit, you'd need to query once per fruit.
There are some ways to do this that are kinda sane.
You'll want a view with keys like [fruit_type, date], and then you can query like this:
for fruit in fruits
GET /db/_design/foo/_view/bar?startkey=["apples"]&limit=1&descending=true
This will give you the latest entry for each fruit.
The list operation could be used to do this, it would just echo the first row from each fruit's block. This would be efficient enough as long as each fruit has a small number of entries. Once there are many entries per fruit, you'll be discarding more data than you echo, so the multi-query approach actually scales better than the list approach, when you get to a large data set. Luckily they can both work on the same view index, so when you have to switch it won't be a big deal.

Resources