tymeleaf checkbox without binding to an object or entity - spring

i need to use check box to fire off a java if statement without having to bind the check box to any entity or object here is what i need in code
<input type="checkbox" th:field="${deleteImages}" th:checked="*{false}">Delete all images</input>
the controller would be this
public String updateProduct(#ModelAttribute("product") Product product,
#ModelAttribute("deleteImages") Boolean deleteImages) {
if (deleteImages) {
imageService.deleteImages(savedProduct.getId());
}
i can not figure out how to fix this checkbox.. i don't need it tied up to any object just return true when clicked and false when unchecked

If you want to do this without binding it to an object, just use RequestParams. Like this:
Form:
<form method="POST" class="action-buttons-fixed">
<input type="checkbox" name="deleteImages" />
<button>Go</button>
</form>
Java:
#PostMapping("/whatever")
public String updateProduct(#RequestParam(required = false, defaultValue = "false") boolean deleteImages) {
if (deleteImages) {
imageService.deleteImages(savedProduct.getId());
}
}
You can't, however, mix bound and unbound properties on the same form. If you want to do that, you should just create a new Form object that contains the both the product and the deleteImages variable.
class Form {
private Product product;
private boolean deleteImages;
}
And then change your form bindings appropriately.

Related

Spring MVC return object from JSP

I have a Spring MVC controller that sends a list to the view:
modelAndView.addObject("myList", List<foo>);
On the JSP, I iterate over this list creating a table where each row is a form with a submit button representing one foo instance. How can I get the single instance of foo represented by the row into the next controller? I tried putting it in an input type="hidden" but that didn't work.
A JSP is processed and rendered into HTML, which is just text.
When you submit a form, the browser takes the values of the <input> fields and serializes them in a specific format. With the typical application/x-www-form-urlencoded content type, the following <input> elements
<form action="/some/url" method="POST">
<input name="someField" value"someValue" type="text" />
<input name="someOtherField" value"someOtherValue" type="text" />
<input name="submit" value"submit" type="submit" />
</form>
The serialized format will look something like
someField=someValue&someOtherField=someOtherValue&submit=submit
Spring will take this String from the body of the request as request parameters and try to reconstruct your command object (ex. foo) from its values. The request above could be mapped to a class like
class Foo {
private String someField;
private String someOtherField;
public String getSomeField() {
return someField;
}
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeOtherField() {
return someOtherField;
}
public void setSomeOtherField(String someOtherField) {
this.someOtherField = someOtherField;
}
}
Any fields it can't map, it ignores.

ASP.NET MVC 3 - Custom client side validation not working

I'm trying to implement a custom client side validation, but it is not working. I'm basing myself on the article on Codeproject http://www.codeproject.com/Articles/275056/Custom-Client-Side-Validation-in-ASP-NET-MVC3
I also looked here on SO, but I think I'm implementing it in the correct manner, but I'm overlooking something.
My goal is to validate a date (required, date format and not earlier than another date on the form). The first two can be done with data annotations, the last I have to do with custom validation.
I have on my base class some dataannotations (ClassLibrary is in VB.NET):
Imports System.ComponentModel
Imports System.ComponentModel.DataAnnotations
<MetadataType(GetType(CM_CONTRACTVALIDATIONData))>
Partial Public Class CM_CONTRACTACTIVATION
'...
End Class
Public Class CM_CONTRACTVALIDATIONdata
'...
<DataType(DataType.Date)>
<Required()>
Public Property TakeBackDeviceWhen
'..
End Class
In the javascript file I have added the custom method:
//validation
$.validator.addMethod("checkPickupDate", function (value, element) {
return false;
});
$("#form").validate({
rules: {
TakeBackDeviceWhen: {
checkPickupDate: true
}
},
messages: {
TakeBackDeviceWhen: {
checkPickupDate: "Test"
}
}
}
);
My chtml file is as follow:
#Html.TextBox("TakeBackDeviceWhen", Model.TakeBackDeviceWhen.HasValue ? Model.TakeBackDeviceWhen.Value.ToShortDateString() : "", new { style = "Width: 200px" })
The resulting HTML is as follow:
<input id="TakeBackDeviceWhen" class="hasDatepicker" type="text" value="" style="Width: 200px" name="TakeBackDeviceWhen" data-val-required="The TakeBackDeviceWhen field is required." data-val="true">
It seems that neither my type validation and my custom validation isn't implemented.
What is going wrong?
OK, solved it. I hope :-)
What did I learned today:
(1) Don't use EditorFor: when you scaffold it from a MVC template, input fields are generated to EditorFor, it seems that you can't add custom unobtrusive validation tags. So, I was trying to get this fixed, untill I changed it to TextBoxFor.
(2) You can add custom validation methods in jQuery, but you can't mix them with unobtrusive validation. After adding a custom method, you have to also add it to the unobtrusive adapters. And don't forget to add jQuery on the bottom :-s (I got this from jQuery.validator.unobtrusive.adapters.addMinMax round trips, doesn't work in MVC3)
$(function () {
$.validator.addMethod("checkpickupdate", function (value, element) {
if (value == "20/09/2012") {
return false;
} else {
return true;
}
});
$.validator.unobtrusive.adapters.addBool("checkpickupdate");
} (jQuery));
(3) Add validation tags to the input field in the htmlAttributes:
#Html.TextBox("TakeBackDeviceWhen", Model.TakeBackDeviceWhen.HasValue ? Model.TakeBackDeviceWhen.Value.ToShortDateString() : "",
new {
style = "Width: 200px",
data_val = "true",
data_val_required = "verplicht!",
data_val_date = "moet datum zijn",
data_val_checkpickupdate = "wow"
})
(4) Datatype data annotations will not enforce a validation. You have to add it like in (3). You can add a custom ValidationAttribute like (for server side validation):
public class MustBeDateAttribute : ValidationAttribute {
public override bool IsValid(object value) {
try
{
DateTime dte = DateTime.Parse(value.ToString());
return true;
}
catch (Exception)
{
return false;
throw;
}
}
}
And this is the resulting html output:
<input type="text" value="" style="Width: 200px" name="TakeBackDeviceWhen" id="TakeBackDeviceWhen" data-val-required="required!" data-val-date="has to be a date" data-val-checkpickupdate="custom error" data-val="true" class="hasDatepicker valid">
As I'm using my ClassLibrary in different projects, I'm now going to try to seperate the dataannotations meta data from the class library (maybe with dependency resolver).

