Refactoring Grails templates to expect maps instead of lists - grails

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

Related

how to correctly render controller output to GSP

in my grails application, I have a controller that reads a directory and returns a list of files in it.
My issue is that in my GSP (view) it is not matching the out put from the controller.
Here is my controller:
package frametest
import groovy.io.FileType
class Read_dirController {
def index() {
def list = []
def dir = new File("D:\\TestRepoLocal\\APIAutomation\\src\\test\\cucumber\\features")
dir.eachFileRecurse (FileType.FILES) { file ->
list << file
}
list.each {
println it.name
}
def found = []
dir.eachFileMatch(FileType.ANY, ~/.feature/) {
found << it.name
}
render(view: "index", model: [name:name])
}
}
here is my GSP (view):
<%# page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<meta name="layout" content="main"/>
<title>Welcome to The Marriot test Page</title>
</head>
<body>
<div class="body">
<h2>Marriott Automation Test Page</h2>
<br>
<p></p>
<h3>This is where we can launch our tests and get reports</h3>
<br>
<br>
<p></p>
${name}
</div>
</body>
</html>
The output should just list the file names. It does in the controller output (shown in console), but in the view it shows this:
[D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\APIWAL_525_Account_Security_otp.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\AssignRoomToReservation.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\DeleteAccount.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\DeleteConsumersPaymentMethods.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\GetActivitiesByID.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\GetAddressType.feature, D:\TestRepoLocal\APIAutomation\src\test\cucumber\features\GetAddrMiscType.feature, D:\TestRepo
Local\APIAutomation\src\test\cucumber\features\GetAlerts.feature,
the console output for the controller shows this:
APIWAL_525_Account_Security_otp.feature
AssignRoomToReservation.feature
DeleteAccount.feature
DeleteConsumersPaymentMethods.feature
GetActivitiesByID.feature
GetAddressType.feature
GetAddrMiscType.feature
GetAlerts.feature
GetAttractionsByID.feature
What do i need to do to make the view match the controller output from console??
thanks!
ironmantis7x
UPDATE!!!!!
to solve my listing issue I did this:
I changed the controller file to do this little trick:
render(view: "index", model: [name:list.name])
then to make the gsp list the file names on a new line, I did this:
<ul>
<g:each in="${name}" var="fileName">
${fileName}<br>
</g:each>
</ul>
And presto!!
This is where we can launch our tests and get reports
APIWAL_525_Account_Security_otp.feature
AssignRoomToReservation.feature
DeleteAccount.feature
DeleteConsumersPaymentMethods.feature
GetActivitiesByID.feature
GetAddressType.feature
GetAddrMiscType.feature
.....
Thanks guys for encouraging me to struggle to learn it and helping me along the way!
Your gsp is rendering the list but your list of names is in the variable found, not name. Anyway, your last action line should be:
render(view: "index", model: [name: found])
In other hand, your gsp is rendering the list, but should give it some style. An example:
<ul>
<g:each in="${name}" var="fileName">
<li>${fileName}</li>
</g:each>
</ul>
You are storing your o/p in field named found, but in model you are using field name to be included as name, which doesn't contain your o/p. You haven't declared name, or have added anything to it, idk how it is showing that o/p even on gsp.

Grails nested taglibs that use templates?

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?

Grails GSP-model injection bug

I have the following Grails 2.3.6 controller:
#Slf4j
class FizzController {
def index() {
List<Widget> widgets = getSomehow()
log.info("About to render foo.gsp with ${widgets.size()} widgets.")
render (
view: "foo",
model: widgets
)
}
}
Where foo.gsp is:
<!DOCTYPE html>
<html>
<head>
<omitting a bunch of stuff for brevity>
</head>
<body>
<h3>Widget List:</h3>
<g:each in="${widgets}" var="widget" >
<h2>Name: ${widget.name}, Type: ${widget.type}</h3>
</g:each>
</body>
</html>
When I do a Grails run-app and navigate my browser to:
http://localhost:8080/myapp/fizz
I see my foo GSP view/page (with the <h3> header tag), however it doesn't display anything else. When I view the page source code, I see:
<div id="page-body">
<h3>Widget List:</h3>
</div>
This indicates that I am injecting an empty List<Widget>, right?
However in my log output, I see:
About to render foo.gsp with 1 widgets.
So it actually seems like I do have a non-empty List<Widget>, but apparently I'm not properly injecting it into my GSP. So what's going on here?
you are passing the wrong model, it should be a map:
List<Widget> widgets = getSomehow()
render (
view: "foo",
model:[ widgets:widgets ]
)
then you can access it in your gsp:
<g:each in="${widgets}" var="widget" >
<h2>Name: ${widget.name}, Type: ${widget.type}</h3>
</g:each>

Grails why does respond not work and renders nothing?

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

How do I render a map in Grails view

Lets say I have a map like this ( which is returning from a controller)
a= [a:[1,2,3,4],b:[5,6,7,8],c:[9,10]]
[a] //returning [a] from controller to the view
Now in Grails view, how should I render them so that they look something like this in my browser :
a
-- 1
-- 2
-- 3
-- 4
b
-- 5
-- 6
-- 7
-- 8
//so on..
Edit:
I did something like this on a particular case of displaying the details(with hint got from the answer given by Antoine) :
<html>
<head>
<title>
All Tasks.
</title>
<meta name ="layout" content="main" />
</head>
<body>
<h2>All the task</h2>
<g:each in="${tasksByDate}" var ="tasks">
<h4>${tasks.key}</h4>
<g:each in="${tasksByDate.value}" var="content" >
<div id = "todayswork">
${content}
</div>
<hr/>
</g:each>
<br />
</g:each>
</body>
</html>
But when I render it in my browser, I'm getting only the heading in my browser. Like this :
All the task
//other contents missing. . .
And I'm sure that from the controller alltasks the value is passing to the view, with the name tasksByDate. As it is printing it on my console like this :
[2011-12-19 14:21:35.949:[Belongs to Schedule Finish task A], 2011-12-21 14:21:35.897:[Belongs to Schedule Finish task A], 2011-12-23 14:21:35.907:[Belongs to Schedule Finish task A], 2011-12-19 14:21:36.051:[Belongs to Schedule Finish task A], 2011-12-17 14:21:36.048:[Belongs to Schedule Finish task A]]
Here is my controller code :
def alltasks = {
def allTasks = DaySchedule.findAllByTaskIsNotNull()
def tasksByDate = [:]
tasksByDate.putAll(
allTasks.groupBy {
it.Todaysdate
}
)
println tasksByDate
[tasksByDate]
}
Where I'm making mistake?
Thanks in advance.
You can iterate over a map elements using g:each. The key method of a map element will return the key (a, b, c in your example) and the value method will return the associated value (in your case, a list of integers). In turn you can use g:each on this list.
Here is what I would do in GSP:
<ul>
<g:each var="mapElement" in="${a}">
<li>$mapElement.key
<ul>
<g:each in="${mapElement.value}" var="number">
<li>$number</li>
</g:each>
</ul>
</li>
</g:each>
</ul>
Based on your formatting I deduced that you want to present your map as a list of lists, hence the nested <ul> elements.
In your controller, replace the last line of the allTasks action :
[tasksByDate]
... by ...
[tasksByDate : tasksByDate]
The return of a controller action (as far as I know) should be a map, not a list. The key is the name of the variable that you will retrieve in the GSP, the value is the contents of that variable.
See http://grails.org/doc/latest/guide/single.html#controllers, chapter 6.1.3 Models and Views

Resources