I'm converting a grails 2.4.4 app to 3.1.8.
In some of my controller methods sometimes (based on what happened in the service call) I'll just set a message in the flash and nothing else, this was fine in 2.4.4, the screen would be re-rendered with the flash message but in 3.1.8 nothing is rendered at all, the screen in totally blank.
Seemingly if I add a statement after setting the message in the flash the screen is rendered, the statement can be anything e.g. println 'hello' or return or new ModelAndView().
Example below:
def index() {
def res = myService.whatever()
if (res) {
[res: res]
}
else {
flash.message = message( code: 'no.res' ) // if we get here nothing is rendered
}
}
Is this a change to grails 3 or am I missing something somewhere?
Thanks
Try doing this
def index() {
def res = myService.whatever()
[res: res]
}
Then in the index.gsp view
<g:if test="${res}">
<!-- cool thing goes here -->
</g:if>
<g:else>
<g:message code="no.res"/>
</g:else>
Related
I have written an grails application, In UrlMappings.groovy file I have url mapping as
"500"(controller:"exception" , action:"internalServerError")
, so whenever any exception occurred I getting the exception in the ExceptionController but I need the payload and the url which throwed this exception , Is it possible to get that data.
Hope this helps. This code is straight from default Grails installation
<g:if test="${Throwable.isInstance(exception)}">
<g:renderException exception="${exception}" />
</g:if>
<g:elseif test="${request.getAttribute('javax.servlet.error.exception')}">
<g:renderException exception="${request.getAttribute('javax.servlet.error.exception')}" />
</g:elseif>
<g:else>
<ul class="errors">
<li>An error has occurred</li>
<li>Exception: ${exception}</li>
<li>Message: ${message}</li>
<li>Path: ${path}</li>
</ul>
</g:else>
in your exceptionController (when a 500 happened) you should be able to access the original URL via
request['javax.servlet.error.request_uri']
additionally you can get the requested URL in every controller via
request[RequestDispatcher.FORWARD_REQUEST_URI]
for accessing the request body after it has been consumed, you could use a solution as suggested in
Accessing the raw body of a PUT or POST request but bear in mind that this of course has to keep the body in memory.
to get the originally called controller and action name inside your exceptionController, the only solution I know right now would be either:
class ExceptionController {
def grailsUrlMappingsHolder
internalServerError() {
// request['javax.servlet.error.request_uri'] sometimes returns null?
def url = request['javax.servlet.error.request_uri'] ?: request[RequestDispatcher.FORWARD_REQUEST_URI]
def originalCall = url ? grailsUrlMappingsHolder.match(request['javax.servlet.error.request_uri'])?.paramss : [:]
def controller = original?.controller
def action = original?.action
...
}
}
alternatively, by saving the first controller call in a filter like that:
class SaveFirstCallFilter {
def filters = {
all(controller:'*', action:'*') {
before = {
// don't want to overwrite when forwarding or including other actions
if (!request['SAVED_CONTROLLER']) {
request['SAVED_CONTROLLER'] = controllerName
request['SAVED_CONTROLLER'] = actionName
}
}
}
}
}
So I've just started using Grails/Groovy for my senior capstone project and I'm having a bit of a rocky start. The issue I'm having is when I set up a mapping of my model in the controller I can loop through it and print it to the console but when I try and access it from the view I get blank page.
The Domain class is simple:
package ot
class CrewDeskMapping {
String crewDesk
String desk
}
And the Bootstrap file that creates the test data:
...
new CrewDeskMapping(crewDesk:"North",desk:"MON1").save()
new CrewDeskMapping(crewDesk:"North",desk:"TWI1").save()
new CrewDeskMapping(crewDesk:"North",desk:"NWE1").save()
...
Here is my controller:
import ot.CrewDeskMapping;
class DashboardController {
def index() {
def desks = CrewDeskMapping.list()
[desks:desks]
for (d in desks) {
System.out.println(d.desk);
}
}
}
and the console output looks as it should:
MON1
TWI1
NWE1
CHI1
COL1
...
And the relevant part of my index.gsp
<body>
<g:each in="${desks}">
<p>Title: ${it.crewDesk}</p>
<p>Author: ${it.desk}</p>
</g:each>
</body>
The most perplexing part is if I try this same code but with a different Domain it works just fine. I've been at this problem for a couple days now with no avail so any help would be greatly appreciated!
def desks = CrewDeskMapping.list()
[desks:desks]
for (d in desks) { ...
does not return your [desks: desks]
Swap the for and the [desks: ...] or add a proper return statement at the end like this:
def index() {
def desks = CrewDeskMapping.list()
desks.each{ log.debug it.desk } // groovier debug print
// must be last in the method like a regular return in java,
// but groovy allows implicit return of the last statement
/*return*/ [desks:desks]
}
This is the main login page of my application:
Once you're logged in, you see this page:
Now on this page if you press the 'Choose/Upload' button (circled in blue), you get a template rendered in the middle part of the page and then it looks like this:
If you wait a while such that your session becomes null, then I want the 'Choose/Upload' button to redirect back to the login page, which it does but things look like this:
This is the controller function associated with the 'Choose/Upload' button:
def chooseupload = {
if (session.user == null) {
redirect(action: customerLogin)
}
else {
def batchList = (Batch.findAllWhere(userId: session.user.id.toLong(), [sort: "lastUpdate", order: "desc"]))
render(template: 'chooseupload', model: [batchList: batchList, batchCount: batchList.size()])
}
}
and this is the code of the login action:
def customerLogin = {
} //just renders the view customerLogin.gsp
Any advice greatly appreciated. I am happy to provide more relevant code if needed.
you seem to be calling chooseupload in an AJAX request. If you call redirect in the controller, the browser gets the fully decorated (with header & footer) page back. In order to be able to differentiate between AJAX/noAJAX calls, I'm using the following code:
request.xhr ? render( template:'customerLogin' ) : redirect( action:'customerLogin' )
I wrote a plugin to inject a method into all controllers, I wrote this on doWithDynamicMethod {ctx -> }
for (classes in org.codehaus.groovy.grails.commons.ApplicationHolder.application.controllerClasses){
def controllerClass = classes.clazz
controllerClass.metaClass.static.doTestSearch << {args ->
println "this is dynamic insertion -->"+args.toString()
}
classes.class.metaClass.doTestSearch << {args ->
println "this is dynamic insertion -->"+args.toString()
}
}
When i tried to call this method in form in view, by :
<g:form method="POST">
<g:actionSubmit class="save" action="doTestSearch"
value="${message(code: 'default.button.search.label', default: 'Search')}" />
The method doesn't called and returning a 404 error.
But when i tried to call it from controller by :
doTestSearch(params)
It works.
Can someone please explain why is this happening ? And can i call the dynamic method directly from view ?
Thank you in advance
Try this
application.controllerClasses.each {controller ->
controller.metaClass.doTestSearch = {
//Your action code here
}
}
BTW - you can use DynamicController plugin also for adding actions to controllers
It looks like grails can't map the URL for the methods added at runtime but I haven't tested it.
I'm learning Grails so forgive me if I'm missing something basic.
I'm trying to create a wizard/web flow using the Grails Web Flow plugin. I'd like for the first step of the flow to render some variables. From what I've read about normal controllers, typically this would be done by passing those variables from the controller to the view using a map. In the WebFlow model though, I don't know where to initialize these variables and how to pass them to the first step. I tried creating an initialize "action" and putting the variable into the flash scope, knowing that it should pass through one redirect, but it doesn't render on the gsp.
How is this done?
Here's a snip of the controller, which prints "4" in the console:
class ServicesController {
def index() {
redirect(action: "initialize")
}
def initialize() {
flash.assessmentTypes = AssessmentType.list()
println flash.assessmentTypes.size
redirect(action: "request")
}
def requestFlow = {
selectAssessments {
on("next") {
// capture assessments
}.to("productInfo")
on("cancel").to("finish")
}
...
And a snip of the gsp, which throws a nullpointer when rendering the size:
${flash.assessmentTypes.size}
<g:each var="assessmentType" in="${flash.assessmentTypes}">
<li><g:checkbox name="assessmentType" value="${assessmentType.id}" />${assessmentType.name}</li>
</g:each>
No problem...
Use a flow initializer to act as the first step in the flow and then move it to the first step on success of the initFlow.
def wizardFlow = {
initFlow {
flow.assessmentTypes = AssessmentType.list(); //<-- put them in the flow so you can access it in step1.gsp
}
on('success').to('step1')
on(Exception).to('handleFlowError')
step1{
on('next'){
flow.asessmentType = AssessmentType.get(params.assessmentType.id);
println("They picked ${flow.asessmentType}.");
}.to('step2')
on('exit').to('exit')
}
step2{
on('next'){ /* do stuff */ }.to('finish')
on('previous').to('step1')
on('exit').to('exit')
}
exit( /* exit flow before finish */ )
finish( /* finish up */ )
handleFlowError( */ not good :( */)
}
step1 GSP....
<g:select name="assessmentType.id" from="${assessmentTypes}" optionKey="id" value="${assessmentType?.id}" />
This is untested but it should work just fine. Enjoy :)