Formatting POJO inside Grails/GSP select element - grails

I have the following POJO/POGO:
class Person {
String firstName
String lastName
int age
// ... lots of other fields
}
And a Grails 2.3.6 controller:
class PeopleController {
List<Person> people = new ArrayList<Person>()
def populatePeople() {
// Add lots of people to the 'people' list.
}
def doSomething() {
populatePeople()
render(
view: "people",
model:[
people: people,
]
)
}
}
And then in the GSP:
<div id="peopleSelector">
<g:select name="people" from="${people}" />
</div>
When I run my app I get the <select> element with com.me.myapp.domain.Person#398r4d99-looking values as <option>s. This is obviously Grails not deserializing my Person instances into pretty print form.
I want peoples' first and last names to appear as the select options. Hence, if one of the Person instances in the people list is:
Person smeeb = new Person(firstName: "Smeeb", lastNname: "McGuillocuty")
Then I would expect "Smeeb McGuillocuty" as a select option in the final HTML. How can I accomplish this?

Add the following method to your Person class:
#Override public String toString() {
"$firstName $lastName"
}
And, somewhat unrelated to the actual question, you may have to add an identifier to your option rows to uniquely identify the person. Assuming the Person class has an id property:
<g:select name="people" from="${people}" optionKey="id" />
so that you get the following HTML:
<select name="people" id="people">
<option value="123">Smeeb McGuillocuty</option>
:
Useful link to official doc: http://grails.org/doc/latest/ref/Tags/select.html:
"..The default behaviour is to call toString() on each element in the from attribute.."

If you can't/won't "sacrifice" toString() for rendering in HTML you can also tell the g:select how to render the options. Either by providing the name of a property in optionValue (e.g. optionValue="fullName" and then provide a String getFullName() method (watch out for transients, if you pass a GORM object)) or by providing it directly in the GSP:
<g:select name="person" optionKey="theId" optionValue='${{"$it.lastName, $it.firstName"}}' from="${people}" />

Related

Filtered results fields in grails

Hi I am new in Grails and would like to find out how do I have filtered results of two fields in grails. Should i do it in view, or should i do it in domain class? How do i do it?
In the following example, I have 3 classes:
Country
State
Address
The field State should display only filtered results based on Country. Right now all the states in the State object is presented. The following screenshot depicts this problem.
All the states are listed
The following are my codes.
class Country {
static constraints = {
}
String countryName
static hasMany = [state : State ]
}
class State {
static constraints = {
}
String stateName
static belongsTo = [ country : Country ]
}
class Address {
static constraints = {
}
String line1
String line2
Country country
State state
}
Any help is appreciated. Thank you so much.
Are you using scaffolding?
If so Grails is going to render all of the appropriate form controls unless you exclude them e.g.
static constraints = {
country display:false
}
However I don't think you need to associate Country with Address because there's an association between Address and State which also has an association with a Country so the association between an Address and a country will be implicit after selecting a state.
EDIT 1
If you want to keep the current model you'll probably have to move away from scaffolding and implement the action and gsp yourself e.g.
controller:
def create() {
def states
if ( params.country ) {
states = State.findByCountry(params.country)
}
[countries: Country.all, states: states]
}
gsp:
<g:select class="form-control"
name="country"
from="${countries}"
optionKey="${it}"
value="${params.country}"
required="required"
onchange="submit()"
noSelection="['':'Select country']" />
<g:if test="${params.country}">
<g:select class="form-control"
name="state"
from="${states}"
optionKey="${it}"
value="${params.state}"
required="required"
noSelection="['':'Select state']" />
</g:if>
The above should be treated as pseudo code to give an idea of possible solution

GORM: populate list into select dropdown

I am working on a Grails form item that just populates a list of FaqCategories from an FaqCategory domain class. All I want the select dropdown to do is display the list. So far, this sort of works, but I'm not sure how to generate the list or what to put in the Value section if anything.
Form item:
<div class="col-sm-9">
<g:select name="Category" from="${FaqCategory.list()}" required="" class="form-control" aria-labelledby="faqCategory-label" value=""/>
</div>
Domain class #1
class Faq {
String question
String answer
FaqCategory faqCategory
static constraints ={
question nullable:false, maxSize:1000
answer nullable:false, maxSize:1000
faqCategory nullable:false
}
}
Domain class #2
class FaqCategory {
String categoryType
String toString(){
"$categoryType"
}
static constraints = {
categoryType nullable:true, maxSize:100, unique:true
}
}
Controller snippet
#Transactional(readOnly = true)
def create() {
respond new Faq(params)
}
Controller code :
yourAction(int id){
//do some
def faqInstance = Faq.get(id)
render[view:"form",model:[faqInstance:faqInstance]]
}
View code :
<g:select name="faqCategory"
from="${FaqCategory.list()}"
value="${faqInstance?.faqCategory?.categoryType}"
optionKey="id" />
Hope it helps..
You haven't provided enough information to show the context in which this select is being used so it is hard to say exactly what you need, but something like this should work:
<g:select name="faqCategory"
from="${FaqCategory.list()}"
value="${you only need this if you want to preselect a value}"
optionKey="id" />
See http://grails.org/doc/latest/ref/Tags/select.html for more details.
I hope that helps.

