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
Related
Grails 2.4.4 here. I currently have a GSP that displays a list of Widget objects like so:
// The Widget domain object
#Canonical
class Widget {
String title
String history
}
// The controller
class WidgetController {
def widgetService // Grails Service provided below
def index() {
def widgetType = params.widgetType
def model = widgetService.getAllWidgetsByType(widgetType)
render(view: 'index', model: model)
}
}
// WidgetService
class WidgetService {
def widgetDataService // Omitting for brevity; makes a call to a DB
def getAllWidgetsByType(widgetType) {
def model = [:]
def widgetList = widgetDataService.getWidgets(widgetType)
model.put('widgetList': widgetList)
model.put('foo', 'baz')
model.put('fizz', 'buzz')
model
}
}
// grails-app/views/widget/index.gsp
<!DOCTYPE html>
<html>
<head>
<!-- omitted for brevity -->
</head>
<body>
<div class="content">
<h2>Here are the widgets:</h2>
<g:render model="[providedWidgetList:widgetList]" template="/shared/widgetBlock"></g:render>
</div>
</body>
</html>
// grails-app/views/shared/_widgetBlock.gsp
<g:each var="widget" in="${providedWidgetList}">
<div id="widgetBlock">
<h3>${widget.title}</h3>
<span>${widget.history}</span>
</div>
</g:each>
So, to recap:
WidgetService pulls back a List<Widget> from a DB and plops that into a model map (along with) some other stuff
WidgetController feeds this model to a widget/index.gsp
widget/index.gsp renders a shared/widgetBlock template that generates a widgetBlock div tag for each widget in the returned List
So, if there are 3 widgets returned by the DB, the resultant HTML might look like:
<!DOCTYPE html>
<html>
<head>
<!-- omitted for brevity -->
</head>
<body>
<div class="content">
<h2>Here are the widgets:</h2>
<div id="widgetBlock">
<h3>Hello There, Little Widget!</h3>
<span>What a nice little widget.</span>
</div>
<div id="widgetBlock">
<h3>My Fair Widget</h3>
<span>The best there ever was.</span>
</div>
<div id="widgetBlock">
<h3>A Time of Dogs!</h3>
<span>It was a time of tradition. It was a time of Dogs.</span>
</div>
</div>
</body>
</html>
I now need to add a new property to the Widget, a favoriteFood property, so that now Widget looks like:
#Canonical
class Widget {
String title
String history
String favoriteFood // values might be 'Pizza', 'French Fries' or 'Ice Cream'
}
And now, in the UI, I need the widget list visually-grouped by favoriteFood, so that widgets who share the same favorite food appear in their own section like so:
<!DOCTYPE html>
<html>
<head>
<!-- omitted for brevity -->
</head>
<body>
<div class="content">
<h2>Here are the widgets:</h2>
<h3>Pizza</h3>
<hr/>
<div id="widgetBlock">
<h3>Hello There, Little Widget!</h3>
<span>What a nice little widget.</span>
</div>
<div id="widgetBlock">
<h3>My Fair Widget</h3>
<span>The best there ever was.</span>
</div>
<h3>Ice Cream</h3>
<hr/>
<div id="widgetBlock">
<h3>A Time of Dogs!</h3>
<span>It was a time of tradition. It was a time of Dogs.</span>
</div>
</div>
</body>
</html>
So in the above example, the first two widgets both have favoriteFood = 'Pizza' and the last widget alone loves Ice Cream.
To accomplish this, I need to group all the widgets (returned from the DB) according to favoriteFood, so something like this:
def widgetsByFavoriteFood = widgetList.groupBy { widget -> widget.favoriteFood}
However because of how the service returns a model map, and how the index.gsp invokes and renders the template, and because the template is expecting a list (not a map), I'm just not seeing where I would make these changes.
Very important: This is a gross simplification of my actual Grails app. There are several things I cannot change without enormous refactoring (which I really don't want to have to do):
I really can't change the fact that the WidgetService returns a model map
I really can't change the fact that the widget/index.gsp invokes the _widgetBlock
Anyone have any ideas?
You can make the change right in the GSP. I consider your case UI logic, so it makes sense to put it in the GSP.
Oops, that was an opinion ;)
<g:each var="entry" in="${widgetList.groupBy { widget -> widget.favoriteFood } }">
<!-- entry.key is the favorite food,
and entry.value is the list of widgets grouped under
the favorite food -->
</g:each>
Iterating over a Map, which is what groupBy() returns, produces Map.Entrys.
Tip:
You can build your model much more succinctly like this:
[widgetList: widgetList, foo: 'baz', fizz: 'buzz']
So you get a grossly simplified answer... :)
The service returns a map -- still a map, just the inner details of the widgetList elements are different (rather than a list of individual items, have it return a list of lists of items. Maybe extract the favorite food into a parent element.
[
widgetList:[
[
favoriteFood:'Pizza',
widgets:[
[
title: 'title1',
history: 'blah'
]
]
],
[
favoriteFood:'Eggses',
widgets:[]
]
],
foo: 'baz',
fizz: 'buzz'
]
Widget block has to change no? Not sure what your groupBy yields exactly but,
g:each var foodGroup in widgetList
output the foodGroup stuff: h3, hr
g:each var widget in foodGroup.widgets
output the widget div
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
}
I want to create taglibs to simplify a webpage that represents a user dashboard. There are 2 taglibs I want to create. The first is a generic panel that will be used elsewhere, the second is a more specific version of the panel.
The GSP is called from a controller with the following model:
def index() {
def timelineItems = [
[details: "Some text"],
[details: "Some other text"]
]
render(view: "index", model: [timelineItems: timelineItems])
}
My desired GSP looks like this:
<dash:panel panelClass="col-lg-8" panelTitle="Service History">
<p>Some text in the body here</p>
<dash:timelinePanel timelineItems="$timelineItems" />
</dash:panel>
I want to put my HTML code in templates to make editing it simpler, so I call render from inside the tag lib and pass a data model. The taglibs look like this (within a namespace called dash):
def panel = {attrs, body ->
out << render(template: "/dashboard/dashboardPanel", model:[panelClass: attrs.getAt("panelClass"), panelTitle: attrs.getAt("panelTitle"), body: body()])
}
def timelinePanel = { attrs ->
out << render(template: "/dashboard/dashboardTimelinePanel", model: [timelineItems: attrs.getAt("timelineItems")])
}
This is my generic panel template: it should render the data in the top divs and then simply render the body passed to it in the model:
<div class="${panelClass}">
<div class="panel panel-default">
<div class="panel-heading">
${panelTitle}
</div>
<div class="panel-body">
${body}
</div>
</div>
</div>
This is my timeline template: it should render the details of each of the timeline variables passed in the model by the controller.
<g:each in="${timelineItems}">
<p>${it.details}</p>
</g:each>
I'm expecting to see a div which includes something like:
Some text in the body here
<p>Some text</p>
<p>Some other text</p>
However, I get the following error:
Error evaluating expression [it.details] : No such property: details for class: java.lang.String
My map appears to be being converted to a string and I can't access the map. What am I doing wrong please? I want to define multiple tags which can be used inside one another. How do I do this?
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]
}
I'm starting with Grails and I don't know how should I face the following use case.
The app is about sports results prediction, so I have in my domain "Match" and "Prediction", and I want to have one view where the user can update all the predictions of matches that haven't been played yet.
So far I've defined a method in my "PredictionController" that searches all the already existing predictions of games that have to be played and generates new Prediction instances for any new Match with a date higher than now. I've created a view for that method and I'm getting correctly all the predictions that I should complete or update, and I've defined in my controller another method for the form sumbission (so I'm trying to resolve this in the same way that the 'create' and 'update' scaffolded methods work).
My question is, How can I access to all the Predictions modified by my view? How can I send all the predictions to my update method? Is it defining a hidden field with a variable containing all the collection?
This is the form in my GSP view:
<g:form action="savePredicctions">
<fieldset>
<g:each in="${predictions}">
<li>
<div>
${it.match.homeTeam}
<g:field name="${it.match}.homeGoals" type="number" value="${it.homeGoals}" />
</div>
-
<div>
<g:field name="${it.match}.awayGoals" type="number" value="${it.awayGoals}" />
${it.match.awayTeam}
</div>
</li>
</g:each>
</fieldset>
<fieldset class="submit">
<g:submitButton />
</fieldset>
</g:form>
You can use a command object to store the instances of Prediction.
#Validateable
class PredictionCommand {
//data binding needs a non-null attribute, so we use ListUtils.lazyList
List<Prediction> predictions = ListUtils.lazyList([], FactoryUtils.instantiateFactory(Prediction))
}
In your view, you need to control the index of your list, and send the attributes of Prediction to the controller:
<g:each in="${predictions}" status="i">
<g:textField name="predictions[$i].homeGoals" />
<g:textField name="predictions[$i].awayGoals" />
</g:each>
And in your controller you can use bindData() to bind params to your command:
class CommandController {
def save() {
PredictionCommand command = new PredictionCommand()
bindData(command, params)
println command.predictions
}
}