In my Grails controller I'm responding to an AJAX call and using render to return the text:
def ajaxRandomPersonName = {
def person = get a random person ...
render "Name: ${person.name}"
}
The problem is that render renders the whole template. So instead of just rendering "Name: John" it renders all the icons, navigation, etc defined in the template. How do I get render to just render without the template?
I'm pretty much following Chapter 1 of "Grails in Action" (page 28) using Grails 1.1.1.
Follow up:
Returning false per Rhysyngsun's suggestion has no impact. I also tried setting the template to null but it still renders the template:
def ajaxRandomPersonName = {
def person = get a random person ...
render (template:null, text:"Name: ${person.name}")
}
render has its heart bent on rendering it through the template no matter what I do.
Follow up 2: Parallel discussion on grails-user mailing list.
Follow up 3: Sample code:
I paired down my code the bare minimum and it still exhibits the undesired template rendering.
controllers/PersonController.groovy:
class PersonController {
def index = { }
def home = { [message:"Hello"] }
def ajaxTest = {
println "ajaxTest called"
render text: "ajax message"
}
}
views/person/home.gsp (view page for home method)
<html>
<head>
<title>Home View</title>
<g:javascript library="prototype" />
</head>
<body>
<p>
<g:remoteLink action="ajaxTest" update="test1">ajax call</g:remoteLink>
</p>
<p>Message = ${message}</p>
<p id="test1">Blank</p>
</body>
</html>
views/layouts/person.gsp (layout template for person controller)
<html>
<head>
<title>Test App - <g:layoutTitle/></title>
<g:layoutHead/>
</head>
<body>
<h1>Test App</h1>
<g:layoutBody/>
</body>
</html>
I access person controller with the home view:
http://localhost:8080/test/person/home
the page renders as:
Test App
ajax call (hyperlink)
Message = Hello
Blank
"Test App" is from the template. When I click "ajax call" it makes an asynchronous call to PersonController's ajaxTest method (verified with println). All ajaxTest does is println and render static text. This resultant in the following:
Test App
ajax call
Message = Hello
Test App
ajax message
Note that the template is being rendered within "test1" <p> which results in the second "Test App".
I'm running Grails 1.1.1. Any ideas? The code seems straightforward. I downloaded the Grails source and looked at RenderDynamicMethod.java. It doesn't do any template rendering unless template is in the argument list, which it isn't. So my only guess is something up steam is rendering the template again.
Resolved: Adding contentType results in the template not being rendered:
render text: "Name: ${person.name}", contentType: "text/plain"
Make your client side javascript code handle a JSON respond and render your response with:
render [text:"Name: ${person.name}"] as
JSON
You might be getting burnt by the 'layout-by-convention' feature in Grails. If your layout name matches the controller name prefix, for example, Grails will apply the layout to every view managed by that controller. Unfortunately, it even applies to text and templates. There are currently a few JIRAs logged regarding this (see http://jira.grails.org/browse/GRAILS-7624 for example).
I got burnt by this today. I resolved it by simply renaming my layout gsp such that it doesn't match any controller name. My layout was initially named 'storefront.gsp' and I have a controller named StorefrontController. I renamed the layout to 'public.gsp'.
We've found that explicitly returning false from the action fixes this.
I believe doing render foo as JSON returns false implicitly.
Related
In my controller, I've got my show action using a different layout (e.g., foo.html.erb) than my application.html.erb. foo.html.erb has styles loading directly in the <head>, for example:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">
{styles here}
</style>
</head>
<body>
<%= yield %>
</body>
</html>
Here is the controller code for how I'm resolving the layout:
class FoosController < ApplicationController
layout :resolve_layout
def show
end
private
def resolve_layout
case action_name
when 'show'
'foo'
else
'application'
end
end
end
The problem is, the application seems to cache the templates too heavily, and when I click through to a view that should be using the show template, I don't see those <head> styles without a refresh.
Am I handling this correctly, or is there a more "rails way" of doing this? What I'm really trying to accomplish here is having styles unique to a particular template only load with that single template. I think this may be an asset pipeline question, but I'm not sure, as I'm still new to Rails.
Seems like it was an issue with the styles living in the head. I had a relatively simple page, so I was able to move them to be inline styles, and the problem seems to be solved.
Happy new year to all,
I am working on a project where I have to show the details of each record in the "list" in a dialog (modal window) window while the user click the link on each record in the list. I am trying to accomplish this using the GrailUI plugin. Here is my code:
<gui:dialog width="300px"
controller="tag"
action="showTags"
params=" [id:userInstance.userId]"
update="dialogData"
draggable="true"
triggers="[show:[type:'link', text:'Show Tags', on:'click']]"
modal="true">
<div id='dialogData'>This will be updated by the controller action....</div>
For some reason the dialog tag is not firing the controller action. It opens the dialog window, but shows just this message "This will be updated by the controller action....". It's not showing the controller action rendered output(view). Could someone help me to understand what I am doing wrong?
Jquery and jquery-ui are the other plugins I am using in my project.
Appreciate your help.
Edit
def test(Integer max) {
....
....
userInstanceList = User.list(params)
render (view: "test", model: [userInstanceList: userInstanceList, userInstanceTotal: User.count()])
}
def showTags () {
def user = User.findByUserId(params.id)
def tagInstanceList = user.tags
render(view: "test", model: [tagInstanceList: tagInstanceList])
}
If you want something be submit to remote you need to set form="true". Then any form elements can be placed inside the dialog tag without defining a form. When form="true", the dialog creates its own form.
Here is a example I have tested:
test.gsp:
<html>
<head>
....
<r:require modules="grailsui-dialog"/>
</head>
<body class="yui-skin-sam">
<gui:dialog width="300px"
controller="test"
action="showTags"
params=" [id:userInstance.userId]"
form="true" <!-- the key to remote submit -->
update="dialogData"
draggable="true"
triggers="[show:[type:'link', text:'Show Tags', on:'click']]"
modal="true" >
<!-- You can put any input element here, which will be submitted in the form-->
<div id='dialogData'>This will be updated by the controller action....</div>
</gui:dialog>
</body>
</html>
TestController:
class TestController {
def test() {
.........
}
def showTags() {
def user = User.findByUserId(params.id)
def tagInstanceList = user.tags
render(template: "ajaxResponse", model: [tagInstanceList: tagInstanceList, user:user]) //render a template, not a view
}
For an Ajax request, you can not render a view, which will replace the original page. Instead, you should send back a template with the tags you want to show in the dialog:
_ajaxResponse.gsp
<h3>Tag List of user ${user.username}</h3>
<g:each in="${tagInstanceList}" var="tag">
<p>${tag}</p>
</g:each>
I am new to Grails and I am trying to get a very simple example to work. I should just submit a form and display "Hello World" on the screen. It consists of the following controller:
package surface
class SearchController {
def index() {
render(view: "search")
}
def result() {
render "Hello World"
}
}
and a view, with the form:
<%# page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<g:form name="searchform" url="result">
<g:textArea cols="80" rows="30" name="searchfield"/>
<g:actionSubmit value="Ask"/>
</g:form>
</body>
</html>
When I click on "Ask" I get a 404 error but the browser correctly accesses "/surface/search/result". When I enter that address directly without using the form the "Hello World" appears correctly. This is probably a no-brainer but I seem to be unable to find out why this does not work from the documentation.
Complementing the #Tom Metz answer, what you need to keep in mind in the Grails controller structure is that every public method is considered an action. This action is mapped to a url. In your example will exists /search/index and /search/result (controller + action).
The documentation of the g.form is corret, since this says that;
url (optional) - A map containing the action,controller,id etc.
So to correct your view you can set the action as commented or you can adjust the way you use url:
<g:form name="myForm" url="[action:'result',controller:'search']">
Is there a way to use render() with a fragment param so on page load it automatically scrolls to a specific part of the page? Similarly to how we can call
redirect(controller: "book", action: "show", fragment: "profile")
You cannot pass it to render(), because by the time you're actually invoking render(), the URL has already been determined and mapped to your action; all render is doing is controlling what gets written back to the response.
The fragment must already be in the URL before the rendering controller action gets called. Here's an example:
grails-app/controllers/MyController.groovy
class MyController {
def foo = {
render(view: 'foo')
}
def quux = {
redirect(action: 'foo', fragment: 'baz')
}
}
grails-app/views/my/foo.gsp
<html>
<head>
<title>Foo</title>
</head>
<body>
<a id="bar">Bar</a>
<g:each in="${0..100}"><br/></g:each>
<a id="baz">Baz</a>
</body>
</html>
With various URLs:
http://example.com/myapp/my/foo - doesn't scroll to an anchor
http://example.com/myapp/my/foo#baz - scrolls to the 'baz' anchor
http://example.com/myapp/my/quux - scrolls to the 'baz' anchor'
There is no way to specify a fragment directly with a grails render call, but in my code I'm using a work around that seems to provide most of the desired functionality with only a little extra complexity. The trick is to pass in the desired fragment reference as a part of the model, and then to act upon that reference in the GSP page. Thus, the render call in my controller looks like this:
def foo() {
render(view : 'foo', model:[fragment:'myFragment'])
}
then, down in the GSP, I access the model with the following Javascript:
<g:javascript>
if ("${fragment}") {
window.location.href = "#${fragment}";
}
</g:javascript>
The GSP will then tell your browser to jump forward to the desired anchor in the page (if any).
I am trying to create a simple portlet for Liferay 5.2.2 using Grails 1.2.1 with the grails-portlets 0.7 and grails-portlets-liferay 0.2 plugins.
I created and deployed a stock portlet (just updated title, description, etc...). It deploys correctly and the view renders correctly. However, when I submit the default form that is in view.gsp it never hits the actionView function.
Here are the relevant code bits:
SearchPortlet.groovy
class SearchPortlet {
def title = 'Search'
def description = '''
A simple search portlet.
'''
def displayName = 'Search'
def supports = ['text/html':['view', 'edit', 'help']]
// Liferay server specific configurations
def liferay_display_category = 'Category'
def actionView = {
println "In action view"
}
def renderView = {
println "In render view"
//TODO Define render phase. Return the map of the variables bound to the view
['mykey':'myvalue']
}
...
}
view.gsp
<%# taglib uri="http://java.sun.com/portlet" prefix="portlet" %>
<div>
<h1>View Page</h1>
The map returned by renderView is passed in. Value of mykey: ${mykey}
<form action="${portletResponse.createActionURL()}">
<input type="submit" value="Submit"/>
</form>
</div>
The tomcat terminal prints In render view whenever I view the portlet, and after I press the submit button. It never prints the In action view statement.
Any ideas?
Update
I turned on logging and this is what I see whenever I click the submit button in the portlet:
[localhost].[/gportlet] - servletPath=/Search, pathInfo=/invoke, queryString=null, name=null
[localhost].[/gportlet] - Path Based Include
portlets.GrailsDispatcherPortlet - DispatcherPortlet with name 'Search' received render request
portlets.GrailsDispatcherPortlet - Bound render request context to thread: com.liferay.portlet.RenderRequestImpl#7a158e
portlets.GrailsDispatcherPortlet - Testing handler map [org.codehaus.grails.portlets.GrailsPortletHandlerMapping#1f06283] in DispatcherPortlet with name 'Search'
portlets.GrailsDispatcherPortlet - Testing handler adapter [org.codehaus.grails.portlets.GrailsPortletHandlerAdapter#74f72b]
portlets.GrailsPortletHandlerAdapter - portlet.handleMinimised not set, proceeding with normal render
portlet.SearchPortlet - In render view
portlets.GrailsPortletHandlerAdapter - Couldn't resolve action view /search/null.gsp
portlets.GrailsPortletHandlerAdapter - Trying to render mode view /search/view.gsp
portlets.GrailsDispatcherPortlet - Setting portlet response content type to view-determined type [text/html;charset=ISO-8859-1]
[localhost].[/gportlet] - servletPath=/WEB-INF/servlet/view, pathInfo=null, queryString=null, name=null
[localhost].[/gportlet] - Path Based Include
portlets.GrailsDispatcherPortlet - Cleared thread-bound render request context: com.liferay.portlet.RenderRequestImpl#7a158e
portlets.GrailsDispatcherPortlet - Successfully completed request
The fourth line in that log snippet says Bound render request..., which I don't understand because the action in the form that is in the portlet is to the action url. I would've thought that should be an action request.
I have this same issue and it would be very nice to get it working.
UPDATE
I added method="post" to the form and it worked like a charm :)