Grails: Reference Two Models in One View - grails

As mentioned in my previous post, I'm working on a small Grails CRUD app. In my app I have a "Master Agreement" model that has a hasMany relationship with a model called "SOW" (scope of work). I have a view called "show" under Master Agreeements that shows the administrative information for the selected Master Agreement. At the bottom of the page I would like to show each SOW associated with the master agreement. Below is my code along with the actions I've taken to try and solve this.
Show Action in Master Agreement Controller:
The Master Agreement model is successfully passed to the Master Agreement "show" view and Grails successfully prints the sowList to the console. However, when I reference the SOW model in the view, nothing happens.
def show(Long id){
println 'I made it to show for Master Agreement.'
def masterAgreementInstance = MasterAgreement.get(id)
def sowList = masterAgreementInstance.getSows()
println sowList
render(view: "show", model: [masterAgreement: masterAgreementInstance, sow: sowList])
}
Master Agreement "Show" View
The HTML is rather long, so below is how I reference the Master Agreement model (this works), followed by how I am trying to reference the SOW model (this doesn't work - the page loads, but nothing happens with the sow reference). In fact, no rows show up at all under my table headers.
<div class="grid-item">
<div class="label">Office:</div>
<div class="data">${masterAgreement.office.name}</div>
</div>
Unsuccessful SOW Model Reference - (see ${sow.name} and ${sow.spendCap} )
<table class="infoTable fullWidth">
<tr>
<th>#</th>
<th>SOW Name</th>
<th>Invoice Total</th>
<th>SOW Amount</th>
<th>View</th>
<th class="centered">Expand</th>
</tr>
<g:each status="i" var="sow" in="${sow}">
<tr>
<td>${i + 1}</td>
<td>${sow.name}</td>
<td>Placeholder data</td>
<td>${sow.spendCap}</td>
<td>
<g:link controller="SOW" action="show">View</g:link>
</td>
<td class="centered">
<i class="fas fa-angle-left" id="expandArrow" onclick="expandInvoices();" title="Expand Invoices"></i>
</td>
</tr>
</g:each>
</table>
Below are the two models.
class MasterAgreement {
String name
String pointOfContactFName
String pointOfContactLName
String pointOfContactPhone
String pointOfContactEmail
Double spendCap
Date startDate
Date endDate
Office office
BidType bidType
MasterAgreementStatus masterAgreementStatus
static hasMany = [sows: SOW]
static belongsTo = [contractor: Contractor] //will cascade on Contractor delete
static constraints = {
}
}
class SOW {
String name
Double spendCap
static hasMany = [sowInvoices: SOWInvoice]
static belongsTo = [masterAgreement: MasterAgreement] //will cascade on Contractor and/or MasterAgreement delete
static constraints = {
}
}
How the page looks upon loading.
enter image description here
Any help is greatly appreciated!

Related

Grails won't retrieve single item from list using get(params.id)

I have a table where you input information which is then displayed, when you click the name of it I want it to go to a new page where it only displays the item clicked and not everything.
Here is my controller...
class RecipeController {
def index() {
def recipe = Recipes.list() //Recipes is the Grails Domain
[recipe: recipe]
}
def newRecipeForm() {
}
def createRecipe() {
def r = new Recipes(name: params.name, course: params.course, diet: params.diet)
r.save()
redirect(action:"index")
}
def deleteRecipe() {
def r = Recipes.get(params.ID)
r.delete()
redirect(action:"index")
}
def showRecipe() {
def rec = Recipes.get(params.ID)
[recipe: rec]
}
}
My index.gsp where the recipe name is clickable which should redirect by ID to a new page where it just displays only that recipe info.
<g:each var="Recipes" in="${recipe}">
<tbody>
<tr>
<td><g:link action="showRecipe" id="${Recipes.id}">${Recipes.name}</g:link></td>
</tr>
</tbody>
</g:each>
and finally my showRecipe.gsp where the recipe should be displayed by itself...but it keeps displaying all of them that i add
<g:each var="rec" in="${recipe}">
<tbody>
<tr>
<td>${rec.name}</td>
</tr>
</tbody>
</g:each>
any guidance would be awesome! thanks
I could say that your first error is in your index.
It is likely that the Recipe.id that you have is retrieving all the id´s and sending them in the link. You should not use UpperCase in a property name, the compiler might think of the property as a Class. The code should be more like:
<tr>
<td><g:link action="showRecipe" id="${recipes.id}">${recipes.name}</g:link></td>
</tr>
</g:each>
Add a println(params) or log.info(params) in your show() action to print all your params and see exactly what you are receiving from your view.
Also be careful with your naming conventions. You might want to change recipe for recipeList or something, and recipes to recipeInstance or simply recipe. It will make the code more readable, and make it easier for us to help you.
EDIT
As #Nitin Dhomse said you only need to access the data of a single recipe so you don´t need to do
<g:each var="rec" in="${recipe}">
in your show.gsp.
It would be more like
<table>
<tbody>
<tr>
<td>${recipe?.id}</td>
<td>${recipe?.name}</td>
....
</tbody>
</table>
Also, you should either redirect in your show() action if you dont find the recipe Instance or access your properties like $(recipe?.name) in your show.gsp, otherwise you will get nullPointer Exceptions.

ASP.NET MVC creating Model from Database (Postgres)

Please forgive me if this question is too stupid but I just started learning ASP for few days...
So I decided to make some simple Web application which will dispaly data from database (Postgres).
To connect to DataBase I using NpgsqlConnection class. I saw few tutorials how to connect to DB i.e. here, but almoust everywhere they are using MSSQL and nowhere I cant find solutions for my case.
So I would like to have model which will be contains all fetched data within I will be able to iterate like this:
<% foreach (var item in Model)
{ %>
<tr>
<td><%: item.Title %></td>
<td><%: String.Format("{0:g}", item.ReleaseDate) %></td>
<td><%: item.Genre %></td>
<td><%: item.Rating %></td>
<td><%: String.Format("{0:F}", item.Price) %></td>
</tr>
<% } %>
So How I should do to achieve this?
I thought to create class
public class Person{
int id;
string Name;
string Surname;
...
}
next create generic List of type Person and after fetch data, add all fetched data to my List. And then somehow pass this List as Model.
I think there is some better way to do it right. Any Suggest?
Nope, you're pretty much on track.
The main difference you see from the tutorials is most of them are probably using entity framework to populate the models. Since you're using postgres...i wouldn't really recommend trying to get entity framework to work with it (I heard it's a nightmare). You can use a different Orm if you like, or just do it with your connection command and reader like you're probably used to.
The way I would do it is create a domain model that looks like just like the database model (looks like what you did with Person)
From there you would populate it in a Controller.
public class PersonController : Controller
{
//this method will map to the Person/Index route by default
public ActionResult Index()
{
//use your npgsqlconnection right now to populate whatever object you'd like
List<Person> people = PopulateFromPostgres();
//here were returning the index view with the model being a list of person
return View(people)
}
}
Then in your view (Views/Person/Index.cshtml i believe in this example)
#model List<Person>
<table>
#foreach (var item in Model)
{
<tr>
<td>#item.Title</td>
<td>#String.Format("{0:g}", item.ReleaseDate)</td>
<td>#item.Genre</td>
<td>#item.Rating </td>
<td>string.Format("{0:F}", item.Price)</td>
</tr>
}
</table>
Let me know if there's more specific area you don't understand here.

asp.net mvc - pass partial data model to partial view

I wish to build a partial view that gets a model column and print it.
Something like that:
At the view:
#model IEnumerable<products_comparison.Models.Product>
#{
ViewBag.Title = "Index";
var Brand = (from r in Model
select r.Brand).Distinct();
}
<h2>
Index</h2>
#Html.RenderPartial("_DisplayAttribute",Brand)
And at the partial view:
<table>
<tr>
<th>
Brand
</th>
</tr>
#foreach (var row in Model)
{
<tr>
<td>
#Html.DisplayFor(r => row)
</td>
</tr>
}
</table>
There are a few problems I run into:
The compiler doesnt allow me to send Barnd to the partial view.
If you look at the partial view code you will see the word Brand, which is the column name. I dont wish to hard-coded the word "Brand" in the partial view, instead I like that the column name will be there.
In the partial view I need to put #model products_comparison.Models.Product, but I dont
want to send the hole table. I want to send only one column - But I dont know what to put there..
Thanks!
EDIT:
Just to clear one thing, I want that the view will call the same partial view for each column in the table(for most of the columns in the table anyway) and each time I'll send a different column(distinct value column to be exact).
Start by refactoring and putting the right logic into the right place. This LINQ query has strictly nothing to do in a view. A view is not supposed to do any LINQ queries or whatever to pull data. A view is supposed to work with data that it is passed to it from the controller action under the form of a view model. A controller action builds and passes an adapted view model that you define for the view.
So as always you start by defining a view model that will be adapted to the requirements of your view:
public class MyViewModel
{
public IEnumerable<Brand> Brands { get; set; }
}
then you write a controller action that will populate this view model and pass it to the view:
public ActionResult Foo()
{
IEnumerable<products_comparison.Models.Product> products = ...
var model = new MyViewModel
{
Brands = (from r in Model select r.Brand).Distinct()
};
return View(model);
}
then a view:
#model MyViewModel
<table>
<tr>
<th>
Brand
</th>
</tr>
#Html.DisplayFor(x => x.Brands)
</table>
and finally you could define a corresponding display template which will automatically be rendered for each element of the Brands collection of your view model (~/Views/Shared/DisplayTemplates/Brand.cshtml):
#model Brand
<tr>
<td>
#Html.DisplayForModel()
</td>
</tr>
For 1 try changing #Html.RenderPartial("_DisplayAttribute",Brand) to #Html.Partial("_DisplayAttribute",Brand)
You will also need to specify the model in the partial view like #model products_comparison.Models.Brand or something like it
Also please clarify 2 & 3 as they are not clear what you want

How to get an html table back to a Model?

I'll use the famous NerdDinner as an example here.
I have a search page where the user can enter a search string and then see the result in a table below. The user can also add more results to the table, like this:
Search for dinners today and display in a table.
Search for dinners tomorrow and add the result to the table.
The table will now show dinners today and tomorrow.
The user is also able to remove dinners from the table by clicking on them, one by one.
I need to generate a pdf with the results in the table. Not like a print screen because the pdf has it's own layout. I just need the data in the table. Preferably in a list of Dinner models.
Right now I can generate a pdf from a list of Dinner models. But once I've printed them to the table and the user has manipulated it I don't know how to get it back to a list of Dinner models.
Another solution could be to keep the Id's hidden in the table and then do another search in the DB with the Id's from the table (after the user has manipulated it). At least then I would get the result in the form of a list of Dinners. But this seems redundant to me.
Has anyone had a similar problem and how did you solve it?
You could put the table inside an html <form> and on each row in addition to the label you could have a hidden field:
#model IEnumerable<Dinner>
#using (Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Prop1</th>
<th>Prop2</th>
<th>Prop3</th>
</tr>
</thead>
<tbody>
#Html.EditorForModel()
</tbody>
</table>
<input type="submit" value="Export to PDF" />
}
and in the editor template:
#model Dinner
<tr>
<td>
#Html.DisplayFor(x => x.Prop1)
#Html.HiddenFor(x => x.Prop1)
</td>
<td>
#Html.DisplayFor(x => x.Prop2)
#Html.HiddenFor(x => x.Prop2)
</td>
<td>
#Html.DisplayFor(x => x.Prop3)
#Html.HiddenFor(x => x.Prop3)
</td>
</tr>
Now this form could be submitted to the following controller action:
public ActionResult GeneratePdf(IEnumerable<Dinner> dinners)
{
byte[] pdf = ...
return File(pdf, "application/pdf", "dinners.pdf");
}
You may also checkout the following blog post for managing a variable length list in order to keep input field names in sync for the binder when adding/removing elements.

