How do I render a map in Grails view - grails

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

Related

Refactoring Grails templates to expect maps instead of lists

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

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.

How to list childrens recursively in a GSP page?

i have a Media Domain
def Media {
String name
static belongsTo = [parent:Media]
static hasMany = [children:Media]
}
In the show.gsp page i want to list all my root Media (that haven't parent) into an ul list an their children and their children recursively in another ul lists. I have used the Tag for the first list but i don't know how to do this recursively for the children.
So have you an idea of how to do this ?
Thanks.
You could put the recursive part into a GSP-template and then call it recursively, e.g.:
index.gsp: Assuming rootMedias is passed into the view
<g:each in="${rootMedias}" var="media">
<g:render template="step" model="${[media: media]}" />
</g:each>
_step.gsp
<ul>
<g:each in="${media.children}" var="child">
<li>
${child.name}
<g:render template="step" model="${[media: child]}" />
</li>
</g:each>
</ul>

Grails 2.1 createCriteria issue with params

edit-
I am trying to return a list of portfolios, along with the last 5 publications attached to each portfolio from my 2 domain classes. I am getting the last total 5 publications back and each list displays all 5. The query is not returning that particular instances own publications. Kellys great ideas put back into another track.
I have created the method in the portfolio controller which is the hasMany side to the publications which belongsTo the portfolio domain class.
I just cant seem to get the portfolios to list their own publications. If I change the eq portfolios, it all works fine, except each portfolio list shows the same publications.
How do I load each portfolio and list the last 5 publications. This is rendered from the portfolio/list page as a partial.Is this the issue maybe. Should I just render it from a new view which is not associated with the portfolio list action??
Newbie to grails and have read and read the doc's, just cant seem to get the params query to return correctly. Help
def _webList (){
//def per = Portfolio.properties
def portfolios = Portfolio.list(params.id)
def results = Publication.withCriteria {
eq('published', 'Yes')
order('lastUpdated', 'desc')
maxResults(5)
}
def reportscount = Publication.count()
[ portfolios: portfolios, results: results, reportscount: reportscount]
}
I can show the the sql log if needed.
EDIT
The following code is the entire partial from file _webList.gsp. The top div -alert loads on the page, but the content within the div property-list portfolio fails to load. Using Kelly's hibernate criteria produces the query in the sql log but not results or styles or anything are return to the view??. weird.!
<div class="alert alert-info" xmlns="http://www.w3.org/1999/html">Permissions apply to <strong>editing</strong> publications.<br>
<div style="display: inline;"><p>Click portfolio name to read or edit publications. Total number of sites: <strong>${rsNumb}</strong> | Total number of publications: <strong>${reportscount}</strong> </p>
</div>
</div>
<div class="property-list portfolio">
<g:each in="${portfolios}" var="portfolioInstance">
<div class="site-listing">
<div><span class="label">Site Name:</span><g:link action="show" id="${portfolioInstance?.id }">${portfolioInstance?.portfolioName?.encodeAsHTML()}</g:link></div>
<div><span class="label">Site Description: </span>${portfolioInstance?.portdescrip?.encodeAsHTML() }</div> <br>
<div><span class="label">Site Administrator: </span>${portfolioInstance?.profile?.portfolioAdmin?.encodeAsHTML() }</div> <br>
<div><span class="label"> Total publications:</span><span class="badge badge-success"> ${portfolioInstance?.publications?.size()}</span> </div>
<!-- whatever else you need here -->
<!-- now iterate through the pubs -->
<g:if test="${portfolioInstance?.publications}">
<g:set var="publicationInstance" />
<ul class="site-publication">
<li class="fieldcontain">
<span id="publications-label" class="property-label"><g:message code="portfolio.publications.label" default="Last 5 published publications:" /></span>
<g:each in="${portfolioInstance.publications}" var="publicationInstance">
${publicationInstance?.id}
<span class="property-value" aria-labelledby="publications-label"><g:link controller="publication" action="show" id="${publicationInstance.id}">${publicationInstance?.encodeAsHTML()}</g:link></span>
<!-- and again whatever else you need here -->
</g:each>
</g:if>
</g:each>
</div>
EDIT - sql log below
Hibernate: select this_.id as id5_1_, this_.version as version5_1_, this_.date_created as date3_5_1_, this_.last_updated as last4_5_1_,
this_.portdescrip as portdesc5_5_1_, this_.portfolio_name as portfolio6_5_1_, this_.portpublished as portpubl7_5_1_, this_.profile_id as profile8_5_1_,
this_.status as status5_1_,
publicatio1_.portfolio_id as portfolio5_5_3_,
publicatio1_.id as id3_, publicatio1_.id as id2_0_,
publicatio1_.version as version2_0_,
publicatio1_.date_created as date3_2_0_,
publicatio1_.last_updated as last4_2_0_,
publicatio1_.portfolio_id as portfolio5_2_0_,
publicatio1_.publication_content as publicat6_2_0_,
publicatio1_.publication_name as publicat7_2_0_,
publicatio1_.published as published2_0_,
publicatio1_.publisheddate as publishe9_2_0_,
publicatio1_.publishedemail as publish10_2_0_,
publicatio1_.pubproduct_id as pubproduct11_2_0_
from portfolio this_ left outer join publication publicatio1_
on this_.id=publicatio1_.portfolio_id where (this_.status=?)
and (publicatio1_.published=?) order by publicatio1_.last_updated desc
You are getting the java.lang.ClassCastException because portfolios is a list and portfolio in the Publication class (probably) isn't, it's probably an id (long); can't cast a list in any meaningful way to compare to a long in eq ('portfolio', portfolios)
You should not need two separate queries since the domain classes are related.
--EDIT--
Editing to not use separate action and just use list action. I've not been able to get the include to work either, but below is pretty much what I've in dozens of cases. If there is some reason you can't do this then maybe a new question on just using the include mechanism might generate some attention.
I'm not sure what your current list action looks like. This is how I would code a list method to get ALL Portfolios and their last 5 Publications. No need for any parameters because I'm returning ALL portfolios.
//PortfolioController
def list (){
def portfolios = Portfolio.createCriteria().list {
//if you needed to filter the list by for example portfolio status or something you could add that here
or {
eq('status','ACTIVE')
eq('status','PENDING')
}
publications(org.hibernate.criterion.CriteriaSpecification.LEFT_JOIN) {
eq("published", "Yes")
order("lastUpdated", "desc")
firstResult(5)
}
}
[portfolios: portfolios, portfolioCount:portfolios.size()]
}
Now the Publications come pre-attached to their Portfolio.
The LEFT_JOIN part of the above insures you get back a list of portfolios with ONLY those Publications attached that meet the criteria; if you leave that out it defaults to an inner join and when you iterate you will get ALL of the Publications for that Portfolio (even if they don't meet the criteria).
Then in your gsp iterate through the portfolios - it can either be directly in the list.gsp or in a template rendered in list.gsp. If you put it in a template called _webList.gsp you would render it in the list.gsp as
<g:render template="weblist" model="['portfolios': portfolios]" />
This is either in list.gsp or _webList.gsp - I would start with it directly in list.gsp to make sure it's all working.
<g:each in="${portfolios}" var="portfolioInstance" status="i">
${portfolioInstance?.portfolioName?.encodeAsHTML()
<!-- whatever else you need here -->
<!-- now iterate through the pubs -->
<g:each in="${portfolioInstance.publications"} var="publicationInstance" status="j">
${publicationInstance.id}
<!-- and again whatever else you need here -->
</g:each>
</g:each>
--EDIT--
firstResult(5) seems to do the trick.
--EDIT--
You'll notice I have the maxResults(5) commented in there - I'm having trouble getting that to work correctly. It seems to control the number of portfolios that are returned even though it is in the association block. Maybe someone else will see this and add that piece of the puzzle - or tinker with it yourself. I'll keep trying and update if I figure it out.

execute GORM query in GSP with Grails

In my gsp I have to execute queries to get back some datas. My queries work in a controller, and the same ones do not work in my gsp. I suppose this is because of the quotes, and I've tried to escape quotes, without success.
Here is my actual code :
<%# page import="package.PositionnementProfil" %>
<html>
<g:set var="listPositionnementActuel" value="${PositionnementProfil.executeQuery("from PositionnementProfil as pp WHERE pp.profil = ':profilInstance' AND pp.positionnement.libelle = 'possible'", [profilInstance:profilInstance])}" />
[...]
</html>
<body>
<g:each in="${listPositionnementActuel}" var="pos" >
<h1>test</h1>
</g:each>
[...]
</body>
which prints nothing. If I do a simple PositionnementProfil.list() I've got all the results

Resources