I am trying to create a search action in my grails app that accepts a number of criteria and displays a table (similar to a default index action) of instances that match the criteria.
For search criteria that have many results I would like to use the paginate tag:
<g:paginate total="${alarmInstanceCount ?: 0}" />
By default, this tag forgets the search criteria. I believe that the params attribute allows me to add the search parameters to the links that the paginate tag creates.
To encapsulate my search criteria, I've organized them into their own map called searchParams.
However, when I try to pass my searchParams to the paginate tag:
<g:paginate total="${alarmInstanceCount ?: 0}" params="${[searchParams:searchParams]}"/>
my searchParams are turned into a string.
Here is an example of what I mean.
When I first open the search page the controller reports the params as follows:
[action:index, format:null, controller:alarm, max:10]
null
class org.codehaus.groovy.runtime.NullObject
So, since the params map doesn't contain an entry for searchParams it comes up as null and has the NullObject class. My code handles this case gracefully.
When I enter some text into the channelName field the controller reports the params as follows:
[searchParams.channelName:SWR, searchParams:[channelName:SWR], _action_index:List, _method:PUT, action:index, format:null, controller:alarm, max:10]
[channelName:SWR]
class org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap
So, now the params map has a searchParams entry which refers to a GrailsParameterMap. This is the desired behaviour, and the controller interprets this correctly.
However, if I click on the first entry in the paginate bar, the controller reports the params as follows:
[max:10, searchParams:[channelName:SWR], offset:10, action:index, format:null, controller:alarm]
[channelName:SWR]
class java.lang.String
In this third case, the params map has a searchParams entry that looks correct, but is actually a String object. This causes my code to implode with a:
No such property: channelName for class: java.lang.String.
Is this the expected behaviour of the params attribute of the paginate tag? If so, what is the cleanest way for me to achieve the behaviour I am looking for (i.e. searchParams being passed as a map and not a string to my controller)?
Edit:
Here is the relevant gsp code for my search form:
<g:form url="[action:'index']" method="PUT" >
<fieldset class="form">
<div class="fieldcontain">
<label for="channelName">
<g:message code="alarm.channelName.label" default="Channel Name" />
</label>
<g:textField name="searchParams.channelName" value="${searchParams?.channelName}"/>
</div>
</fieldset>
<fieldset class="buttons">
<g:actionSubmit class="list" action="index" value="${message(code: 'default.button.list.label', default: 'List', args: [entityName])}" />
</fieldset>
</g:form>
and here is my index action:
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
def alarmCriteria = new DetachedCriteria(Alarm)
def channelInterfaceInstances = ChannelInterface.list();
if(params.searchParams?.channelName != null && params.searchParams?.channelName != "" && params.searchParams?.channelName != []) {
alarmCriteria = alarmCriteria.and {
like("channelName", "%${params.searchParams?.channelName}%")
}
}
def results = alarmCriteria.list(params) {}
respond results, model:[alarmInstanceCount: results.totalCount, searchParams: params.searchParams]
}
Related
I'm wondering, in the scaffold controller and views, how the fields you fill in the "create" page get updated to your domain class instance before the save action. I'm on Grails 2.4.4.
Take an example, I have one class called Customer and I generate the controller and views all in the default way.
class Customer {
String name;
String email;
String address;
String mobile;
}
And when you run the application and in the generated green-styled index page, click on "create new customer", one Customer instance will be created as the link goes to "create" action.
<ul>
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
</ul>
In controller:
def create() {
log.info "A customer object is created here.";
Customer c=new Customer(params)
respond c
}
But now you haven't filled in the form on all the fields yet! And after you fill in the form in create.gsp, the link will point you directly to "save" action.
<g:form url="[resource:customerInstance, action:'save']" >
<fieldset class="form">
<g:render template="form"/>
</fieldset>
<fieldset class="buttons">
<g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
</fieldset>
</g:form>
But in the save action I see nothing related to setting the fields on this instance as the form goes. Where is it done?
#Transactional
def save(Customer customerInstance) {
if (customerInstance == null) {
notFound()
return
}
if (customerInstance.hasErrors()) {
respond customerInstance.errors, view:'create'
return
}
customerInstance.save flush:true
//omit everything after save here
}
Grails does this for you automatically with Data Binding. Grails controllers can take two categories of arguments: basic objects, and complex objects. The default behavior is to map HTTP request parameters to action arguments by name, wherever applicable. For example, say we have a controller like so:
def doSomething(Integer magicNumber) {
println "The magic number is $magicNumber"
}
and a view that contains a field like this:
<g:textField name="magicNumber" value="${magicNumber}" />
When the form is submitted to the doSomething action, Grails will automatically take the magicNumber request param, convert it from a String to an Integer and pass it to the action.
Complex types (like domain objects) are treated as command objects. Command objects function in a similar manner to basic objects in regards to data binding. Notice how in your code, the save action takes a Customer instance as an argument? Behind the scenes, Grails takes the HTTP request parameters and binds those parameters to the properties of a given object, in this case, a Customer. From the documentation:
Before the controller action is executed Grails will automatically create an instance of the command object class and populate its properties by binding the request parameters.
In other words, Grails will look at all of the incoming request parameters and attempt to bind those to properties on the object that you've stated as an argument to the action. In addition, it will perform validation for you. It's essentially the same as:
#Transactional
def save() {
Customer customerInstance = new Customer(params)
customerInstance.validate()
if (customerInstance == null) {
notFound()
return
}
if (customerInstance.hasErrors()) {
respond customerInstance.errors, view:'create'
return
}
customerInstance.save flush:true
//omit everything after save here
}
In my application, I have search button and based on the search criteria, I have to search data. I have used few hidden variables to store data.
My .gsp page looks like this:
<g:form id="job" name="job" method="POST">
<div>
<input type="hidden" id="country" name="country" value="${country}"/>
<input type="hidden" id="paginationFlag" name="paginationFlag" value="${paginationFlag}"/>
<!-- There are other components like text field, select box etc -->
<g:actionSubmit id="Search" value="Search" formmethod="post" action="findJob"/>
</div>
</g:form>
My respective controller method looks like this:
def findJob(){
def country
def paginationFlag
if(params?.country){
country = params?.country
}else{
country = 'USA'
}
if(params?.paginationFlag){
paginationFlag = params?.paginationFlag
}else{
paginationFlag = 'false'
}
withFormat{
html{
List<Applicant> searchList //get data from database.
// other business logic
render(view : "jobList",model:[paginationFlag: paginationFlag, country:country])
}
json{
// some business logic
def candidateList // value for this candidateList is acquired from database
def json = ['jsn': candidateList]
render json as JSON
}
}
When I click search button and debug the code, first my control goes to controller-> findJob() method and executes the code inside html part.
Secondly, it goes to the gsp page (view page) and sets value.Third, it again goes to the controller and executes the code inside json part.
On the first entry to the controller, the value of paginationFlag and country in param are both null. So it will set the value 'false' and 'USA' respectively. But when control goes to controller again for the second time, again the value of param.paginationFlag and params.country are null. Should not they have the assigned values?
Why is it so? What should I do go get the value of country and paginationFlag in params on the second time? Can any one explain me ? Thank you very much for advance.
The problem is you're using a regular HTML input tag rather than a Grails g:hiddenField tag. Try this:
<g:form id="job" name="job" method="POST">
<div>
<g:hiddenField name="country" value="${country}"/>
<g:hiddenField name="paginationFlag" value="${paginationFlag}"/>
<!-- There are other components like text field, select box etc -->
<g:actionSubmit id="Search" value="Search" formmethod="post" action="findJob"/>
</div>
</g:form>
Tip
You can also simply the params assignment:
def country = params.country ?: 'USA'
def paginationFlag = params.paginationFlag ?: 'false'
I can't tell where the paginationFlag comes from, but know that a boolean is valid.
I have a domain class TmMessage for which I use generate-all to create the scaffolded controller and views. The auto-generated show() method looks like:
def show(TmMessage tmMessage) {
respond tmMessage
}
Scaffolding is defined in my BuildConfig.groovy:
plugins {
compile ":scaffolding:2.1.2"
}
The list of TmMessage objects is given by controller's method:
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond TmMessage.list(params), model:[tmMessageCount: TmMessage.count()]
}
The TmMessages are stored in a hasMany List of a parent object, TmBulkMessage, and I can see the TmMessages listed ok in when inspecting a TmBulkMessage. However, the list of TmMessage objects displays nothing (I can see a number of pages of TmMessage objects, but the details for them don't display). When I click on one of the links from the TmBulkMessage to look at a specific TmMessage object, nothing displays. I believe that's because the tmMessage being displayed is null.
The show() method is very different to what I've seen elsewhere, where it looks like (taken straight from Grails docs):
def show() {
def book = Book.get(params.id)
log.error(book)
[bookInstance : book]
}
The auto-generated unit tests all use the first method, so what's going on here please? Is there something missing from the scaffolded code?
EDIT:
From the Grails docs, what's new in 2.3 (I'm using 2.4):
Domain Classes As Command Objects
When a domain class is used as a
command object and there is an id request parameter, the framework
will retrieve the instance of the domain class from the database using
the id request parameter.
So it would appear that the domain class / command object interface provided by Grails is returning null.
FURTHER EDIT:
Thanks to Gregor's help, it would appear that the domain object binding is working ok, but that the respond isn't working as advertised.
The show.gsp is below:
<%# page import="com.example.TmMessage" %>
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main">
<g:set var="entityName" value="${message(code: 'tmMessage.label', default: 'TmMessage')}" />
<title><g:message code="default.show.label" args="[entityName]" /></title>
</head>
<body>
<g:message code="default.link.skip.label" default="Skip to content…"/>
<div class="nav" role="navigation">
<ul>
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
</ul>
</div>
<div id="show-tmMessage" class="content scaffold-show" role="main">
<h1><g:message code="default.show.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<ol class="property-list tmMessage">
<g:if test="${tmMessage?.bulkMessage}">
<li class="fieldcontain">
<span id="bulkMessage-label" class="property-label"><g:message code="tmMessage.bulkMessage.label" default="Bulk Message" /></span>
<span class="property-value" aria-labelledby="bulkMessage-label"><g:link controller="tmBulkMessage" action="show" id="${tmMessage?.bulkMessage?.id}">${tmMessage?.bulkMessage?.encodeAsHTML()}</g:link></span>
</li>
</g:if>
<g:if test="${tmMessage?.message}">
<li class="fieldcontain">
<span id="message-label" class="property-label"><g:message code="tmMessage.message.label" default="Message" /></span>
<span class="property-value" aria-labelledby="message-label"><g:fieldValue bean="${tmMessage}" field="message"/></span>
</li>
</g:if>
</ol>
<g:form url="[resource:tmMessage, action:'delete']" method="DELETE">
<fieldset class="buttons">
<g:link class="edit" action="edit" resource="${tmMessage}"><g:message code="default.button.edit.label" default="Edit" /></g:link>
<g:actionSubmit class="delete" action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />
</fieldset>
</g:form>
</div>
</body>
</html>
The output of tmMessage?.dump() within show() is:
<com.example.TmMessage#6d6cf0a5 message=abc errors=grails.validation.ValidationErrors: 0 errors $changedProperties=null id=1 version=0 bulkMessage=com.example.TmBulkMessage : 1>
If I amend the gsp to read:
<ol class="property-list tmMessage">
<% System.out.println "tmMessage : " + tmMessage %>
Then I get "tmMessage : null" written to the console when I view the page.
I have changed show() to read:
def show(TmMessage tmMessage) {
respond tmMessage, [model: [tmMessage : tmMessage]]
}
Which appears to fix the rendering issue for show. I don't know what needs to be changed for index(). When I select "edit" from the show page, I get a blank textfield for the message field and I don't know if this is expected behaviour or not, but it would be much preferred if the field was preloaded with the existing value.
I think I know now what the problem is: respond has a really weird variable naming convention. If you respond with a single TmMessage instance, the variable will be called tmMessageInstance in the view. If you respond with a list of them, the variable will be called tmMessageInstanceList. If you return a set... well, you know what I mean.
So in the GSP code above you can probably replace all tmMessage with tmMessageInstance and get rid of [model: [tmMessage : tmMessage]] in the controller. A habit of mine is to explicitly test for the presence and type of every expected model variable in every single GSP I write, like so: <% assert tmModelInstance instanceof com.package.TmModel %>. Those lines then serve as documentation and if the controller passes something unexpected into your GSP (can happen frequently during active development, especially when filling the data model from services), your code fails quite obviously with a nice diagnostic message.
In my opinion a better option for Grails would be to stick to a single variable for respond renderers (e.g. model), document it in several places just so nobody misses this, and then people can detect what was in there when necessary (how often does it happen anyway that you don't know if you will have a list or a single instance for a single view/template?).
EDIT: Apparently you can use the Map syntax with respond and have it be used as the model to get fixed variable names, it was just poorly documented: https://github.com/grails/grails-doc/commit/13cacbdce73ca431619362634321ba5f0be570a1
With thanks to Gregor, whose help put me on the right track, the issue is with the generated code. It would appear that there is not a model being passed to the view, hence it's rendering nothing. Below are the changes to index(), show() edit()
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond TmMessage.list(params), model:[tmMessageCount: TmMessage.count(), tmMessageList : TmMessage.list(params)]
}
def show(TmMessage tmMessage) {
respond tmMessage, [model: [tmMessage: tmMessage]]
}
def edit(TmMessage tmMessage) {
respond tmMessage, [model: [tmMessage: tmMessage]]
}
This preloaded the text fields with the correct values.
I also had to amend the parameters sent when there was an error when creating by passing the model along with the desired view. Below is the example for save():
#Transactional
def save(TmMessage tmMessage) {
if (tmMessage == null) {
notFound()
return
}
if (tmMessage.hasErrors()) {
respond tmMessage.errors, [view:'create', model: [tmMessage: tmMessage]]
return
}
tmMessage.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: 'tmMessage.label', default: 'TmMessage'), tmMessage.id])
redirect tmMessage
}
'*' { respond tmMessage, [status: CREATED] }
}
}
This was happening me for when I had inheritance in my domain model.
For instance, if we have
class Vehicle {}
and
class Car extends Vehicle {}
The scaffolded controller action was passing carInstanceList into the view when the view was trying to render vehicleInstanceList.
As stated in previous answers, the respond method creates variable names by convention, the convention seems to fail here
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond Vehicle.list(params), model:[vehicleInstanceCount: Vehicle.count()] //actually injecting carInstanceList
}
Had to be changed to :
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
def vehicles = Vehicle.list(params)
respond vehicles, model:[vehicleInstanceCount: Vehicle.count(), vehicleInstanceList:vehicles]
}
I think it is to do with checking the class of the first element in the list maybe and if that is a car, naming it carInstanceList, if the first was a vehicle, the issue probably wouldn't present itself
I'm a newbie trying to find uses for Neo4J on Grails.
Basically, I've made 20 grocery item nodes through the Neo4J browser and I want to create a simple Grails site that will let users search a grocery item and visually show the items related to it.
My index.gsp has:
<input id="item" />
My viz.js has:
$('#item').keyup(function() {
var item = $('#item').val();
My Item Domain class has
class Item {
static mapWith = "neo4j"
String name
My ItemController class has:
def index() {
def item = Item.list() [item:item] //No idea, just trying out whatever i find :(
and a query with something like:
def query = Item.cypherStatic ("""start n=node({Item}) match (n)-[r]->(x) where r='partner' return n, x)
Questions:
How can I properly send the JS 'item' variable into the ItemController?
How can I use the 'item' variable to properly query the node names which have a 'partner' relationship with the item?
in addition to Motilals answers, you definetly need a wrapping form with an action that points your controller
like
<g:form controller="itemController" action="index" >
<input type="text" id="item" name="item" value="" />
<input type="submit" value="submit" >
</g:form>
then on clicking submit the for will call your index action and there you could parse the value with
def item = params.item
but it looks more like you want some asynchronous stuff right after keyup-function, therefore you could do sth like this :
$('#item').keyup(function() {
var item = $('#item').val();
$.ajax({
url: "${createLink(controller:'itemController', action:'index')}",
data:"&item="+item
})
.done(function( data ) {
console.log(data)
});
});
in this case, you need to pay attention what your index-action is returning, so you can do in the .done() whatever you want with the response.
also note, that when you name an action "index" it will be available at
.../myproject/item/index
or, and thats important
.../myproject/item/
so if your index method requires the data from the input, it will miss them if a user has gone straight to that url
so your index action would rather render the page with the input
and you define another action for executing your query based on input and returning data
set the item to hidden field and then you can access it directly in your controller using params
here you go:
//in index.gsp add below hidden field and set the hidden filed in your js code
<g:hiddenField name="item" value="" />
$('#item').keyup(function() {
var item = $('#item').val();
//in your controller
def index() {
def item = params.item
print item // you see the value for item
//do your stuff
}
once you have item value you could directly use HQL query or use the domain instance
hope this helps you
Regards
Motilal
I have been stuck for a couple of days on the same problem and I am not getting anywhere.
I am using a g:formRemote tag to update a messages template
<g:formRemote url="[action: 'updateStatus']" update="messages" name="updateStatusForm"
onSuccess="document.updateStatusForm.message.value='';">
<g:textArea name="message" value="" cols="3" rows="1"/><br/>
<g:submitButton name="Update Status"/>
</g:formRemote>
This form then calls a updateStatus method in my controller
def updateStatus(String message) {
def status = new Post(message: params.message, author: lookupPerson())
status.save(flush: true, failOnError: true)
def messages = currentUserTimeline()
render template: 'profileMessages', collection: messages, var: 'profileMessage'
}
But the def status line should be
def status = new Post(message: params.message, author: lookupPerson(), child: Child.get(childInstance.id)
In my view I have a childInstance which is the selected by the user. Is there anyway I can have a global variable in the controller which is saved or can I send the child instance params id in the formRemote tag. I don't think there is as i've been trying to do it this way all today.
Any help will be good. The only way I know will work will be to write the childInstance.id to a properties file and read it back in but there must be a better way as I think this would be very bad practice.
You can add params to <g:formRemote>
Example from the grails docs:
<g:formRemote name="myForm" update="updateMe"
url="[controller: 'book', action: 'byAuthor', params: [sort: 'title', order: 'desc']]">
Author: <input name="author" type="text" />
</g:formRemote>
You could probably add a <g:hiddenField name="childId" value="${childInstance.id}"/> to your formRemote Tag and this will send it to the server in the same way message is.