Handling Multiple forms of the same type - impossible?

Consider the following scenario:
My form model
public class PersonForm {
#NotNull
private String name;
/*usual getters and setters*/
}
My controller:
#Controller
#SessionAttribute(types={ PersonForm.class })
public class MyController {
#RequestAttribute(...)
public String render(final ModelMap map) {
/* get list of info and for each info
* create a PersonForm and put it in the modelmap
* under key p0, p1, p2, ..., pn
*/
}
public String submit(final ModelMap map,
#Valid final PersonForm form,
final BindingResult result) {
if (result.hasErrors()) {
// return to page
} else {
// do necessary logic and proceed to next page
}
}
}
And finally my JSP view
...
<c:forEach ...>
<form:form commandName="p${counter}">
... other form:elements and submit button goes here
</form:form>
</c:forEach>
...
As you can see I am trying to handle multiple forms of the same class type. The submit works -- it gets me to the submit(...) method just fine, and so does the validation. However re-rendering the page does not show me the expected error messages!
Even worse -- I checked what is being passed in the submit header and there is no indication whatsoever which form submitted, so there is no way to discriminate between one form on another. This led me to believe multiple forms of the same class type is not possible ...
Is there any other way I could do this (apart from Ajax) ?
Many thanks.
I managed to get this 'hack' to work. It is as what jelies has recommended so the credit goes all to him.
In simple terms, the concept is to pre-fill your view using the traditional <c:forEach> construct. The tricky part is whenever the 'Submit' button of that respective row is pressed, all of the information must be injected into a hidden form and force-submitted to the Controller. If the screen is rendered again with some errors, the script must be responsible of injecting the values back to the respective rows including the errors.
1) My model
public class PersonForm {
private String id;
#NotNull
private String name;
/*usual getters and setters*/
}
2) My controller
#Controller
#SessionAttribute(/* the hidden form name, the person list */)
public class MyController {
#RequestAttribute(...)
public String render(final ModelMap map) {
/* get list of info and for each info
* create a PersonForm and put it in the modelmap
* under key p0, p1, p2, ..., pn
*/
}
public String submit(final ModelMap map,
#Valid final PersonForm form,
final BindingResult result) {
if (result.hasErrors()) {
// return to page
} else {
// do necessary logic and proceed to next page
}
}
}
3) My view
...
<form:form commandName="personForm" cssStyle="display: none;">
<form:hidden path="id"/>
<form:hidden path="name" />
<form:errors path="name" cssStyle="display: none;" />
</form:form>
...
<c:forEach var="p" items="${pList}">
<input type="text" id="${ p.id }Name" value="${ p.name }" />
<!-- to be filled in IF the hidden form returns an error for 'name' -->
<span id="${ p.id }nameErrorSpan"></span>
<button type="button" value="Submit" onclick="injectValuesAndForceSubmit('${ p.id }');" />
</c:forEach>
...
<script type="text/javascript">
injectValuesAndForceSubmit = function(id) {
$('#id').val( id ); // fill in the hidden form's id
$('#name').val( $('#'+id+'name').val() ); //fill in the hidden form's name
$('#personForm').submit(); //submit!
}
$(document).ready(function() {
var id = $('#id').val();
if (id.trim().length == 0) {
//Empty. Nothing to do here as this is a simple render.
} else {
//The page seems to be returning from some sort of error ... pre-fill the respective row!
$('#'+id+'name').val($('#name').val());
var hiddenNameErrorSpan = $('#name.errors');
if (hiddenNameErrorSpan) {
$('#'+id+'nameErrorSpan').text(hiddenNameErrorSpan.html());
}
} //else
}
</script>
As you can see the view has the hairiest parts -- hopefully it will still proves to be useful for anyone who (unfortunately) comes across the same situation as mine. Cheers!
IMHO having multiple forms makes things overcomplicated (or at least with spring). Also, you are using multiple forms but only one is going to be submitted.
So, I suggest that the easiest way to manage this is using a unique hidden external form with person properties. When one of the buttons is pressed, fill accordingly the person properties of form and submit it. With this you are achieving the tipical spring form submit/validation.
Maybe this solution requires a bit work with JavaScript, but I don't know how to handle spring-mvc with multiple forms, I always tried to avoid it, due to previous unsuccessful attemps.

MV3 Duplicate Query String Values for CheckBox (true,false for boolean)

I've created a fairly straight forward page with a check box:
#using (Html.BeginForm("MyController", "MyAction", FormMethod.Get))
{
#Html.CheckBoxFor(x => x.MyCheckBox)
<input type="submit" value="Go!" />
}
The URL is populated with the MyCheckBox value twice!? As such:
MyAction?MyCheckBox=true&MyCheckBox=false
It only duplicates the value if the check box is true. If set to false it will only appear once in the query string.
The code above is simplified as I have a couple of drop downs and a textbox on the form which work fine. I don't think there's anything unusual about the code which I've left out from this question.
Has anyone had a similar issue with query string parameters being duplicated?
This behaviour is by design of the checkbox control. The standard HTML checkbox control passes no value if it is not checked. This is unintuitive. Instead, the ASP.Net checkbox control has 2 elements, the standard control which is visible and also a hidden control with a value of 'False'.
Therefore, if the checkbox is not checked, there will be one value passed: False.
If it is checked, there will be two values, True and False. You therefore need to use the following code to check for validity in your code:
bool checkboxChecked = Request.QueryString["MyCheckBox"].Contains("True");
Accepted answer is correct however in my case in a recent development the MVC behaviour is misleading.
The MVC Html.CheckBox(...) and Html.CheckBoxFor(...) generate an extra input of 'type=hidden' with the same ID as the checkbox control, leading to the duplicate URL parameters. I got around this problem by simply including the mark up desired as follows:
#if(checkTrue){
<input type="checkbox" id="MyCheckBox" name="MyCheckbox" checked="checked">
}else{
<input type="checkbox" id="MyCheckBox" name="MyCheckbox">
}
Would be better wrapped upin a helper to use in place of the MVC code so the value check is encapsulated.
As part of my application, the controller maintains sets of query parameters using both form injection and link injection using helpers in order to preserve state (of paging/filtering controls for example) when clicked to navigate within the same controller scope. As a result of this feature, the check box element is always set back to false if the standard MVC helpers are used. It's a good thing I noticed and did not waste much time on this bug.
In my model, I had a collection of checkboxes like so:
public class PrerequisitesViewModel
{
public List<StudentPrerequisiteStatusViewModel> PrerequisiteStatuses { get; set; }
}
public class StudentPrerequisiteStatusViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
In order to get everything to bind correctly, I had to actually convert the values from the querystring and parse them manually with the following code:
// fix for how MVC binds checkboxes... it send "true,false" instead of just true, so we need to just get the true
for (int i = 0; i < model.PrerequisiteStatuses.Count(); i++)
{
model.PrerequisiteStatuses[i].IsSelected = bool.Parse((Request.QueryString[$"PrerequisiteStatuses[{i}].IsSelected"] ?? "false").Split(',')[0]);
}
Alas, it works, but I can't believe this is necessary in MVC! Hopefully, someone else knows of a better solution.
I solve this issue with use #Html.HiddenFor
<input id="checkboxId" type="checkbox" value="true" onchange="changeCheckboxValue()">
#Html.HiddenFor(m => m.MyCheckBox, new { #id = "hiddenId" } )
<script>
function changeCheckboxValue() {
document.getElementById("checkboxId").value = document.getElementById("hiddenId").checked;
}
</script>