ASP.NET Collection Model Binding Nested Form Problem

I have a partial view that is bound to an object Cart. Cart has a collection of CartLines. My view is below:
<tbody>
<% foreach (var line in Model.Lines) { %>
<tr>
<td align="center"><%=Html.CatalogImage(line.Product.DefaultImage, 80) %></td>
<td align="left">
<%=Html.ActionLink(line.Product.Name, "Product", "Catalog",
new { productId = line.Product.Id }, new { title = "View " + line.Product.Name })%>
</td>
<td align="right"><%= line.Product.Price.ToString("c")%></td>
<td align="center">
<%=Html.Hidden("lines[" + i + "].key", line.Product.Id) %>
<%=Html.TextBox("lines[" + i + "].value", line.Quantity, new { #class = "quantity" })%>
</td>
<td align="right"><%= (line.LineTotal).ToString("c")%></td>
<td>
<%using (Ajax.BeginForm("RemoveFromCart", "Cart",
new {ProductId = line.Product.Id, returnUrl = ViewData["returnUrl"]},
new AjaxOptions { UpdateTargetId="cart", LoadingElementId="loading" }))
{%>
<input type="image" src="<%=AppHelper.ImageUrl("delete.gif")%>" value="Remove item" />
<%} %>
</td>
</tr>
<% i++; } %>
</tbody>
There are two things to note. The first is that I am using a form per line for removing items.
The second is that I had attempted to allow users to change the quantity of line items and then click an update button to pass all the changes to the controller action:
// POST: /Cart/Update
[HttpPost]
public ActionResult Update(Cart cart, IDictionary<int,int> lines, string returnUrl)
{
foreach (var line in lines) {
Product p = _catalogService.GetProduct(line.Key);
cart.UpdateItem(p, line.Value);
}
if (Request.IsAjaxRequest())
return PartialView("Cart", cart);
else
return RedirectToAction("Index", new { returnUrl });
}
Note that I am using a dictionary since I am only concerned about the product and quantity. I don't really like the fact that I am having to retrieve the product again before calling cart.UpdateItem but I couldn't figure out how to pass the Product from the model to my action instead of the id.
The main problem however, is rather stupidly I wrapped the entire cart in a form so that I could post back the values and then spent a good hour wondering why things were not working correctly in IE - doh! nested forms
So I am stuck on how to get round this. I want the ability to remove items individually but allow a user to change item quantities and then pass all changes at once to the controller. I can't use links for my remove action as I would need to use javascript to force a post and everything must work without javascript enabled.
[Update]
Would a better solution be to allow updates on my custom model binder? This way I could make changes inside my view and post the cart object back to the controller - although I'm not sure whether this is possible with child collections (Cart.CartItems).
I've had a look on sites like Amazon and it would appear they wrap the entire cart in a form and both global update buttons and indidivual remove item buttons post back to the same action when javascript is disabled.
Any help would be appreciated.
Thanks,
Ben
There is only one way here and thats the ugly way. Have 1 form around everything.
Then in the action you have to check which button was pressed (you get the name of the button in the request).
It gets even more ugly with differences in firefox and ie. If you have a button pressed ie or firefox (Dont remember which one) not only sends the name of the pressed button, but also the location where the button was pressed.
You have more options if your solution can rely on JS enabled browsers. But thats another story.

Resources