I am trying to take advantage of Grails' ability to handle data binding for a one-to-many association. I am able to successfully assigning to the relationship but removing single elements or all of them is not working.
Here is an example of what my model looks like:
class Location {
Float latitude
Float longitude
}
class Route {
Location start
Location stop
static belongsTo: [courier: Courier]
}
class Courier {
String name
static hasMany = [pickups: Location]
}
class ScheduledCourier extends Courier {
static hasMany = [routes: Route]
static mapping = {
routes(cascade: 'all-delete-orphan')
}
}
When creating a new ScheduledCourier object via a website, I can pass a list of Routes to automatically bind with markup like this:
<input type="hidden" name="routes[0].latitude" value="5" />
<input type="hidden" name="routes[0].longitude" value="5" />
<input type="hidden" name="routes[1].latitude" value="10" />
<input type="hidden" name="routes[1].longitude" value="10" />
This works for me just fine in my controller:
class CourierController {
// Very simplistic save
def saveScheduled = {
def courier = new ScheduledCourier(params)
courier.save()
}
// Very simplistic update
def update = {
def courier = Courier.get(params.id)
courier.properties = params
courier.save()
}
}
If I use the following markup instead, I can step through the debugger and see that the routes property is now [] and the object saves fine but the records are not removed from the database.
<input type="hidden" name="routes" value="" />
In addition, if I sent markup like this:
<input type="hidden" name="routes[0].latitude" value="5" />
<input type="hidden" name="routes[0].longitude" value="5" />
courier.routes will not be updated to only contain the 1 object.
Has anyone seen this behavior?
This is Grails 1.3.7...at least for now.
Wrote an integration test that reproduces this behavior:
public void testCourierSave() {
def l1 = new Location(latitude: 5, longitude: 5).save(flush: true)
def l2 = new Location(latitude: 10, longitude: 10).save(flush: true)
def params = ["name": "Courier", "pickups[0].id": l1.id, "pickups[1].id": l2.id,
"routes[0].start.id": l1.id, "routes[0].stop.id": l2.id,
"routes[1].start.id": l2.id, "routes[1].stop.id": l1.id]
def c1 = new ScheduledCourier(params).save(flush: true)
assertEquals(2, c1.routes.size())
params = [routes: ""]
c1.properties = params
c1.save(flush: true)
c1.refresh() // Since the Routes aren't deleted, this reloads them
assertEquals(0, c1.routes.size()) // Fails
assertEquals([], Route.findAllByCourier(c1)) // Fails if previous assert is removed
}
I wonder if the following is happening:
When passing the params [routes:""] the framework is ignoring it as it's just an empty string.
Similarly <input type="hidden" name="routes[0].latitude" value="5" /> probably just updates the zeroth route entry in the collection, the others aren't deleted because all you've told it is that the latitude value of the zeroth route should be 5, not that this is should now be the only route in the collection.
To get the effect you want, you'll need to add a routes.clear() before binding the parameters.
To control when the state of the model is persisted to the database you can use Spring transactionality which is available in Grails. This would allow you to revert to the original state of the object if subsequent processing failed. eg:
Courier.withTransaction {status ->
// Load the courier
//clear the existing routes
// bind the new properties
// perform extra processing
//if the object is invalid, roll back the changes
status.setRollbackOnly()
}
Related
I want the form to pass the values from the hidden inputs to the server and I also expected it to build the URL as
"localhost:9392/Ranking/Index/2?rankingType=SOMEVALUE&ageGroup=SOMEVALUE&week=SOMEVALUE"
but it shows like "localhost:9392/Ranking/Index/2?rankingType=rankingTypeID&ageGroup=ageGroupID&week=week"
#using (Html.BeginForm("Index", "Ranking", new { id = Model.CurrentRanking, rankingType = "rankingTypeID", ageGroup = "ageGroupID", week = "week"}, FormMethod.Post, new { id = "ageGroupForm" }))
{
<input id="ageGroupID" name="ageGroup" hidden />
<input id="rankingTypeID" name="rankingType" hidden />
<input id="week" name="week" hidden />
}
Why is that ? How do I pass the values and also have them show up as query string ?
Try this code:
#using (Html.BeginForm("Index", "Ranking"))
{
<input id="ageGroupID" name="ageGroup" hidden />
<input id="rankingTypeID" name="rankingType" hidden />
<input id="week" name="week" hidden />
}
and in your action method, decorate it like:
public ActionResult Index(string ageGroupID, string rankingTypeID, string week){}
but it shows like "localhost:9392/Ranking/Index/2?rankingType=rankingTypeID&ageGroup=ageGroupID&week=week"
That's because those are exactly the string values you're using:
new { id = Model.CurrentRanking, rankingType = "rankingTypeID", ageGroup = "ageGroupID", week = "week"}
It's not really clear why you're trying to set query string values for the exact form inputs you already have:
<input id="ageGroupID" name="ageGroup" hidden />
<input id="rankingTypeID" name="rankingType" hidden />
<input id="week" name="week" hidden />
If what you're trying to accomplish is to have those values be on the query string (as a GET request) instead of in the request body (as a POST request) then all you have to do is change your form method to a GET:
#using (Html.BeginForm("Index", "Ranking", new { id = Model.CurrentRanking }, FormMethod.Get, new { id = "ageGroupForm" }))
HTML form inputs are automatically added to the request, that's how forms work. You don't need to try to do it manually.
Though it's strange that you're doing this at all. Your inputs are hidden, which usually means you don't want to concern the user with them. But you're also showing them on the URL, which may be confusing to the user (or may even be suspicious to the user). You also aren't setting any values to those hidden inputs, unless you have something else not included here which does that?
Either way, your form will automatically include its inputs in the submitted request.
I need to pass some flag into a view and get it back.
But requirements are:
this flag is not part of model
and it must not be stored in session or such.
I mean - flag visibility scope should be - current page for current user only.
I tried to put it into a ViewBag like this:
public ActionResult Product_Add()
{
ViewBag.IsNew = true;
ViewData["IsNew"] = ViewBag.IsNew;
ViewBag.RecordActionName = "Add";
return View("Product_Edit");
}
And then use this code in *.cshtml:
<input type="hidden" name="IsNew1" value="#ViewBag.IsNew" />
<input type="hidden" name="IsNew2" value='#ViewData["IsNew"]' />
But in resulting HTML I see this:
<input type="hidden" name="IsNew1" value="value" />
<input type="hidden" name="IsNew2" value='value' />
Then I tried this code in *.cshtml:
#{ string isNewRec = ViewBag.IsNew.ToString(); }
<input type="hidden" name="IsNew1" value="#isNewRec" />
And now it works:
<input type="hidden" name="IsNew1" value="True" />
Question is - why first attempt put "value" text instead of boolean value into HTML?
Also the strange thing - this code works fine:
<input type="hidden" name="ProductID" value="#ViewBag.ProductID" />
So, for integer values in ViewBug it works, returns an actual interger value, but for boolean it returns "value" text.
Another part of question.
Currently I use code like this:
[HttpPost]
public ActionResult Product_Commit(Product pProduct)
{
if (ModelState.IsValid)
{
AbcProducts ctx = new AbcProducts();
bool isNew = Convert.ToBoolean(Request.Form["IsNew"]);
if (isNew)
ctx.Products.Add(pProduct);
else
{
// [...]
}
ctx.SaveChanges();
}
return View();
}
Could you recommend something more correct from MVC point of view to pass non-model parameter into a view and get it back then with submited data?
But this parameter should not be stored anywhere where other pages can see it somehow.
I have some hidden inputs that are generated dynamically using JQuery. For example :
<input type="hidden" name="name1" value="SomeValue1">
<input type="hidden" name="name2" value="SomeValue2">
The method
FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("name1")
returns the value SomeValue1 which is correct. But, at runtime I am not aware of the input names. How can I get all the hidden inputs without knowing their names ?
Thansk for help.
Give them all the same name, so that you can use getRequestParameterValuesMap() instead.
<input type="hidden" name="name" value="SomeValue1">
<input type="hidden" name="name" value="SomeValue2">
...
String[] names = externalContext.getRequestParameterValuesMap().get("name");
The ordering is guaranteed to be the same as in HTML DOM.
Alternatively, based on the incremental integer suffix as you've in the HTML DOM, you could also just get the request parameter in a loop until null is returned.
List<String> names = new ArrayList<>();
for (int i = 1; i < Integer.MAX_VALUE; i++) {
String name = requestParameterMap.get("name" + i);
if (name != null) {
names.add(name);
} else {
break;
}
}
I am trying to understand how web-services work and I think I need some help with my controller. For example, I am trying to add a user into my data base ... This is what I have:
public static Result addUser(){
DynamicForm form = Form.form().bindFromRequest();
String url = "http://my-url-qqq";
WSResponse response;
WSRequestHolder holder = WS.url(url);
holder.setHeader("Cookie", "sessionid="+ session("sessionid"));
Map<String,String> anyData = new HashMap();
JsonNode content = response.asJson();
// how can i put all this things togeter
//to put the elements from my form in
//my database ... ?
//and what is the role of all the pieces ?
return ok(index.render("Bello! Now you can log in!"));
}
And I have this model:
#Entity
public class registerForm extends Model {
//for registration
#Id
public String Id;
public String username;
public String first_name;
public String last_name;
public String password1;
public String re_password1;
....
}
routes:
GET /register controllers.Application.register()
POST /register controllers.Application.addUser()
and my html form:
<form action="#routes.Application.addUser()" method="POST">
<div class="col-md-offset-1 col-md-4">
<h3><b>Register : </b></h3>
<br>
Username :
<input type="input" name="username" class="form-control"><br>
First Name :
<input type="input" name="first_name" class="form-control"><br>
Last Name :
<input type="input" name="last_name" class="form-control"><br>
Email :
<input type="input" name="email" class="form-control"><br>
Password:
<input type="password" name="password" class="form-control"><br>
Repeat Password :
<input type="password" name="re_password" class="form-control"><br>
<input type="submit" class="btn">
<br><br><br><br>
<h2> ^_^ : have fun .</h2>
</div>
</form>
Can anyone explain/translate how to connect this things ?
I'll appreciate any kind of example ...
First dont use DynamicForm when your form has same structure like your entity class means DynamicForm are used when For ex. if you want to search user from database then your form will have only one field in that case you can use DynamicForm where you can search from predefined entity field.If your form have same field like your entity fields
Second I think you misunderstood entity i.e. entity is a POJO(Plane Old Java Object) your class represent a table in database and your entity name is registrationforn and I think thats not look good you should name your entity like User or Member.This is completely optional for you but it gives better understanding
To save data do
public static Result addUser(){
registerForm user = Form.form(registerForm.class).bindFromRequest().get;
user.save(); //and the data is saved
return ok(index.render("Hello! Now you can log in!"));
}
And delete, find entity etc check Play Ebean Sample Application.
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.