Correct way to bind an mvc3 radiobutton to a model

I have a view that contains a radiobutton list for my terms and conditions of the site.
e.g.
Yes
#Html.RadioButtonFor(model => model.TermsAndConditions, "True")
No
#Html.RadioButtonFor(model => model.TermsAndConditions, "False",
new { Checked = "checked" })
</div>
#Html.ValidationStyledMessageFor(model => model.TermsAndConditions)
All is ok if the user completes the form without any errors however if I do serverside validation and the page is refreshed I lose the selection that the user made for the radiobutton and the selected radio goes back to the default false field.
How am I meant to be binding the radiobutton so if a user selects true this value is maintained even after serverside validation?
Any suggestions would be great!
For the short answer, you need to do three things:
Remove the new { Checked = "checked" } from the second radio button. This hard-coded checked value will override all of the magic.
When you return your ViewResult from the controller action, give it an instance of your model class where TermsAndConditions is false. This will provide the default false value you need in order to have the false radio button preselected for you.
Use true and false as the values for your radio buttons instead of "True" and "False". This is because your property is of type bool. Strictly speaking, you coincidentally chose the correct string representations for true and false, but the value parameter for the RadioButtonFor method is of type object. It's best to pass in the actual type you want to compare to rather than converting it to a string yourself. More on this below.
Here's what's going on in depth:
The framework wants to do all of this for you automatically, but you did those first two things incorrectly which makes you have to fight with the framework to get the behavior you want.
The RadioButtonFor method calls .ToString() on the value of the property you specified and compares it to the .ToString() of the value you passed in when creating the radio button. If they are equal, then it internally sets isChecked = true and ends up rendering checked="checked" in the HTML. This is how it decides which radio button to check. It simply compares the value of the radio button to the value of the property and checks the one that matches.
You can render radio buttons for pretty much any property this way and it will magically work. Strings, ints, and even enum types all work! Any object that has a ToString method that returns a string which uniquely represents the object's value will work. You just have to make sure you're settings the radio button's value to a value that your property might actually have. The easiest way to do this is just to pass in the value itself, not the string representation of the value. Let the framework convert it to a string for you.
(Since you happened to pass in the correct string representations of true and false, then those values will work as long as you fix your two actual mistakes, but it's still wise to pass in the actual values and not their strings.)
Your first real mistake was hard-coding Checked = "checked" for the "No" radio button. This will override what the framework is trying to do for you and results in this radio button always being checked.
Obviously you want the "No" radio button to be preselected, but you have to do it in a way that's compatible with everything above. You need to give the view an instance of your model class where TermsAndConditions is set to false, and let it "bind" that to the radio buttons. Normally, a controller action which responds to the initial GET request of a URL doesn't give the View an instance of the model class at all. Typically, you just return View();. However, since you want a default value selected, you must provide the view with a instance of your model that has TermsAndConditions set to false.
Here is some source code illustrating all of this:
Some sort of Account class that you probably already have. (Your View's model):
public class Account
{
public bool TermsAndConditions { get; set; }
//other properties here.
}
Some methods in your controller:
//This handles the initial GET request.
public ActionResult CreateAccount()
{
//this default instance will be used to pre-populate the form, making the "No" radio button checked.
var account = new Account
{
TermsAndConditions = false
};
return View( account );
}
//This handles the POST request.
[HttpPost]
public ActionResult CreateAccount( Account account )
{
if ( account.TermsAndConditions )
{
//TODO: Other validation, and create the account.
return RedirectToAction( "Welcome" );
}
else
{
ModelState.AddModelError( "TermsAndConditionsAgreement", "You must agree to the Terms and Conditions." );
return View( account );
}
}
//Something to redirect to.
public ActionResult Welcome()
{
return View();
}
The entire View:
#model Account
#{
ViewBag.Title = "Create Account";
}
#using ( Html.BeginForm() )
{
<div>
<span>Do you agree to the Terms and Conditions?</span>
<br />
#Html.RadioButtonFor( model => model.TermsAndConditions, true, new { id = "TermsAndConditions_true" } )
<label for="TermsAndConditions_true">Yes</label>
<br />
#Html.RadioButtonFor( model => model.TermsAndConditions, false, new { id = "TermsAndConditions_false" } )
<label for="TermsAndConditions_false">No</label>
<br />
#Html.ValidationMessage( "TermsAndConditionsAgreement" )
</div>
<div>
<input id="CreateAccount" type="submit" name="submit" value="Create Account" />
</div>
}
BONUS: You'll notice that I added a little extra feature to the radio buttons. Rather than just use plain text for the radio button labels, I used the HTML label element with the for attribute set to the IDs of the each radio button. This lets users click on the label to select the radio button instead of having to click on the radio button itself. This is standard HTML. For this to work I had to set manual IDs on the radio buttons, otherwise they would both get the same ID of just "TermsAndConditions", which wouldn't work.
There are a few things you need to do here in order to ensure the user's selection is maintained after server side validation.
a) Bind the "checked" property of each radio to your model in the view, for example:
Yes
#Html.RadioButtonFor(model => model.TermsAndConditions, "True", model.TermsAndConditions == true ? new { Checked = "checked" } : null)
No
#Html.RadioButtonFor(model => model.TermsAndConditions, "False", model.TermsAndConditions == false ? new { Checked = "checked" } : null)
b) To define the initial default value when the view is first displayed, initialise the model returned to the view in the GET request (in the controller action), for example:
public ActionResult SomeForm()
{
return View(new SomeModel { TermsAndConditions = false });
}
b) Ensure in your [HttpPost] controller action that you return the model when the validation fails, for example:
[HttpPost]
public ActionResult SomeForm(SomeModel model)
{
if (!ModelState.IsValid)
return View(model);
// Do other stuff here
}
This way when the view is rendered in the response after validation fails, it will have the actual model state that was passed in (thus maintaining the user's selection).
I can't really tell since you haven't shown your code, but I suspect that if you're failing on server side validation you're just returning the raw view. When it fails, you need to populate the view with the model that was submitted, same as if you were returning any other validation errors. Otherwise you'll get the default model values (which will always be false for the registration boolean).
Maybe you could post your server side code?
Here I am offering another more complex example.
public enum UserCommunicationOptions
{
IPreferEmailAndSMS = 1,
IPreferEmail = 2,
IPreferSMS = 3
}
Html
#model UserProfileView
// Some other code
<div class="form-group">
<label class="col-lg-2 control-label">Communication</label>
<div class="col-lg-10">
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmailAndSMS.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS)
}
<label class=" control-label" for="#Model.UserCommunicationOption">I Prefer Email And SMS</label>
</div>
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmail.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail)
}
<label class=" control-label" for="#Model.UserCommunicationOption">I Prefer Email</label>
</div>
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferSMS.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS)
}
<label class=" control-label" for="#Model.UserCommunicationOption">#DLMModelEntities.Properties.Resource.IPreferSMS</label>
</div>
</div>
</div>
Model
[Required(ErrorMessageResourceName = "Communications", ErrorMessageResourceType = typeof(Resource))]
[Display(Name = "Communications", ResourceType = typeof(DLMModelEntities.Properties.Resource))]
public UserCommunicationOptions UserCommunicationOption { get; set; }
GET
var client = AppModel.Clients.Single(x => x.Id == clientId);
if (Convert.ToBoolean(client.IsEmailMessage) && Convert.ToBoolean(client.IsSMSMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferEmailAndSMS;
}
else if (Convert.ToBoolean(client.IsEmailMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferEmail;
}
else if ( Convert.ToBoolean(client.IsSMSMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferSMS;
}
POST
[HttpPost]
public ActionResult MyProfile(UserProfileView model)
{
// Some code
var client = AppModel.Clients.Single(x => x.Id == clientId);
if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmail)
{
client.IsSMSMessage = false;
client.IsEmailMessage = true;
}
else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmailAndSMS)
{
client.IsSMSMessage = true;
client.IsEmailMessage = true;
}
else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferSMS)
{
client.IsSMSMessage = true;
client.IsEmailMessage = false;
}
AppModel.SaveChanges();
//Some code
}
Database
Webpage
I had a similar issue and solved the problem by setting a ViewData value in controller to keep track of what the user had selected.

Resources