Auto Binding a Grails One-To-Many Relationship

I'm having difficulty auto-binding a one-to-many relationship in Grails without resorting to some hack in the controller. I understand that a one to many relationship in Grails is a set which is unordered and somehow affects binding.
When I save this form, sometimes the data saves correctly, and sometimes it does not. If an author has 3-4 books, it seems that it works less often.
In this example, I've tried to remove all non-relevant code to illustrate the issue.
Models:
class Author {
String name
static hasMany = [ books:Book ]
}
class Book {
String title
static belongsTo = [ author:Author ]
}
View:
<g:form method="post" class="form-horizontal">
<g:hiddenField name="id" value="${authorInstance?.id}" />
<g:hiddenField name="version" value="${authorInstance?.version}" />
<g:textField name='name' value='${authorInstance?.name}'/>
<g:each var="book" in="${authorInstance.books}" status="i">
<g:hiddenField name='book[${i}].id' value='${book.id}'/>
<g:textField name='book[${i}].title' value='${book.title}'/>
</g:each>
<g:actionSubmit action="update" value="Update" />
</g:form>
Controller:
def update(Long id, Long version) {
def author = Author.get(id)
// removed "Not Found" and "Version" validation for this example
author.properties = params
if (!author.save(flush: true)) {
render(view: "edit", model: [author: author])
return
}
flash.message = "Success"
redirect(action: "list"
}
How can I structure my model and view so I can leave the controller relatively untouched?
I've struggled with similar issues submitting one-to-many forms. I solved it in my app by converting the set to a bag.
So unless you specifically need books to be a set, try this:
class Author {
String name
Collection<Book> books
static hasMany = [ books:Book ]
}
I found that the easiest thing to do was force "Books" to be a List so it's ordered.
class Author {
List books <------- Added (by default this one-to-many relationship is a Set)
String name
static hasMany = [ books:Book ]
}
Then the view can remain the same and everything should work as expected.

MVC Model Binding to a collection where collection does not begin with a 0 index

I'm trying to perform remote validation on a property of an item within a collection. The validation works OK on the first item of the collection. The http request to the validation method looks like:
/Validation/IsImeiAvailable?ImeiGadgets[0].ImeiNumber=123456789012345
However on the 2nd item where the url looks like below, the validation doesn't work
/Validation/IsImeiAvailable?ImeiGadgets[1].ImeiNumber=123456789012345
Now I'm pretty sure the reason for this, is that binding wont work on a collection that doesn't begin with a zero index.
My validation method has a signature as below:
public JsonResult IsImeiAvailable([Bind(Prefix = "ImeiGadgets")] Models.ViewModels.ImeiGadget[] imeiGadget)
Because I'm passing an item within a collection I have to bind like this yet what I'm really passing is just a single value.
Is there anyway I can deal with this other than just binding it as a plain old query string.
Thanks
Edit: This is the quick fix to get the Imei variable but I'd rather use the model binding:
string imeiNumber = Request.Url.AbsoluteUri.Substring(Request.Url.AbsoluteUri.IndexOf("=")+1);
Edit: Here is my ImeiGadget class:
public class ImeiGadget
{
public int Id { get; set; }
[Remote("IsImeiAvailable", "Validation")]
[Required(ErrorMessage = "Please provide the IMEI Number for your Phone")]
[RegularExpression(#"(\D*\d){15,17}", ErrorMessage = "An IMEI number must contain between 15 & 17 digits")]
public string ImeiNumber { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
You could write a custom model binder:
public class ImeiNumberModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var modelName = bindingContext.ModelName;
var request = controllerContext.HttpContext.Request;
var paramName = request
.Params
.Keys
.Cast<string>()
.FirstOrDefault(
x => x.EndsWith(modelName, StringComparison.OrdinalIgnoreCase)
);
if (!string.IsNullOrEmpty(paramName))
{
return bindingContext
.ValueProvider
.GetValue(request[paramName])
.AttemptedValue;
}
return null;
}
}
and then apply it to the controller action:
public ActionResult IsImeiAvailable(
[ModelBinder(typeof(ImeiNumberModelBinder))] string imeiNumber
)
{
return Json(!string.IsNullOrEmpty(imeiNumber), JsonRequestBehavior.AllowGet);
}
Now the ImeiGadgets[xxx] part will be ignored from the query string.
If you are posting the whole collection, but have a nonsequential index, you could consider binding to a dictionary instead
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
If you're only posting a single item or using a GET link, then you should amend to
/Validation/IsImeiAvailable?ImeiNumber=123456789012345
and
public JsonResult IsImeiAvailable(string imeiNumber)
If you are sending up a single value to the server for validation, then your Action Method should only accept a scalar (single-value) parameter, not a collection. Your URL would then look like this (assuming default routing table for {controller}/{action}/{id}:
/Validation/IsImeiAvailable?ImeiNumber=123456789012345
the corresponding action method signature could look like this:
/* note that the param name has to matchthe prop name being validated */
public ActionResult IsImeiAvailable(int ImeiNumber)
EDIT:
which you could then use to lookup whether that particular ID is available.
if you want to change the name of the parameter, you can modify the routing table, but that's a different topic.
The long story short of it is that if you wanted to do validate a collection of ImeiGadget, you'd GET or POST that full collection. For a single value, it doesn't make much sense to send up or to expect an entire collection.
UPDATE:
Based on new info, I would look at where the remote validation attribute is being placed. It sounds like it might be placed on something like an IEnumerable<IMEiGadgets>, like this:
[Remote("IsImeiAvailable", "Validation", "'ImeiNumber' is invalid"]
public IEnumerable<ImeiGadget> ImeiGadgets { get; set;}
Would it be possible to move that attribute and modify it to be on the ImeiGadget class instead, to be something like this?
[Remote("IsImeiAvailable", "Validation", "'ImeiNumber is invalid"]
public int ImeiNumber { get; set;}
In theory, you shouldn't have to change anything on your HTML templates or scripts to get this working if you also make the change suggested in my answer above. In theory.
Unless you need this binding feature in many places and you control the IsImeiAvailable validation method then I think creating a custom model binder is an over-head.
Why don't you try a simple solution like this,
// need little optimization?
public JsonResult IsImeiAvailable(string imeiNumber)
{
var qParam = Request.QueryString.Keys
.Cast<string>().FirstOrDefault(a => a.EndsWith("ImeiNumber"));
return Json(!string.IsNullOrEmpty(imeiNumber ?? Request.QueryString[qParam]), JsonRequestBehavior.AllowGet);
}
You can add an extra hidden field with .Index suffix to allow arbitrary indices.
View:
<form method="post" action="/Home/Create">
<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />
<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />
<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />
<input type="submit" />
</form>
Model:
public class Product{
public string Name{get; set;}
public decimal Price{get; set;}
}
Controller:
public ActionResult Create(Product[] Products)
{
//do something..
}
For more information refer to : You've Been Haacked: Model Bindig To A List

DRY in the MVC View

I've been working a lot with asp.net web forms and one think that I like about the is the consistency with the generated markup e.g. if you create a composite control for a TextField you can control the generated markup in a single class like and don't break the SRP:
<form:textfield id="firstName" runat="server" required="true" label="First Name" />
I you're your going to generate the markup by hand it might look like this:
<label for="firstName" id="lbl_firstName">Name <span class="required">*</span></label>
<input id="firstName" name="firstName" type="text" value="" />
The problem is when would like to change something for example add a wrapping div or move the span. In worst case you have to edit thousands of views.
That's why I really like the MVC Contrib FluentHtml.
<%= this.TextBox(x => x.Message.PostedBy).Class("required").Label("Name") %>
My question is what do you think is the best way to add a wrapping div for the code line above? I think hand writing is not an option because of the arguments above? Perhaps extending the TextBox : MvcContrib.FluentHtml.Elements.TextInput?
have you checked InputBuilder in MvcContrib project? it is used in Codecampserver as well. have a look and i think u will like it.
Honestly, I don't think the example case you've given applies to real world. A textbox is a textbox. If you need one, you render one.
If you need a more "complex" control like a textbox wrapped in a div tag, then you can have a partial view for that.
For example, Model :
public class CustomControlModel {
public string Name { get; set; }
public string Value { get; set; }
public string Class { get; set; }
public bool WrapInDivTag { get; set; }
//you get the idea
}
Custom Control :
<%# Control Inherits="System.Web.Mvc.ViewUserControl<CustomControlModel>" %>
<%if (Model.WrapInDivTag) {%> <div> <% } %>
<%=Html.TextBox(Model.Name, Model.Value, new { #class = Model.Class })%>
<%if (Model.WrapInDivTag) {%> </div> <% } %>
And when rendering :
<%Html.RenderPartial("CustomControl",
new CustomControlModel { Name = "name", WrapInDivTag = true }); %>
That's a very simple example but I hope it explains why I suggested partial views. Don't forget that you can expose another property to get which tag to render etc.
InputBuilders are one option. With FluentHtml you could create a custom element, something like this:
public class TextBoxInContainer : TextInput<TextBox>
{
public TextBoxInContainer (string name) : base(HtmlInputType.Text, name) { }
public TextBoxInContainer (string name, MemberExpression forMember, IEnumerable<IBehaviorMarker> behaviors) : base(HtmlInputType.Text, name, forMember, behaviors) { }
protected override ToString()
{
divBuilder = new TagBuilder(HtmlTag.Div);
divBuilder.InnerHtml = ToString();
return divBuilder.ToString(TagRenderMode.SelfClosing);
}
}
To use this from your view you would extend IViewModelContainer something like this:
public static MyTextBox TextBoxInContainer <T>(this IViewModelContainer<T> view, Expression<Func<T, object>> expression) where T : class
{
return new TextBoxInContainer (expression.GetNameFor(view), expression.GetMemberExpression(), view.Behaviors)
.Value(expression.GetValueFrom(view.ViewModel));
}
Then if you want to change your container to a span sitewide, you change the ToString method of TextBoxInContainer.

Resources