Updating many instances in one view - grails

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
}
}

Related

Assign an object in a form

I have a form which should print questions dynamically. Foo has a Field object and a Field Definition object. I want the Field to have its fieldDefinition assigned by the form. All of the behind the scenes stuff works fine.
The below code works with assigning Strings and Longs in other scenarios.
Here's the line that's causing trouble:
<input th:type="hidden" th:field="*{fields[__${iterationStatus.index}__].fieldDefinition}" th:value="${fooViewModel.fields[__${iterationStatus.index}__].fieldDefinition}"/>
This is what it looks like when it renders in html:
<input type="hidden" value="com.blah.domain.FieldDefinition#fbb2e392" id="fields0.fieldDefinition" name="fields[0].fieldDefinition">
When I submit the form, no controller action is invoked, and the app simply redirects to the error page.
If it's impossible to actually do the assignment that way, please suggest other methods. The only way I came up with is to have Foo use the FieldDefinitionService to do the assignment after being passed an ID. I don't that Domain class to have access to another Domain object's Service.
Thanks
Just an example: when you need to iterate a form inside an element that has a th:each attribute, you can use the following structure (so far, it's the only way that's working for me).
<tr th:each="rank, stat : ${ranks}">
<td th:text="${rank.name}">This is static</td>
<td th:text="${rank.description}">This is static</td>
<td>
<form th:action="#{/user/ranks/delete}" method="post">
<input type="hidden" id="id" name="id" th:value="${rank.id}"></input>
<button class="btn btn-danger" type="submit">
<span>Delete</span>
</button>
</form>
</td>
</tr>
Here ranks is a list of entities that have to be displayed on a table and, for each entity, there is a delete button associated to a form.
The controller method should be similar to the following fragment, the parameter is availabled with the name id:
#RequestMapping(path = "/delete", method = RequestMethod.POST)
public View deleteRank(Model model, #RequestParam(name = "id") String rankId,
#ModelAttribute("user") User user)
{
Long id = Long.parseLong(rankId);
// delete ...
RedirectView redirectView = new RedirectView("/user/ranks");
return redirectView;
}

How to read a string parameter passed to a partial view

Sorry if this appears as a duplication - I can see dozens of similar questions concerning Controllers, but not partial views inside views.
I have a very simple partial view:
<p>
<input type="button" value="Go Back" onclick="history.go(-1);" />
</p>
I then reference this across many views:
#Html.Partial("~/Views/Shared/MyPartialView.cshtml")
Suppose I need to change the value of the button in the partial view. I think I can reference the partial view like this:
#Html.Partial("~/Views/Shared/MyPartialView.cshtml", new { ButtonValueParam = "Restart" })
But then how to I read the value of ButtonValueParam? I tried this but it doesn't work.
<p>
<input type="button" value="#ButtonValueParam " onclick="history.go(-1);" />
</p>
All other examples I've seen today either include a Model or a Controller.
You can pass with the help of ViewDataDictionary.
#Html.Partial("~/Views/Shared/MyPartialView.cshtml", new { new ViewDataDictionary { "ButtonValueParam", "Restart" }} )
and read the values from the ViewData.
<input type="button" value="#ViewData["ButtonValueParam"]" onclick="history.go(-1);" />
Hope it helps

displaying bean class items on view - beginner

The bean class looks like this:
String houseNo
String address
Person person
The view looks like this
<g:form 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>
According to my knowledge in Grails, i think is <g:render template="form" /> will pull all form attributes and display it.
But what i want to do is Instead of displaying a drop-down for Person, i want to display all Person related fields like personName, personAge. How can i display these fields in a label underneath ?
Help
You're correct about the way g:render works, the template part refers to a GSP which will look through the bean values and print them according to the html + groovy markup in "_form.gsp" (located in your views folder under the controller name).
To change the way the Person domain object is displayed, simply edit this "_form.gsp" and take out the 'select' html code - replacing it with groovy markup using the property values of the bean, eg.
${beanName.person.personName} //(use the existing code to help workout the bean name etc)
Hopefully that helps you on your way.

DropDownListFor not selecting the selected item in the SelectList

I am working on an ASP.NET MVC3 application and I cannot get a DropDownListFor to work with my property in a particular editor view.
My model is a Person and a person has a property that specifies it's "Person Type". PersonType is a class that contains a name/description and an ID. I can access the available PersonTypes within the application through my static/shared class called ApplicationSettings.
In the Edit Template view for my Person I have created a SelectList for debugging purposes:
#ModelType MyNamespace.Person
#Code
Dim pTypesSelectList As New SelectList(MyNamespace.ApplicationSettings.PersonTypes, "ID", "Name", Model.PersonType.ID)
End Code
I am then providing this SelectList as a parameter to the DropDownListFor that is bound to the PersonType property of my Person.
I am also printing the Selected property of each item in the SelectList for debugging purposes:
<div style="text-align: center; margin: 5px 0 0 0;">
<div>
#Html.LabelFor(Function(model) model.PersonType)
</div>
<div>
#Html.DropDownListFor(Function(model) model.PersonType, pTypesSelectList)
#Html.ValidationMessageFor(Function(model) model.PersonType)
<br />
<br />
<!-- The following is debugging code that shows the actual value-->
#Model.Type.Name
<br />
#Model.Type.ID
<br />
<br />
<!--This section is to show that the select list has properly selected the value-->
#For Each pitem In pTypesSelectList
#<div>
#pitem.Text selected: #pitem.Selected
</div>
Next
</div>
</div>
The view is bound to a Person whose PersonType property is "Person Type # 2" and I expect this to be selected; however the HTML output of this code looks like this:
<div style="text-align: center; margin: 5px 0 0 0;">
<div>
<label for="PersonType">PersonType</label>
</div>
<div>
<select id="PersonType" name="PersonType">
<option value="7e750688-7e00-eeee-0000-007e7506887e">Default Person Type</option>
<option value="87e5f686-990e-5151-0151-65fa7506887e">Person Type # 1</option>
<option value="a7b91cb6-2048-4b5b-8b60-a1456ba4134a">Person Type # 2</option>
<option value="8a147405-8725-4b53-b4b8-3541c2391ca9">Person Type # 3</option>
</select>
<span class="field-validation-valid" data-valmsg-for="PersonType" data-valmsg-replace="true"></span>
<br />
<br />
<!-- The following is debugging code that shows the actual value-->
Person Type # 2
<br />
a7b91cb6-2048-4b5b-8b60-a1456ba4134a
<br />
<br />
<!--This section is to show that the select list has properly selected the value-->
<div>
Default Person Type selected: False
</div>
<div>
Person Type # 1 selected: False
</div>
<div>
Person Type # 2 selected: True
</div>
<div>
Person Type # 3 selected: False
</div>
</div>
</div>
As you can see the printed Selected properties for the items in the SelectList shows that the 3rd item is "Selected". But what is driving me crazy is that the option that corresponds with this is Not Selected.
Generally, the Selected property in SelectList will be totally ignored by the HTML helpers unless there's no other option. If DropDownListFor can find the value by other means, it will insist on using that value.
In this case, it will use the value of model.PersonType(.ToString()) - but that's not what you want, judging by the model.PersonType.ID you pass to the SelectList.
More info in the answer here.
Workaround
One easy workaround that should work would be to set:
ViewData["PersonType"] = model.PersonType.Id.
The helper looks in ModelState first if it exists - i.e. on POST. This should work already, since ModelState["PersonType"] will be populated with the actual selected value that was posted.
After ModelState it will look in ViewData - with ViewData["PersonType"] first, and only then ViewData.Model.PersonType. In other words, you can "override" the value on your model with a value set directly on ViewData.
Better (IMO) solution
The more general, "better practice", way to solve it (which also avoids having a custom model binder in order to translate the POST'ed ID back to PersonType) is to use a ViewModel instead of working with full models in your view:
Have a PersonTypeID property - instead of PersonType.
Populate it with PersonType.ID
use this in your view
VB.NET: Html.DropDownListFor(Function(model) model.PersonTypeID), or
C#: Html.DropDownListFor(model => model.PersonTypeID)
When form is POST'ed, translate the ViewModel (including PersonTypeID => PersonType) back into the actual model in your POST Action.
This may seem like more work, but generally there tend to be many occasions in a project where you need more view-specific representations of your data to avoid too much inline Razor code - so translating from business objects to view models, while it may seem redundant and anti-DRY at times, tends to spare you of a lot of headaches.
Are you sure that your ModelState for "PersonType" key before rendering the view is empty? As JimmiTh commented is going to search for the value in the ModelState first. It happened to me too, you can try
#Html.DropDownList("PersonTypeFake", Function(model) model.PersonType, pTypesSelectList) and it should select the right option.

How to conditionally disable a form input field

Say I have a Domain object Teacher with two fields String name, TeacherType teacherType, where TeacherType is an enum containing AssitantProfessor, AssociateProfessor, Professor.
After I generate the views using grails run-target generate-all Teacher, it produces an _form.gsp that is used for both create and edit of Teacher. In the edit view I want only the name to be editable but the TeacherType to be unmodifiable once created (this is just an example, it is a requirement that certain fields can't be updated after creation). In the create view, both TeacherType and name should be editable.
Since both create.gsp and edit.gsp render the _form template, what is the preferred approach here?
Create two separate templates i.e. _formCreate.gsp , _formEdit.gsp; Or
Pass in a model map within create.gsp and edit.gsp and use them in _form.gsp to conditionally render the view?
e.g.
In create.gsp:
<fieldset class="form">
<g:render template="form" model="[teacherInstance: teacherInstance, 'mode':'create']"/>
</fieldset>
In edit.gsp
<fieldset class="form">
<g:render template="form" model="[teacherInstance: teacherInstance, 'mode':'edit']"/>
</fieldset>
In _form.gsp
<g:if test="${mode == 'edit'}">
<g:select name="teacherType" from="${TeacherType?.values()}" keys="${TeacherType.values()*.name()}" required="" value="${teacherInstance?.teacherType?.name()}" disabled="disabled"/>
</g:if>
<g:else>
<g:select name="teacherType" from="${TeacherType?.values()}" keys="${TeacherType.values()*.name()}" required="" value="${teacherInstance?.teacherType?.name()}" disabled="false"/>
</g:else>
Approach 2 works but I suppose if the number of conditional statements increase it may just be better to follow approach 1 and split the forms.
Is there another approach that I'm not aware of?
The disabled attribute of <g:select> (and many other <g:...> form field tags) can be a boolean-valued expression:
<g:select name="teacherType" from="${TeacherType?.values()}"
keys="${TeacherType.values()*.name()}" required=""
value="${teacherInstance?.teacherType?.name()}"
disabled="${mode == 'edit'}"/>
This will render as disabled="disabled" if the expression evaluates to true, and as the absence of a disabled attribute (i.e. the field will not be disabled) if the expression is false. You could even use a boolean entry in the model, e.g. render the template with
model="[teacherInstance: teacherInstance, editing:true]"
(or editing:false respectively) and then say disabled="${editing}" on the <g:select>.

Resources