We have a ASP.NET MVC 3 application that uses unobtrusive jQuery validation. The page allows to add children objects to the model in the same go. The <form> contains a grid for the children, and some input fields for adding new children.
Simplified example with Issue as the Model and Subtasks as the children:
Issue.cshtml -> Defines the form and includes fields for the issue as well as its subtasks.
#model Issue
#using (Html.BeginForm("Create", "Issues", FormMethod.Post, new { id = "mainForm" })
{
#Html.TextBoxFor(model => model.Summary)
#Html.Partial("SubtaskFields", new Subtask())
#Html.Partial("SubtasksGrid", model.Subtasks)
}
SubtaskFields.cshtml:
#model Subtask
#Html.TextBoxFor(model => model.Summary)
<button id="add">Add</button>
SubtasksGrid.cshtml:
#model IEnumerable<Subtask>
<table>
#foreach (var subtask in Model)
{
<tr>
<td>
#subtask.Name
<input type="hidden" name="Subtasks[#subtask.Index].Name" value="#subtask.Name"/>
</td>
</tr>
}
</table>
The point is, when submitting the form, only the properties of the issue (Issue.Name, e.g.), plus the hidden fields for the children (Subtask.Name, e.g.) should be validated and submitted.
We have some javascript code that hooks on the "add" button, and adds a new subtask based on the values in the SubtaskFields.cshtml partial view. That script validates the input fields first. In order for this to work, we use the TextBoxFor etc. html helpers for the SubtaskFields.cshtml, too, rendering a dummy/default Subtask object (new Subtask()). Our javascript the uses $("#mainForm").validate().element(...) to validate the SubtaskFields before adding a new subtask.
The big problem with this approach is that the jQuery unobtrusive validation framework automatically hooks on the submit button and validates all fields within the form before submitting the form. I.e., even the subtask fields are validated. This does not make any sense. Say that the subtask name is mandatory (which means the user can only click on "add" if he has filled in a subtask name). But if the user does not click on "add", the values in the Subtask Fields don't have any meaning and can in particular be left blank. In this case, in our current setting, jQuery validation fails because a mandatory field was left blank.
How can this be solved?
This is what we've come up with:
Add an attribute to all subtask fields (which should not be validated when submitting the form), e.g. "data-val-ignore".
Set the ignore setting on the form's validator to "[data-val-ignore]"
For the add button, in order to validate the subtask fields (which are normally ignored), iterate over them, and for each field, remove the attribute, re-parse to genereate the rules, execute validation, add the attribute, parse one more time.
Ad 2:
$(document).ready(function () {
$.data($('form')[0], 'validator').settings.ignore = "[data-val-ignore]";
});
Ad 3:
$(allSubtaskFields).each(function() {
$(this).removeAttr("data-val-ignore");
$.validator.unobtrusive.parseElement(this, false);
if (!$("mainForm").validate().element($(this))) { result = false; }
$(this).attr("data-val-ignore", "true");
$.validator.unobtrusive.parseElement(this, false);
});
I would suggest moving #Html.Partial("SubtasksGrid", model.Subtasks) outside of your form, and either having it in a single separate form, or have the partial generate a form for each grid row.
This will address your validation problems with your main form, and should also permit you to simplify validation of each row in SubTasksGrid.
To validate part of the form, wrap the section or the controls you want to validate into a div with an #id or .class and do the following:
var validator = $("#myForm").validate();
var isValid = true;
$("myDivToBeValidated").find("*[data-val]").each(function (indx, elem) {
if (!validator.element(elem)) {
isValid = false;
}
});
//this part of form is valid however there might be some other invalid parts
if (isValid)
//do your action, like go to next step in a wizard or any other action
goToNextStep();
I hope it is clear, if not please leave a comment. For more info about jQuery validation plugin and element() function, check this
Looks like you are working against the MVC egine here.
I would use Editor templates and Display templates, EditorFor template for the stuff you wanna validate and post, and Display template for the stuff you dont wanna post and validate.. If you have a TextBoxFor in the display template make sure its binding property has no Required attribute, and if its a value type make it nullable.
Related
I have an application in ASP.NET MVC 3 using Razor. I have setup some data annotations on text boxes along with jQuery validation and unobtrusive validation.
The thing is, some fields have custom validation logic using plain old vanilla JavaScript like the following functions.
function Validation() {
var signUp = SignUpValidation();
var book = BookingValidation();
if (signUp && book) {
return true;
}
else
{
ShowErrorMessage();
return false;
}
}
function ShowErrorMessage()
{
$('span#ErrorMessage').html('These fields are required');
}
where SignUpValidation(), BookingValidation() are functions which
returns either true or false on basis of some other validation
logic.
This is my code for submit button.
#using (Html.BeginForm(MVC.Booking.Actions.AtWork(model: null), FormMethod.Post,
new {#onsubmit = "return Validation()" }))
{
#Html.Partial("_BookingView")
}
This approach is working in all browsers except IE-7/8.
I faced the same issue lately .. and worked out the following solution:
instead of giving your additional form validation (apart from the unobtrusive mvc 3 validation) as a separate/second submit handler in form "onsubmit" event, you should "inject" your additional validation function in the main unobtrusive validation process of mvc3.. let it take care of the rest.
Create a custom validation adaptor somewhere in your common javascript code/file:
(function ($) {
if($.validator && $.validator.unobtrusive) {
$.validator.unobtrusive.adapters.addBool("AdditionalFormValidation");
}
} (jQuery));
In your view file, where you have the form, add this code to create a new jquery validator method for the custom validator adaptor that you defined in your common file above:
(function ($) {
if ($.validator) {
$.validator.addMethod("AdditionalFormValidation", function (value, element) {
return Validation();
});
}
} (jQuery));
Here
- "AdditionalFormValidation" is the validator method name same as your custom validation adaptor.
- "Validation" is the name of your javascript function that takes care of your additional validation and returns a boolean result for validation successs or failure.
In your form, remove the "onsubmit" handler that you had supplied, add a invisible dummy text field to your form and apply the custom unobtrusive validation adaptor/rule that you created, as given below:
#using (Html.BeginForm(MVC.Booking.Actions.AtWork(model: null), FormMethod.Post))
{
#Html.Partial(MVC.Booking.Views._BookForAtWork)
<input type="text" style="visibility:hidden; width: 1px; height: 1px;" name="hiddenValidation" id="hiddenValidation" data-val="true" data-val-AdditionalFormValidation />
}
This solution worked like a charm for me. To me appears a cleaner solution as it injects the additional validation in the same unobtrusive validation flow of mvc3 rather than creating a second validation flow. Also it is inline to future improvement for creating custom data annotation (validations) for all the custom client side validation work.
You may try updating both your jQuery.Validate.min.js and jquery.validate.unobtrusive.min.js files to latest version...it could be that these files are old...I had the same issue some time back and fixed it by doing this update.
I'm using ASP.NET MVC3 and I was wondering if there's any way to create a listbox that contains the values which I would like my model to have.
Using #Html.ListBoxFor will only store the selected items into the model when the form is submitted rather than all the items in the listbox. I plan on using javascript to add items from another textbox.
Thanks
You are not clear, but are you trying to POST values back? If so, then they must be selected (i.e. active) form values in order to POST. If you use JavaScript to add options to a listbox (HTML select> then these don't post. You would need a multi-select enabled select and then flag each value you want to submit as selected.
To get values back they need to POST in some manner.
No. This has nothing to do with MVC3. This is a limitation of the HTTP model. When a form is posted, the browser only posts the selected value. It does not post the other elements of the select list.
MVC must work within the framework of the way the browsers work, and this can't be changed.
Yes, you can do this. You need a couple things to make this work though, it can be troubling.
In view:
//generate list box with
<select id="NAMEOFLISTBOX" name="NAMEOFLISTBOX" multiple="multiple">
Okay, here is the part that most people miss. The controller will only collect selected items if they are actually designated to be selected. Therefore, where your submit button is you need to include some javascript.
<input type="submit" value="DO WORK" onclick="selectLISTBOXITEMS()" />
Script:
function selectLISTBOXITEMS(){
var curList = document.getElementById("NAMEOFLISTBOX");
for (var i = 0; i < curList.length; i++) {
curList.options[i].selected = true;
}
}
In controller:
[HttpPost]
public ActionResult controllerName(List<string> NAMEOFLISTBOX)
{
foreach(string s in NAMEOFLISTBOX)
{
//do work
}
return RedirectToAction("controllerGet");
}
Not impossible, but the first time I did this it took a while to figure out why nothing was being sent.
I have got a hidden field with a validation for it as below
#Html.HiddenFor(m => m.Rating)
#Html.ValidationMessageFor(m => m.Rating)
The Rating property has Range validator attribute applied with range being 1-5. This is put inside a form with a submit button.
I have then got following jquery that sets the value in hidden field on some user event (Basically user clicks on some stars to rate)
$(".star").click(function(){
$("#Rating").val(2);
});
Now if I submit the form without the user event that sets the hidden field, the validation works. The error messages is displayed properly and it works all client side.
Now, in this situation, if I click on stars, that invokes the above javascript a sets the hidden field, the validation error message would not go away. I can submit the form after the hidden variable has some valid value. But I'm expecting that the client side validation should work. (When the hidden variable has been set with some valid value, the validation error should go away)
Initially I thought, the jquery validation would be invoked on some special events so I tried raising click, change, keyup, blur and focusout events myself as below
$(".star").click(function(){
$("#Rating").val(2);
$("#Rating").change();
});
But this is still not working. The error messages once appeared, does not go away at all.
You can wrap your hidden field with a div put somewhere but still inside the <form>. Add css to kick it to outer space.
<div style="position:absolute; top:-9999px; left:-9999px">
<input id="Rating" type="hidden" name="rating" >
</div>
Then add the following label to where you want to show the error:
<label for="rating" class="error" style="display:none">I am an an error message, please modify me.</label>
Client-side validation ignores hidden fields. You can set the "ignore" option dynamically but just to get it to work I did the following directlyl in the .js file.
For now this should do the trick.
In my aspx...
<%: Html.HiddenFor(model => model.age, new { #class="formValidator" }) %>
In jquery.validate.js
ignore: ":hidden:not('.formValidator')",
This turned out to be a very interesting issue. the default "ignore" setting is ignores hidden fields. The field was hidden in a jQuery ui plug-in. I simply added a class called "includeCheckBox" to the rendered input I wanted to validate and put the following line of code in...
var validator = $('#formMyPita').validate();
validator.settings.ignore = ':hidden:not(".includeCheckBox")';
if ($('#formMyPita').valid()) {....
In the code which sets the hidden field's value, manually invoke validation for the form, like so:
$("form").validate().form();
I think it is because hidden inputs don't fire any of these events.
What you could do instead would be to use a <input type="text" style="display:none" /> instead of the hidden field;
#html.TextBoxFor(m => m.Rating, new {display = "display:none"})
I want to pass two values from view to controller . i.e., #Model.idText and value from textbox. here is my code:
#using HTML.BeginForm("SaveData","Profile",FormMethod.Post)
{
#Model.idText
<input type="text" name="textValue"/>
<input type="submit" name="btnSubmit"/>
}
But problem is if i use "Url.ActionLink() i can get #Model.idText . By post action i can get textbox value using FormCollection . But i need to get both of this value either post or ActionLink
using ajax you can achieve this :
don't use form & declare your attributes like this in tags:
#Model.idText
<input type="text" id="textValue"/>
<input type="submit" id="btnSubmit"/>
jquery:
$(function (e) {
// Insert
$("#btnSubmit").click(function () {
$.ajax({
url: "some url path",
type: 'POST',
data: { textField: $('#textValue').val(), idField: '#Model.idText' },
success: function (result) {
//some code if success
},
error: function () {
//some code if failed
}
});
return false;
});
});
Hope this will be helpful.
#using HTML.BeginForm("SaveData","Profile",FormMethod.Post)
{
#Html.Hidden("idText", Model.idText)
#Html.TextBox("textValue")
<input type="submit" value="Submit"/>
}
In your controller
public ActionResult SaveData(String idText, String textValue)
{
return null;
}
I'm not sure which part you are struggling with - submitting multiple values to your controller, or getting model binding to work so that values that you have submitted appear as parameters to your action. If you give more details on what you want to achieve I'll amend my answer accordingly.
You could use a hidden field in your form - e.g.
#Html.Hidden("idText", Model.idText)
Create a rule in global.asax and than compile your your with params using
#Html.ActionLink("My text", Action, Controller, new { id = Model.IdText, text =Model.TextValue})
Be sure to encode the textvalue, because it may contains invalid chars
Essentially, you want to engage the ModelBinder to do this for you. To do that, you need to write your action in your controller with parameters that match the data you want to pass to it. So, to start with, Iridio's suggestion is correct, although not the full story. Your view should look like:
#using HTML.BeginForm("SaveData","Profile",FormMethod.Post)
{
#Html.ActionLink("My text", MyOtherAction, MaybeMyOtherController, new { id = Model.IdText}) // along the lines of dommer's suggestion...
<input type="text" name="textValue"/>
<input type="submit" name="btnSubmit"/>
#Html.Hidden("idText", Model.idText)
}
Note that I have added the #Html.Hidden helper to add a hidden input field for that value into your field. That way, the model binder will be able to find this datum. Note that the Html.Hidden helper is placed WITHIN your form, so that this data will posted to the server when the submit button is clicked.
Also note that I have added dommer's suggestion for the action link and replaced your code. From your question it is hard to see if this is how you are thinking of passing the data to the controller, or if this is simply another bit of functionality in your code. You could do this either way: have a form, or just have the actionlink. What doesn't make sense is to do it both ways, unless the action link is intended to go somewhere else...??! Always good to help us help you by being explicit in your question and samples. Where I think dommer's answer is wrong is that you haven't stated that TextValue is passed to the view as part of the Model. It would seem that what you want is that TextValue is entered by the user into the view, as opposed to being passed in with the model. Unlike idText that IS passed in with the Model.
Anyway, now, you need to set up the other end, ie, give your action the necessary
[HttpPost]
public ActionResult SaveData(int idText, string textValue) // assuming idText is an int
{
// whatever you have to do, whatever you have to return...
}
#dommer doesn't seem to have read your code. However, his suggestion for using the Html.ActionLink helper to create the link in your code is a good one. You should use that, not the code you have.
Recapping:
As you are using a form, you are going to use that form to POST the user's input to the server. To get the idText value that is passed into the View with the Model, you need to use the Html.Hidden htmlhelper. This must go within the form, so that it is also POSTed to the server.
To wire the form post to your action method, you need to give your action parameters that the ModelBinder can match to the values POSTed by the form. You do this by using the datatype of each parameter and a matching name.
You could also have a complex type, eg, public class MyTextClass, that has two public properties:
public class MyTextClass
{
public int idText{get;set}
public string TextValue{get;set;}
}
And then in your controller action you could have:
public ActionResult SaveData(MyTextClass myText)
{
// do whatever
}
The model binder will now be able to match up the posted values to the public properties of myText and all will be well in Denmark.
HTH.
PS: You also need to read a decent book on MVC. It seems you are flying a bit blind.
Another nit pick would be to question the name of your action, SaveData. That sounds more like a repository method. When naming your actions, think like a user: she has simply filled in a form, she has no concept of saving data. So the action should be Create, or Edit, or InformationRequest, or something more illustrative. Save Data says NOTHING about what data is being saved. it could be credit card details, or the users name and telephone...
Using Asp.Net MVC3 with unobtrusive validation. I have a form that has a few input fields. One of the fields is pretty simple:
<input id="Name" class="valid" type="text" value="" name="Name" data-val-required="Enter your name." data-val-length-min="5" data-val-length-max="50" data-val-length="Enter a valid name." data-val="true">
I then have some js that validates the form and submits it only if client-side validation passes as in:
var $form = $('#contact_form');
var formAction = $form.attr('action');
var serialized = $form.serialize();
if ($form.validate().valid()) {...
Now the last line always returns true (yes I have unobtrusive enabled). However if I change the last line to:
if ($form.validate().element('#Name')) { ...
It works great and returns false. I have many fields and don't want to iterate over each one and am confused as to why when validating the whole form it says true but validating each individual element returns false correctly.
Things I tried:
- reparsing the the form via unobtrusive's $.validator.parse(... to no avail. It's not a dynamic form and gets rendered when the page loads.
Note: I also checked that jquery (v1.6.2) jquery.validate.min.js (v1.8.1) and jquery.validate.unobtrusive.min.js are loaded in the browser.
You don't have to call .validate() but directly .valid():
if ($form.valid()) {
...
}