I'm wondering, in the scaffold controller and views, how the fields you fill in the "create" page get updated to your domain class instance before the save action. I'm on Grails 2.4.4.
Take an example, I have one class called Customer and I generate the controller and views all in the default way.
class Customer {
String name;
String email;
String address;
String mobile;
}
And when you run the application and in the generated green-styled index page, click on "create new customer", one Customer instance will be created as the link goes to "create" action.
<ul>
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
</ul>
In controller:
def create() {
log.info "A customer object is created here.";
Customer c=new Customer(params)
respond c
}
But now you haven't filled in the form on all the fields yet! And after you fill in the form in create.gsp, the link will point you directly to "save" action.
<g:form url="[resource:customerInstance, 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>
But in the save action I see nothing related to setting the fields on this instance as the form goes. Where is it done?
#Transactional
def save(Customer customerInstance) {
if (customerInstance == null) {
notFound()
return
}
if (customerInstance.hasErrors()) {
respond customerInstance.errors, view:'create'
return
}
customerInstance.save flush:true
//omit everything after save here
}
Grails does this for you automatically with Data Binding. Grails controllers can take two categories of arguments: basic objects, and complex objects. The default behavior is to map HTTP request parameters to action arguments by name, wherever applicable. For example, say we have a controller like so:
def doSomething(Integer magicNumber) {
println "The magic number is $magicNumber"
}
and a view that contains a field like this:
<g:textField name="magicNumber" value="${magicNumber}" />
When the form is submitted to the doSomething action, Grails will automatically take the magicNumber request param, convert it from a String to an Integer and pass it to the action.
Complex types (like domain objects) are treated as command objects. Command objects function in a similar manner to basic objects in regards to data binding. Notice how in your code, the save action takes a Customer instance as an argument? Behind the scenes, Grails takes the HTTP request parameters and binds those parameters to the properties of a given object, in this case, a Customer. From the documentation:
Before the controller action is executed Grails will automatically create an instance of the command object class and populate its properties by binding the request parameters.
In other words, Grails will look at all of the incoming request parameters and attempt to bind those to properties on the object that you've stated as an argument to the action. In addition, it will perform validation for you. It's essentially the same as:
#Transactional
def save() {
Customer customerInstance = new Customer(params)
customerInstance.validate()
if (customerInstance == null) {
notFound()
return
}
if (customerInstance.hasErrors()) {
respond customerInstance.errors, view:'create'
return
}
customerInstance.save flush:true
//omit everything after save here
}
Related
Is there a way to change a value in the domain class by having the user input the new value ?
so if we have a view page, and an input box, whatever is entered in the input box will modify a specific value in the domain class
for e.g:
class User {
String userName
}
in views page:
<input type="userName" action="changeUsername">
Please take Alidad's advice:
Take a look at Grails scaffolding, it is a great resource to
understand how Grails works.– Alidad
That being said we can look at what is generated when running grails generate-all testapp.User and how it relates to your question.
generate-all with create a Controller and Views based off of your domain class User.
package testapp
class User {
String userName
static constraints = {
userName()
}
}
Notice the static constraints (this is a great place to add validation) but all you need in order to generate Views and controllers related to specific values is to add it to your static constraints within your domain class.
You want to be able to update a domain class.
Well the code to do this has been generated for us!
if you look at views/user/edit.gsp
<g:form url="[resource:userInstance, action:'update']" method="PUT" >
<g:hiddenField name="version" value="${userInstance?.version}" />
<fieldset class="form">
<g:render template="form"/>
</fieldset>
<fieldset class="buttons">
<g:actionSubmit class="save" action="update" value="${message(code: 'default.button.update.label', default: 'Update')}" />
</fieldset>
</g:form>
This will create a form to update the current user we have selected userInstance in this case.
Also an important observation is <g:form url="[resource:userInstance, action:'update']" method="PUT" > this tells us which controller will be called based on the resource and method will be called based on the action (You can look more into good design for controller calling here Grails WebServices REST)
This will call controllers/testapp/UserController.groovy - update which takes a User instance
#Transactional
def update(User userInstance) {
if (userInstance == null) {
notFound()
return
}
if (userInstance.hasErrors()) {
respond userInstance.errors, view:'edit'
return
}
userInstance.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: 'User.label', default: 'User'), userInstance.id])
redirect userInstance
}
'*'{ respond userInstance, [status: OK] }
}
}
Which you can see does some checks to make sure the instance exists and doesn't have errors (which is based off of the validation you add in your Domain Class) then saves your updated User Instance and then returns a message back. It is also a good practice to move the "work" out of your controller and into a service class.
But there are a lot of different ways to skin a cat. This communication between view and Controller to update a Domain class can be done in many different ways.
Well I hope that makes sense, just play around and have fun!
I have an ASP.NET MVC 5 project. I have some custom validation needs. I know that in the end, a possible result for my HTML looks like this:
<div class="form-group has-error">
<label for="field">Field Label</label>
<div id="field">
<input name="field" class="form-control" type="text" autocomplete="off">
</div>
<div>
Field label is invalid. You need to do something else.
</div>
</div>
The validation errors are stored in a Dictionary<string, string> in the ViewBag. I was thinking of doing something similar to the following:
#string fieldErrorMessage = getFieldError('field', ViewBag.ValidationErrors)
<div class="form-group #if(fieldErrorMessage.length >0) { 'has-error' } ">
<label for="field">Field Label</label>
<div id="field">
<input name="field" class="form-control" type="text" autocomplete="off">
</div>
#if (fieldErrorMessage.length > 0) {
<div>
#fieldErrorMessage
</div>
}
</div>
My problem is, I do not know where to define getFieldError. I would like to use this function in multiple views. So I'm not sure a. where to define it. or b. how to call it. I'm not even sure if my approach to applying 'has-error' is correct. I feel like I have pseudocode more than mvc code.
Embedded functions in the page
For including functions in the page, you have two options.
Extending HtmlHelper:
You can extend HtmlHelper so that you can call Html.getFieldError("field"). Because ViewBag is in HtmlHelper, you won't need to pass that into the function. The best way to demonstrate this is by showing an example.
public static class ErrorValidation
{
public static MvcHtmlString getFieldError(this HtmlHelper<TModel> h, string f)
{
if(h.ViewBag.ValidationErrors.ContainsKey(f))
{
return MvcHtmlString.Create(h.ViewBag.ValidationErrors[f]);
}
return MvcHtmlString.Empty;
}
}
Adding a namespace in your views:
You can include a namespace in your views by adding a line to the Views\Web.config file. Then, you could use static methods like you planned. This is done by adding a line of something like <add namespace="MyProj.Validation" /> inside of <configuration><system.web.webPages.razor><pages><namespaces>. In addition, you can leave this out by calling the full reference to your function each time with MyProj.Validation.getFieldError(...).
Relying on MVC error handling
You can also use API's already built into MVC, which do allow for customized validation.
Doing error checks through model attributes:
The most simple way to do validation is by adding attributes to your model. If your model had a required field, you can simply add [Required] above the field in the class that defines your model. A plethora of validation attributes are provided by System.ComponentModel.DataAnnotations.
If you wanted to do a custom check, you could create your own attribute by implementing abstract class ValidationAttribute. Simply define the IsValid function with your custom logic.
If you have validation checks that need to happen on multiple fields in your model at the same time, you can have your model implement IValidatableObject. And to make this simpler, in the Validate function you don't need to create a list and add all your errors to that; simply return each ValidationResult object by putting the keyword yield at the beginning of the line. This would look like...
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
// Duplicate checks
List<String> fields = new List<String>();
for (var i=0; i<PhoneNumbers.Count; i++)
{
var item = PhoneNumbers[i];
if (PhoneNumbers.IndexOf(item) != PhoneNumbers.LastIndexOf(item))
{
fields.Add("PhoneNumbers["+i+"]");
}
}
if(fields.Count > 0)
{
yield return new ValidationResult("You cannot include duplicate phone numbers.", fields.ToArray());
}
// More validation checks
}
Doing error checks in the controller:
You can also do error checks in the controller, which allows for validation to vary depending on the action. This also lets you use the validation that already happened in the model with the ModelState object. In order to add errors to ModelState, simply call ModelState.AddModelError(). In order to check if a model is valid after all checks are done, you can check ModelState.IsValid.
Displaying errors in the view:
With validation happening in the ModelState object, you can display errors in your view by using the Html object. This allows you to generate a list of errors by calling Html.ValidationSummary() or display errors for individual properties with Html.ValidationMessageFor(...). Here's an extensive example...
for (var x = 0; x < Model.PhoneNumbers.Count(); x++ )
{
<tr>
<td>#Html.EditorFor(m => m.PhoneNumbers.ElementAt(x))</td>
#if(ViewData.ModelState["PhoneNumbers[" + x + "]"].Errors.Any())
{
<td>#Html.ValidationMessageFor(m => m.PhoneNumbers.ElementAt(x))</td>
}
else
{
<td>Ok!</td>
}
</tr>
}
I have the following domain classes:
Airport:
class Airport {
String airportName
List<Flight> flights
static hasMany = [flights: Flight]
static mappedBy = [flights: "departureAirport"]
}
Flight:
class Flight {
String flightName
Integer numOfStaff
Airport departureAirport
}
I have in a form the following input fields which is correctly prints the saved airport and it's flights:
<input name="airportName" value="${airportInstance.airportName}" />
<input name="id" value="${airportInstance.id}" />
<input name="version" value="${airportInstance.version}" />
<g:set var="counter" value="${0}" />
<g:each in="${airportInstance?.flights?}" var="f">
<div>
name : <input name="flights[${counter}].flightName" value="${f.flightName}" />
id : <input name="flights[${counter}].id" value="${f.id}" />
numOfStaff : <input name="flights[${counter}].numOfStaff" value="${f.numOfStaff}" />
<g:set var="counter" value="${counter + 1}" />
</div>
</g:each>
I post the form to the following controller action:
#Transactional
def update() {
Airport airportInstance = Airport.get(params.id)
// doesn't work as well
// airportInstance.properties = params
bindData(airportInstance, params)
airportInstance.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(
code: 'default.updated.message',
args: [
message(
code: 'Airport.label',
default: 'Airport'
),
airportInstance.id
]
)
redirect airportInstance
}
'*'{ respond airportInstance, [status: OK] }
}
}
When I modify airportName or some of the flights properties (e.g.: numOfStaff) and I click update (post it to the controller action), Grails does his work correctly and updates my changes.
But if I manually add a new instance in the view e.g.: if there were two flights flights[0] and flights[1] listed by g:each and I add flights[2] it doesn't create a new instance of Flight and doesn't save it.
The same if I try to delete an existing flight from the view and post it to the controllers update action.
I'm using grails version 2.3.9
My questions are:
Am I doing something wrong?
Is it a Grails bug or Grails developers implemented bindData method like this for some reason?
What is the best way in Grails to implement issue like above (I mean giving the name attributes like foo[index].prop where foo is a collection in the entity).
Sorry for the junk html.
Thank you guys, for the answers.
I found the solution. If I declare a controller action like:
def update(Airport airport) {
// ...
}
In this case before the controller action is executed Grails will automatically create an instance of the Airport class and populate its properties by binding the request parameters.
If there is an id request parameter then instead of invoking the domain class constructor to create a new instance a call will be made to the static get method on the domain class and the value of the id parameter will be passed as an argument. Whatever is returned from that call to get is what will be passed into the controller action. This means that if there is an id request parameter and no corresponding record is found in the database then the value of the command object will be null.
See Grails Reference Documentation: http://grails.org/doc/latest/guide/theWebLayer.html#commandObjects
I'm a newbie trying to find uses for Neo4J on Grails.
Basically, I've made 20 grocery item nodes through the Neo4J browser and I want to create a simple Grails site that will let users search a grocery item and visually show the items related to it.
My index.gsp has:
<input id="item" />
My viz.js has:
$('#item').keyup(function() {
var item = $('#item').val();
My Item Domain class has
class Item {
static mapWith = "neo4j"
String name
My ItemController class has:
def index() {
def item = Item.list() [item:item] //No idea, just trying out whatever i find :(
and a query with something like:
def query = Item.cypherStatic ("""start n=node({Item}) match (n)-[r]->(x) where r='partner' return n, x)
Questions:
How can I properly send the JS 'item' variable into the ItemController?
How can I use the 'item' variable to properly query the node names which have a 'partner' relationship with the item?
in addition to Motilals answers, you definetly need a wrapping form with an action that points your controller
like
<g:form controller="itemController" action="index" >
<input type="text" id="item" name="item" value="" />
<input type="submit" value="submit" >
</g:form>
then on clicking submit the for will call your index action and there you could parse the value with
def item = params.item
but it looks more like you want some asynchronous stuff right after keyup-function, therefore you could do sth like this :
$('#item').keyup(function() {
var item = $('#item').val();
$.ajax({
url: "${createLink(controller:'itemController', action:'index')}",
data:"&item="+item
})
.done(function( data ) {
console.log(data)
});
});
in this case, you need to pay attention what your index-action is returning, so you can do in the .done() whatever you want with the response.
also note, that when you name an action "index" it will be available at
.../myproject/item/index
or, and thats important
.../myproject/item/
so if your index method requires the data from the input, it will miss them if a user has gone straight to that url
so your index action would rather render the page with the input
and you define another action for executing your query based on input and returning data
set the item to hidden field and then you can access it directly in your controller using params
here you go:
//in index.gsp add below hidden field and set the hidden filed in your js code
<g:hiddenField name="item" value="" />
$('#item').keyup(function() {
var item = $('#item').val();
//in your controller
def index() {
def item = params.item
print item // you see the value for item
//do your stuff
}
once you have item value you could directly use HQL query or use the domain instance
hope this helps you
Regards
Motilal
Basically I have a form that I am dynamically adding objects to. I am doing this with AJAX so can just initialise the object and return it with JSON. Each new object has a unique GUID assigned to it so we can identify each object in the model collection when it is passed back into the action.
However, I need to support non JavaScript so am trying to write a solution that will post back the model and add or remove the given object from the model. There can be any number of these new objects on the model so I need to pass back several things to find out which object to delete before returning the model back to the view. This could be either
a) The GUID for the object the user has deleted.
b) The button that has been clicked to identify which object to delete.
The problem is that the partial view is generic and I would like to keep it that way so I'm trying to pass the identifying GUID back with the input button on each partial view but don't know how. I can easily do this with JavaScript because I just remove the created AJAX object from the page before posting it when the user clicks the remove link but can't figure out how to do it with a submit. Basically I want to do something like this:
#using (Project.Namespace.Infrastructure.Helpers.HtmlPrefixScopeExtensions.HtmlFieldPrefixScope _scope = Html.BeginCollectionItem())
{
<ul class="ulMedicationsControl">
#Html.ActionLink("Remove This Object", "RemoveObject", null)
#Html.Input("RemoveObject", "Remove This Object", new { Prefix = _scope.Prefix, objectGUID = IdentifyingGUID })
#Html.HiddenFor(m => m.IdentifyingGUID);
<li class="liQuestion">
#Html.MandatoryLabelFor(m => m.myField)
#Html.TextBoxFor(m => m.myField)
</li>
</ul>
<div id="#(_scope.Prefix).ajaxPlaceholder"></div>
}
In the controller:
[ActionName("FormName")]
[AcceptParameter(Name = "RemoveObject", Value = "Remove This Object")]
public ActionResult RemoveObject(MyParentModel model, string Prefix, string objectGUID)
{
Guid ID = new Guid(objectGUID);
foreach (ObjectModel object in model.objects){
if (object.IdentifyingGUID == ID)
{
model.objects.Remove(object);
break;
}
}
return View(model);
}
Any help I would really appreciate as I simple can't figure out how to do this!
EDIT
Also just to add the prefix attribute simply identifies where in the form the object sits. This will be needed for me to find which object list to go through and remove the object from as there may be several lists in different placed in the model.
An HTML input only passes "name=value" when a form post occurs so that's all you have to work with. With <input type=submit> you're further limited by the fact that the button's value is its caption (i.e. "myControl=Click Me!" is posted), so you can't stick anything programmatically meaningful in the value.
Method 1: So you're left with encoding all the information you need into the input's name - an approach that works fine, but you'll have to have to go digging into the controller action method's FormCollection parameter rather than relying on model binding. For example:
<input name="delete$#(_scope.Prefix)$#objectGUID" type="submit" value="Delete me" />
Better, have a helper class that encapsulates the string format with a ToString override and has Parse/TryParse/etc static methods, which could be used like this:
<input name="#(new DeleteToken{Prefix=_scope.Prefix, objectGUID=IdentifyingGUID})" type="submit" value="Delete me" />
In your action method:
[HttpPost]
public ActionResult Foo(FormCollection formData)
{
var deleteTokens = DeleteToken.ParseAll(formData.AllKeys);
foreach (var token in deleteTokens)
{
//...do the deletion
}
}
Method 2: An alternative approach is to group each item into its own <form> (bear in mind you can't nest forms) - so when the submit happens, only its surrounding form is posted in which you can stash hidden inputs with the necessary data. e.g.
<ul class="ulMedicationsControl">
<form ... >
<!-- hidden field and submit button and whatever else here -->
...
</form>
</ul>