I have a search webpage where user's can filter the search results by a person's ethnicity, as a checkbox group. There are 12 'ethnicity' checkboxes. The params get passed into g:paginate as the following, so that the user can page through the results and preserve what was checked in the ethnicity checkboxes:
<g:paginate controller="search" action="list" total="${resultCount}" params="${params}"/>
What gets output for the links includes a bunch of unnecessary data for each built URL:
2
I'd like the pagination link URLs to be output without all the extra _ethnicity variables that get passed back in the original search post:
2
How can I get the params into the paginate tag without all the extra unnecessary fields? Functionally it works, but the URLs for the paginate get requests are too long and look hideous.
Try this..,.
<g:paginate controller="search" action="list" total="${resultCount}" params="${params.findAll { it.key == 'ethnicity' && it.value }}"/>
it gives you
2
One of the dirty way to achieve what you want is
<g:paginate controller="search" action="list" params="${
params.findAll { a ->
if (a.value instanceof Collection) {
def c = a.value.findAll { b ->
return b
}
if (c) {
return c
}
} else {
return a.value
}
}
}"/>
EDIT:
spock99 answer is much better than mine, one more way is
params="${params.findAll { !it.key.toString().startsWith("_") }}"
Per the previous user, this works to filter out the extra fields, though it is ugly.
params="${params.findAll { a ->
if (!a.key.toString().startsWith("_")) {
return a.value
}
}
}"
EDIT:
Actually a cleaner way is to put this in the controller:
params.keySet().asList().each { if (it.toString().startsWith("_")) params.remove(it) }
Then in the g:paginate you can stick with
params="${params}"
Related
I want my angular material autocomplete to be a list of suggestions but not requirements. However I'm not sure how to implement as their is no clear example from the Angular Material docs.
In the example below my model is $ctrl.item.category
Clearly the example below is wrong, as my model is linked to md-selected-item, but this only works if I select an item. I want the user to be able to free enter the text if the item is not in the list. Basically how autocomplete already works in most browsers.
I see plenty of questions on how to disable this, but they are not trying to disable so much as clean up the left over text when an item is not selected. In these cases when an item is not selected then the model value is null, but text is left in the input.
I want the text left int he input to be the model value if the person does not select (or a match is not made).
md-autocomplete(
md-floating-label="Category Name"
flex="50"
md-input-name="category"
md-selected-item="$ctrl.item.category"
md-search-text="catSearch"
md-items="category in $ctrl.categories"
md-item-text="category"
md-min-length="0"
md-select-on-match=""
md-match-case-insensitive=""
required=""
)
md-item-template
span(md-highlight-text="catSearch" md-highlight-flags="^i") {{category}}
My options ($ctrl.categories) is an array of strings ['Food','Liqour'] and I wan the user to be able to use one of those or free enter Tables as their choice.
In this case you should link md-search-text to your model.
If you want to implement fuzzy search you have to write the filter method yourself. Look at this example:
template:
<md-autocomplete
md-items="item in $ctrl.itemsFilter()"
md-item-text="item.label"
md-search-text="$ctrl.query"
md-selected-item="$ctrl.selected"
>
<md-item-template>
<span md-highlight-text="$ctrl.query">{{item.label}}</span>
</md-item-template>
<md-not-found>
No item matching "{{$ctrl.query}}" were found.
</md-not-found>
<div ng-messages="$ctrl.myValidator($ctrl.query)">
<div ng-message="short">Min 2 characters</div>
<div ng-message="required">Required value</div>
</div>
</md-autocomplete>
controller:
var items = [ ... ];
ctrl.itemsFilter = function itemsFilter() {
return ctrl.query ? filterMyItems(ctrl.query) : items;
};
ctrl.myValidator = function (value) {
return {
short: value && value.length < 2,
required : value && value.length < 1,
};
};
then you just need to add filterMyItems method to filter your items
To improve the answer of #masitko, I have implemented the filter in a way, that it adds the query to the filtered list. So it becomes selectable and a valid option. So it's possible to make the autocomplete a suggestion box.
I'm using ES6 in my projects. But it should be easily adaptable to ES5 code.
myFilter() {
if (!this.query) return this.items;
const
query = this.query.toLowerCase(),
// filter items where the query is a substing
filtered = this.items.filter(item => {
if (!item) return false;
return item.toLowerCase().includes(query);
});
// add search query to filtered list, to make it selectable
// (only if no exact match).
if (filtered.length !== 1 || filtered[0].toLowerCase() !== query) {
filtered.push(this.query);
}
return filtered;
}
I have a domain class
class Url {
UUID id
String url
static hasMany = [
indications:UrlIndication
]
...
}
And
class UrlIndication {
UUID id
String name
static belongsTo = Url
...
}
I want to choose urls so that it has all the necessary UrlIndication elements in a given list indicationsId.
For that I use an association and criteria like this one:
indications {
and {
indicationsId.each{
indication->
eq ('id',UUID.fromString(indication as String))
}
}
}
However, all I got is an empty result. Can you suggest any modifications/ other methods so that I can do this? Thanks in advance
Your query returned an empty list because it's the equivalent of the expression (pseudo-code): if 1 = 1 and 1 = 2 and 1 = 3
Such an expression would always be false. in or inList would not work for the reason #innovatism described.
In theory, Criteria's eqAll() or HQL's = ALL would work. But, I don't know for sure because I could not get either one to work.
What will work is to use inList to return a subset of Urls: those which contain at least one of the UrlIndication IDs. Then use Groovy's containsAll() to finish the job.
def ids = indicationsId.collect { UUID.fromString(it as String) }
Url.createCriteria()
.buildCriteria {
indications {
inList 'id', ids
}
}
.setResultTransformer(org.hibernate.Criteria.DISTINCT_ROOT_ENTITY)
.list()
.findAll {
it.indications.id.containsAll(ids)
}
Since the query has the potential to return duplicate Url instances, the ResultTransformer is set to return a unique list.
Finally, findAll() is used along with containsAll() to filter the list further.
Using eqAll (maybe)
Something like the following might work. Something funky is going on with Grails' HibernateCriteriaBuilder that causes the eqAll method to look up properties in the root entity; completely ignoring the sub criteria. So the following uses Hibernate directly. It didn't work for me, but it's as close as I could get. And it gave me a head-ache!
Url.createCriteria().buildCriteria {}
.createCriteria('indications', 'i')
.add(org.hibernate.criterion.Property.forName('i.id').eqAll(org.hibernate.criterion.DetachedCriteria.forClass(UrlIndication)
.add(org.hibernate.criterion.Restrictions.in('id', ids))
.setProjection(org.hibernate.criterion.Property.forName('id'))
))
.setResultTransformer(org.hibernate.Criteria.DISTINCT_ROOT_ENTITY)
.list()
The problem I had is I could not get Restrictions.in to work. Restrictions.eq works fine.
the in clause should do:
indications {
'in' 'id', indicationsId.collect{ UUID.fromString indication.toString() }
}
What is their difference and why and where we need to use them,i think it seems like they have no difference at all to me ?
withCriteria { ... } is essentially shorthand for createCriteria().list { ... }. If you need to use any of the other criteria methods (get, count, ...) or pass pagination parameters to list then you have to use the long-hand form.
SomeDomain.createCriteria().list(max:10, offset:50) {
// ...
}
It's worth adding what I just came across in the grails documentation for createCriteria().
Because that query includes pagination parameters (max and offset), this will return a PagedResultList which has a getTotalCount() method to return the total number of matching records for pagination. Two queries are still run, but they are run for you and the results and total count are combined in the PagedResultList.
Source
This means you can use getTotalCount() without having to initiate the call (it's made for you). This is very helpful. The example documentation shows:
def c = Account.createCriteria()
def results = c.list (max: 10, offset: 10) {
like("holderFirstName", "Fred%")
and {
between("balance", 500, 1000)
eq("branch", "London")
}
order("holderLastName", "desc")
}
println "Rendering ${results.size()} Accounts of ${results.totalCount}"
This capability is not available when using withCriteria().
Example of createCriteria():
def criteria = OfferCredit.createCriteria {
offer {
eq('status', LeverageUtils.ACTIVE_STATUS)
ge('expirationDate', new Date())
}
user {
eq('userId', userId)
}
eq('status', LeverageUtils.ACTIVE_STATUS)
order('creationDate', 'asc')
}
criteria.list()
Example of withCriteria():
List<Supermarket> results = Supermarket.withCriteria {
like("sp_street", params.street)
productSupermarket {
product {
idEq(params.product)
}
// or just eq('product', someProduct)
}
maxResults(10)
}
withCriteria executes and returns the list. It provides a closure using which you can customize the criteria before it gets executed.
createCriteria just creates a criteria object which you can modify and then explicitly call the list method to execute.
If criteria is simple or if it is defined in a single place it is better to use withCriteria.
If you need to pass the criteria around (create it in one function and pass it to others) createCriteria would be better. I think withCriteria support is limited.
withCriteria ->
Purpose -> Allows inline execution of Criteria queries.
If no matching records are found, an empty List is returned.
If a projection is specified:
returns a single value if it only contains one field
a List in case there are multiple fields in the projection
I have a simple Tag class with only two fields, name and value,
class Tag {
String name
String value
}
and I'm trying to render an XML where I want to search for parts of both parameters via findBy...Ilike().
def getXml = {
render Tag.findAllByNameAndValueIlike("%${params.name}%", "%${params.value}%") as XML
}
But this doesn't give my any results. If I use only one parameter, it works as I expect:
def getXml = {
render Tag.findAllByNameIlike("%${params.name}%") as XML
}
My next question is probably going to be about filtering the results, and adding other "similar" tags to the returns list, so is there a way to solve the above with something like:
def getXml = {
list = Tag.findAllByNameIlike("%${params.name}%")
list.add(Some other stuff)
list.sortBy(Some thing, maby name length)
}
For your multiple-field ilike query you can use withCriteria:
def result = Tag.withCriteria {
ilike('name', "%${params.name}%")
ilike('value', "%${params.value}%")
}
This will return a list of Tag domains whose name matches the provided name and value matches the provided value.
The Criteria DSL will probably let you do most of the filtering you need, but you can also consider using some of the Groovy collection examples here.
You have to put the restrictions(InList, NotNull, etc) on each field of a dynamic finder. If you do not, it assumes equals. Here is what you were looking for:
Tag.findAllByNameIlikeAndValueIlike("%${params.name}%", "%${params.value}%")
Both answer are good. I tried both, but I have to say I like the withcCritia the best. It seems very flexibly.
def result = Tag.withCriteria {
if(params.name != null)
ilike('name', "%${params.name}%")
if(params.value != null)
ilike('value', "%${params.value}%")
}
result.add(new Tag('name': "something"))
render result as XML
I am using Nimble and Shiro for my security frameworks and I've just come accross a GORM bug. Indeed :
User.createCriteria().list {
maxResults 10
}
returns 10 users whereas User.list(max: 10) returns 9 users !
After further investigations, I found out that createCriteria returns twice the same user (admin) because admin has 2 roles!!! (I am not joking).
It appears that any user with more than 1 role will be returned twice in the createCriteria call and User.list will return max-1 instances (i.e 9 users instead of 10 users)
What workaround can I use in order to have 10 unique users returned ?
This is a very annoying because I have no way to use pagination correctly.
My domain classes are:
class UserBase {
String username
static belongsTo = [Role, Group]
static hasMany = [roles: Role, groups: Group]
static fetchMode = [roles: 'eager', groups: 'eager']
static mapping = {
roles cache: true,
cascade: 'none',
cache usage: 'read-write', include: 'all'
}
}
class User extends UserBase {
static mapping = {cache: 'read-write'}
}
class Role {
static hasMany = [users: UserBase, groups: Group]
static belongsTo = [Group]
static mapping = { cache usage: 'read-write', include: 'all'
users cache: true
groups cache: true
}
}
Less concise and clear, but using an HQL query seems a way to solve this problem. As described in the Grails documentation (executeQuery section) the paginate parameters can be added as extra parameters to executeQuery.
User.executeQuery("select distinct user from User user", [max: 2, offset: 2])
this way you can still use criteria and pass in list/pagination paramaters
User.createCriteria().listDistinct {
maxResults(params.max as int)
firstResult(params.offset as int)
order(params.order, "asc")
}
EDIT: Found a way to get both! Totally going to use it now
http://www.intelligrape.com/blog/tag/pagedresultlist/
If you call createCriteria().list() like this
def result=SampleDomain.createCriteria().list(max:params.max, offset:params.offset){
// multiple/complex restrictions
maxResults(params.max)
firstResult(params.offset)
} // Return type is PagedResultList
println result
println result.totalCount
You will have all the information you need in a nice PagedResultList format!
/EDIT
Unfortunately I do not know how to get a combination of full results AND max/offset pagination subset in the same call. (Anyone who can enlighten on that?)
I can, however, speak to one way I've used with success to get pagination working in general in grails.
def numResults = YourDomain.withCriteria() {
like(searchField, searchValue)
order(sort, order)
projections {
rowCount()
}
}
def resultList = YourDomain.withCriteria() {
like(searchField, searchValue)
order(sort, order)
maxResults max as int
firstResult offset as int
}
That's an example of something I'm using to get pagination up and running. As KoK said above, I'm still at a loss for a single atomic statement that gives both results. I realize that my answer is more or less the same as KoK now, sorry, but I think it's worth pointing out that rowCount() in projections is slightly more clear to read, and I don't have comment privileges yet :/
Lastly: This is the holy grail (no pun intended) of grails hibernate criteria usage references; bookmark it ;)
http://www.grails.org/doc/1.3.x/ref/Domain%20Classes/createCriteria.html
Both solutions offered here by Ruben and Aaron still don't "fully" work for pagination
because the returned object (from executeQuery() and listDistinct) is an ArrayList
(with up to max objects in it), and not PagedResultList with the totalCount property
populated as I would expect for "fully" support pagination.
Let's say the example is a little more complicated in that :
a. assume Role has an additional rolename attribute AND
b. we only want to return distinct User objects with Role.rolename containing a string "a"
(keeping in mind that a User might have multiple Roles with rolename containing a string "a")
To get this done with 2 queries I would have to do something like this :
// First get the *unique* ids of Users (as list returns duplicates by
// default) matching the Role.rolename containing a string "a" criteria
def idList = User.createCriteria().list {
roles {
ilike( "rolename", "%a%" )
}
projections {
distinct ( "id" )
}
}
if( idList ){
// Then get the PagedResultList for all of those unique ids
PagedResultList resultList =
User.createCriteria().list( offset:"5", max:"5" ){
or {
idList.each {
idEq( it )
}
}
order ("username", "asc")
}
}
This seems grossly inefficient.
Question : is there a way to accomplish both of the above with one GORM/HQL statement ?
You can use
User.createCriteria().listDistinct {
maxResults 10
}
Thanks for sharing your issue and Kok for answering it. I didn't have a chance to rewrite it to HQL. Here is my solution (workaround): http://ondrej-kvasnovsky.blogspot.com/2012/01/grails-listdistinct-and-pagination.html
Please tell me if that is useful (at least for someone).