I have a registration form that contains fields related to two domain objects; User and Profile. The relationship is a 1:1 mapping owned by the User domain class.
A 'register' action on the User controller marshals the form values, and provided there are no validation errors, persists the user object and redirects to the applications root when the form is submitted. Otherwise, the controller will redirect back to the registration form showing pre-populated fields with failed values.
However, in practice, when validation fails, the failed values aren't displayed in the view. Below is the code for the register action:
def registration = {
}
def register = {
def user = new User()
bindData(user, params)
if (user.save()) {
flash.message = 'Successfully Registered User'
redirect(uri: '/')
}else {
flash.message = 'Registration Failed!'
redirect(action: registration, params: [ user: user ])
}
}
Below is an example html excerpt from the view showing User and Profile related fields:
<div class="row">
<label for="city"> City, State: </label>
<g:textField id="city" name="profile.city"
value="${user?.profile?.city}" size="28" />
<span class="red">*</span>
</div>
<hr />
<div class="row">
<label for="email"> E-mail address: </label>
<g:textField id="email" name="userId" value="${user?.userId}" size="28" />
<span class="red">*</span>
</div>
Syntactically, everthing looks okay; I'm using appropriate naming conventions and grail's interpolation for acessing values, so I'm at wits end as to why this isn't behaving as expected.
Any comments or suggestions would be appreciated.
Thanks,
-Tom
If i remember correctly i thought it was something in the lines of:
def user = new User()
user.properties = params
You need to somehow pass the submitted values from user in register action to user in registration action. Like this:
if (params.user) {
user.properties = params.user.properties
}
Try explicitly calling the erorr?
Ive been using this pattern to redirect back to the same form.
if (user.save()) {
...
} else {
return error()
}
I normally use command objects in webflows, so my normal pattern looks like:
def registerFlow = {
registerPage = {
on("submit") { FormDataCommand cmd ->
cmd.validate()
if (cmd.hasErrors()) {
flow.cmd = cmd
return error()
} else {
...
}
}
}
}
class FormDataCommand implements Serializable {
User u
Profile p
static constraints = {
u(validator: { it.validate() })
p(validator: { it.validate() })
}
}
Related
I have web page with the following HTML:
<div class="row">
#Html.ActionLink("Delete Study", "DeleteStudy", "Study", new {topic = #Model.Study.PartitionKey, subtopic = #Model.Study.RowKey}, new { #class = "btn btn-primary" })
#Html.ActionLink("View Studies", "StudyList", "Study", null, new { #class = "btn btn-primary" })
</div>
When the DeleteStudy link is clicked, the following controller method is called:
[Authorize]
public void DeleteStudy(string topic, string subtopic)
{
...
...
RedirectToAction("StudyList");
}
The DeleteStudy method is called and executes successfully, except for the Redirect. No redirect occurs. The StudyList method (which has an Authorization attribute) is never called. Am I doing something wrong?
You need to change
RedirectToAction("StudyList");
to
return RedirectToAction("StudyList");
However I recommend you make your Delete action a POST rather that a GET. You don't want this added to the browser history or allow the user to enter it in the address bar. At best it's just making an unnecessary call to delete something which no longer exists, and at worst may throw an exception depending on your code
#using (Html.BeginForm("DeleteStudy", "Study", new {topic = Model.Study.PartitionKey, subtopic = Model.Study.RowKey }))
{
#Html.AntiForgeryToken()
<input type="submit" value="Delete Study" /> // style it to look like a link if that's what you want
}
and change the method to
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult DeleteStudy(string topic, string subtopic)
I'm trying to post a message after a contact form, indicating to the user that their message has been sent after they click the submit button. I don't want to redirect to a different page or to return a different view inside my HTTP Post action method. How do I do something like that in ASP.NET MVC framework?
Below is my code sample:
#*contactus.cshtml*#
#model MySite.Models.ContactModel
#using (Html.BeginForm())
{
<div class="col-md-6">
<div class="form-group">
#Html.TextBoxFor(model => model.Name})
<p>#Html.ValidationMessageFor(model => model.Name)</p>
</div>
<div class="form-group">
#Html.TextBoxFor(model => model.Email)
<p>#Html.ValidationMessageFor(model => model.Email)</p>
</div>
<div class="form-group">
#Html.TextAreaFor(model => model.Message)
<p>#Html.ValidationMessageFor(model => model.Message)</p>
</div>
<div class="col-lg-12">
<button type="submit">Send Message</button>
</div>
</div>
}
#*ContactModel.cs*#
public class ContactModel
{
[Required(ErrorMessage = "* Please enter your name.")]
[StringLength(100, MinimumLength=3, ErrorMessage="* Please enter your full name.")]
public string Name { get; set; }
[Required]
[EmailAddress(ErrorMessage="* Not a valid email address.")]
public string Email { get; set; }
[Required]
public string Message { get; set; }
}
I only have a contact us form right now on my home/index page, and I don't want to redirect it to any other pages. I would like to display a message right below the Send Message button, but I'm not sure how to go about it using the action method below:
#*HomeController.cs*#
public ActionResult Index(ContactModel model)
{
if (ModelState.IsValid)
{
// this is my helper library, for brevity, I'm not copying it.
EmailHelper emailService = new EmailHelper();
bool success = emailService.SendEmail(model.Name, model.Email, model.Message);
return Content(success ? "success" : "no...something went wrong :(");
} else {
return View(model);
}
}
Right now this controller will return the string inside Content which replaces my entire page, and I would like the string to be returned below my contact form. Also, I have two sections on the same html page with Contact Form as the second one, when I return View(model), it automatically redirects to the first section, which isn't ideal... How do I tell the controller to only redirect it to the second section after the POST method? In addition, I feel like it would be more efficient if it didn't return the whole page... so is there a way to only return a Message string to the div?
You can place a hidden div on the page which will contain the message.
Then when your form has been submitted, capture the click event for your button, and use that to display the hidden message.
Let me know if you need a code example. Posting your form would help us answer you more specifically.
To only show the success message if the form is successfully sent, I would recommend setting a value in the ViewBag in the POST action of the controller and then returning that same page if you want to still have the same page showing. On the View itself, you could then place an If statement to test if the ViewBag variable contains a value and if so, display the message.
Controller:
[HttpPost]
public ActionResult YourAction(YourModel m)
{
//Do stuff to send the contact form
...
if(error)
{
ViewBag.Message = "There was a problem sending the form.";
}
else
{
ViewBag.Message = "The form was sent successfully!";
}
return View(m);
}
View:
#if(ViewBag.Message != null)
{
<div>#ViewBag.Message</div>
}
This lets you check if the form was posted successfully on the server before telling the user the result and will only display a message if ViewBag.Message has been set. Note that you can have as many ViewBag variables as you want and can name them whatever you want... just remember which one you use in which place.
EDIT:
Following the comments, this could also be done using an AJAX call. I'll use the jQuery .post() method for simplicity sake.
In Script:
<script>
$(document).on('click', "#buttonId", function() {
var nameText = $("#IdOfNameField").val();
var emailText = $("#IdOfEmailField").val();
var messageText = $("#IdOfMessageField").val();
$.post('#Url.Content("~/Controller/AJAXPostContactForm")',//the url to post to
{name: nameText, email: emailText, message: messageText }, //these are values to be sent to the action
function(){ //this is the success function
$("#successMessage").val("Form posted successfully.");
}
)
.fail(function() {//failure function
alert("Something went wrong.");
});
}
</script>
Controller:
public void AJAXPostContactForm(string name, string email, string message)
{
try
{
//do stuff with the information passed into the action
}
catch(exception e)
{
//Handle errors. If error thrown, Ajax should hit fail block in script
}
finally
{
//do any cleanup actions
}
}
View:
<div id="successMessage"></div>
I have not tested this code but it should theoretically work. On a specific button click, it will get the values from the form fields, post those values to a specialized ActionResult in the controller, and then return a message about what happened.
I have the following domain class:
class User {
String name
String contactName
String primaryEmail
String url
String phoneNumber
String address
static hasMany = [users: User]
static constraints = {
name blank: false
contactName blank: false
primaryEmail email: true
url blank: false
phoneNumber blank: false
address blank: false
}
}
And controller for the User:
class UserController {
def create() {
User user = new User()
[user: user]
}
def save(User user) {
if (!user.save(flush: true)) {
render (view : 'create', model: [user: user])
}
redirect action: 'create'
}
}
I want show validation errors in case if validation fails. My create.gsp looks like this:
<body>
<g:form action="save" >
<g:renderErrors bean="${user}"/>
<g:textField name="user.name" id="message" value="${user.name}"/>
<g:textField name="user.contactName" id="contactName" value="${user.contactName}"/>
<g:textField name="user.primaryEmail" id="primaryEmail" value="${user.primaryEmail}"/>
<g:textField name="user.url" id="url" value="${user.url}"/>
<g:textField name="user.phoneNumber" id="phoneNumber" value="${user.phoneNumber}"/>
<g:textField name="user.address" id="address" value="${user.address}"/>
<g:submitButton name="submit" value="Save"/>
</g:form>
</body>
</html>
But after sumbit of create.gsp with invalid data two strange thing happen
1) Despite the fact that all fields have value property mapped to some field of User bean all fields are empty
2) There are no validation errors on the page
What I'm doing wrong?
Thank you!
you must return after calling render() or use else
def save(User user) {
if (!user.save(flush: true)) {
render (view : 'create', model: [user: user])
return // either return here
}else // or else here
redirect action: 'create'
}
In your original code you redirect to create and pass no models into it
I have a partial view which has a Ajax.BeginForm, with a UpdateTargetID set. When the validation on the form fails the update target id is replaced with the validation errors, but when there are no validation errors users should be redirected to a new page.
The code in my Partial view is
<div id="div_UID">
<% using (Ajax.BeginForm("FindChildByUID", new AjaxOptions { UpdateTargetId = "div_UID" } ))
{%>
<p>
<label>UID:</label>
<%= Html.TextBox("UID") %>
</p>
<input type="submit" value="Continue" />
<% } %>
</div>
</pre>
The code in my controller is as follows
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FindChildByUID(Student student)
{
Student matchingStudent = _studentService.FindChildByUID(student.UID);
if (matchingStudent == null)
{
ModelState.AddModelError("UID", String.Format("No matching child found for the entered UID: {0}", student.UID));
return PartialView();
}
else
{
// full view
return RedirectToAction("ConfirmChildDetails", matchingStudent);
}
}
So, for I have been unsuccessful to display the full view on it's own, as it always seems to dipslay the full view in the UpdateTargetID div specfied in the Ajax.BeginForm.
Any suggestions on how I can get this to work?
Thanks
What your AJAX post is doing is making a request and waiting on a response that contains html to input onto the page. The configuration is such that whatever html is returned will be injected into the div you've named "div_UID".
I typically avoid scenarios like this and use traditional posting if a redirect is required upon a successful outcome of the POST.
I imagine you could do it like this using jQuery to submit rather than the Ajax.BeginForm (or just set a callback function for your Ajax.BeginForm):
function SubmitForm(form) {
$(form).ajaxSubmit({ target: "#div_to_update", success: CheckValidity });
}
function CheckValidity(responseText) {
var value = $("#did_process_succeed").val();
if (value == "True") {
window.location.replace("url_of_new_action_here");
}
}
You just have to have a hidden field in your partial view called "did_process_succeed" and set the value of True or False based on some logic in your controller.
There are likely other ways as well. Perhaps someone else will chime in. I hope this helps for now.
I'm having an issue where my validation messages are showing up fine on an add operation, but when it comes to the update page, the validation messages are not showing:
This is my action, IsValid is coming out as false, and action redirects to the edit view, but none of the validation messages are shown. Is there something wrong in my approach?
[Authorize]
public ActionResult UpdateCar(CarDTO car)
{
try
{
_carTask.Update(car); //required Name field not set
}
catch (RulesException ex)
{
ex.AddModelStateErrors(ModelState, null);
}
if (!ModelState.IsValid)
{
return RedirectToAction(ViewNames.EditCar, new {carKey = car.carKey});
}
return RedirectToAction(ViewNames.Home, new {carKey = car.carKey});
}
<li>
<label for="Name">Car Name:</label>
<%= Html.TextBoxFor(x => x.Name, new { watermark="Car Name" })%>
<br />
<%= Html.ValidationMessage("Name") %>
</li>
If the form is invalid then you are redirecting to a new page which will loose any modal error values you set. Instead just return the View. Haven't checked the syntax but something like the below.
if (!ModelState.IsValid)
{
return View(ViewNames.EditCar, new {carKey = car.carKey});
}
return RedirectToAction(ViewNames.Home, new {carKey = car.carKey});