unobtrusive client validation in knockout template binding - data-annotations

I have a model with data annotations and i am an dynamically binding that with viewmodel using knockout template binding and mapping plugin. I am trying to do a unobtrusive client validation to be done on my model. How we can do that in this scenario. Any help/suggestions?
public class MyUser
{
[Required]
[StringLength(35)]
public string Username { get; set; }
[Required]
[StringLength(35)]
public string Forename { get; set; }
[Required]
[StringLength(35)]
public string Surname { get; set; }
}
In my view i am dynamically template binding a list of MyUser using ajax.
public JsonResult TestKnockout()
{
IList<MyUser> myUserList = new List<MyUser>();
myUserList.Add(new MyUser { Username = "ajohn", Surname = "surname" });
myUserList.Add(new MyUser { Username = "ajohn1", Surname = "surname1" });
return Json(myUserList, JsonRequestBehavior.AllowGet);
}
}
View:
<form id="Userform" action='#Url.Action("Save", "Home")' data-bind="template: {name: 'UserTemplate', foreach:UserList}">
<input type="Submit" name="name" value="Submit" />
</form>
<script id="UserTemplate" type="text/Html">
<input type="text" data-bind="value: Username"></input>
<input type="text" data-bind="value: Forename"></input>
<input type="text" data-bind="value: Surname"></input>
</script>
<script type="text/javascript">
var viewModel = {
UserList: ko.observableArray(new Array()),
Save: function () {
//// reached here means validation is done.
alert("Save");
}
}
ko.applyBindings(viewModel);
$.ajax({
type: 'GET',
url: '../Home/TestKnockout',
contentType: "application/json",
success: function (data) {
$.each(ko.mapping.fromJS(data)(), function () {
viewModel.UserList.push(this);
})
// attach the jquery unobtrusive validator
$.validator.unobtrusive.parse("#Userform");
// bind the submit handler to unobtrusive validation.
$("#Userform").data("validator").settings.submitHandler = viewModel.Save;
},
error: function (xhr, ajaxOptions, thrownError) {
alert(xhr.status);
alert(thrownError);
}
});
</script>

pilavdzice and drogon answers are quite good but we forget the basic point.
Since we are using an MVVM pattern for the seperation of UI and data (+vm) we don't want to perform UI validation but DATA VALIDATION. Those two are quite different, jquery validate is a great plugin but it does UI validation (it starts from UI to check the fields).
I have found knockout validation plugin which seems to do quite well and what it does is to go the opposite road, it validates your viewmodel and not your UI (it actually maps to UI elements to display the errors).
Unfortunately If your viewmodel gets complex, that plugin will have some problems, but in any case this is the way to go.
UI validation is perfect as long as we are not using an MVVM pattern, after all what do we separate the components (M-V-VM) for ?
Hope I helped !
Thanks!

I had the same problem as you so I wrote the following component.
https://www.nuget.org/packages/ScriptAnnotations/
https://scriptannotations.codeplex.com/
Please let me know if this helps.

I would go with jquery's event binding for this.
First, add your data-val attributes to the inputs you want to validate. (To figure out which data-val attributes to use, I usually bind a form server-side to a model and view source.)
<input data-val-required="test" data-val="true" data-bind="visible:
$parent.userEditMode, value: FirstName" />
Second, add a validation utility function --this calls the jquery validation plugin used by MVC under the covers.
function validateForm(thisForm) {
var val = thisForm.validate();
var isValid = val.form();
alert(isValid);
if (!isValid) {
thisForm.find('.input-validation-error').first().focus();
}
return isValid;
}
Third, call validate before issuing your viewmodel method. Make sure to remove the "click" data-bind attribute from the markup in your page.
$('#..your form id...').live('submit', function (e) {
e.preventDefault();
if(validateForm($(this)))
viewModel.saveUser();
});

If you are using knockoutjs and jquery, I came up with the following very simple method for doing basic validation.
Wherever you want to display the error message on your page, include a span tag like this:
<span name="validationError" style="color:Red"
data-bind="visible: yourValidationFunction(FieldNameToValidate())">
* Required.
</span>
Obviously you need to write "yourValidationFunction" to do whatever you want it to do. It just needs to return true or false, true means display the error.
You can use jquery to prevent a user from proceeding if any validations errors are displayed. You probably already have a save button that triggers a javascript function to do some ajax or whatever, so just include this at the top of it:
if ($("[name='validationError']:visible").length > 0) {
alert('Please correct all errors before continuing.');
return;
}
This is a lot simpler and more flexible than many other validation solutions out there. You can position your error message wherever you want, and you don't need to learn how to use some validation library.

Related

ASP Net Core - hidden input field data is not passed in the Controller

I need to pass which button is clicked for the form submit. But the hidden input is not received in the controller. Below are my code snippets
View Model:
public class DocumentViewModel
{
public int Id { get; set; }
public int ActionId { get; set; }
}
razor(cshtml):
<form....>
<input id="docActionId" name="docActionId" asp-for="ActionId" type="hidden" value="initialValue" />
</form>
JavaScript:
$("#save_btn").on("click", function ()
{
$("#docActionId").val("test1");
});
$("#submit_btn").on("click", function ()
{
$("#docActionId").val("test2");
});
I tried showing the current value in alert function if it is change and it did.
In my Controller/Action when I debug, I get a null value for the ActionId variable. I think this is very simple but I don't know what I did wrong.
Please help. I'm stucked in this for 2 hours now. Thanks in advance.
remove the "Id" attribute... that's all you need. "asp-for" does that. adding yours duplicates it kind
Remove the name="docActionId" html attributes, they will be generated automatically by Razor.
Specifically by the asp-for="ActionId" attribute.
Furthermore the reason this doesn't work is because the name attribute which is docActionId != ActionId which is the name of the property of your object. So it doesn't know where to bind it to.
<form id="myform">
<input id="docActionId" asp-for="ActionId" type="hidden" value="initialValue" />
<button type="submit" id="save">Submit</button>
</form>
Try this javascript perhaps the form is submitted before you attach the value
var saveButton = documnet.getElementById('save')
saveButton.addEventListener('click', function(e) {
e.stopPropagation();
documnet.getElementById('docActionId').value = "test value"
documnet.getElementById('myform').submit()
})
<input type="hidden" id="tag" >
type="hidden" didn't work for me with .net core 7 but only hidden keyword worked.
<input hidden id="tag">

Can't bind checkbox in partial view to the main model (MVC)

I've been running into to issue and I've been searching for an answer but nothing helped.
I have a Model:
public class Filters
{
public bool Filter1 { get; set; }
public bool Filter2 { get; set; }
public bool Filter3 { get; set; }
etc...
}
I have a partial view with multiple checkboxes and tried multiple things:
<input id="Filter1" name="Filter1" type="checkbox" value="true">
<input type="hidden" value="false" name="Filter1" />
and
#Html.CheckBoxFor(model => model.Filter1)
Then I have a main model:
public class Dashboard
{
...
public Filters FiltersDashboard { get; set; }
}
And somewhere in the main view I insert the partial view like this:
#Html.EditorFor(model => model.FiltersDashboard, "Filters")
In a jquery, I execute an alert when the checkbox is clicked and shows the value of the checkbox. This value remains unchanged.
<script>
$("#Filter1").click(function () {
alert(" #Model.FiltersDashboard.Filter1 ")
});
</script>
EDIT: Also tried it in the .submit function but model value remains unchanged:
<script>
$("#searchform").submit(function (event) {
alert(" #Model.FiltersDashboard.Filter1 ")
event.preventDefault();
});
</script>
This tells me that something isn't correctly bound but I have no clue what I'm doing wrong.
Also, the reason I'm not using a checkboxlist is because I need to execute a different query for each filter so I need specific names and bindings for them.
#Model.FiltersDashboard.Filter1 is razor code and is executed on the server before its sent to the view, so it will only ever return the initial value of the property (if you inspect the source code, you will see the initial value has been hard coded in your script).
However, if your script is being executed, then it means that you are using the manual <input> tags in your 2nd code snippet, in which case your view is not binding to your model because the correct name attribute would be name="FiltersDashboard.Filter1" and the associated id attribute would be id="FiltersDashboard_Filter1".
Always use the strong typed #Html.CheckBoxFor() which will generate the correct html for 2-way model binding and client side validation.
Note also that it just needs to be #Html.EditorFor(model => model.FiltersDashboard) - the 2nd parameter is unnecessary.
Then the script should be
<script>
$('#FiltersDashboard_Filter1').click(function () {
var isChecked = $(this).is(':checked');
alert(isChecked);
});
</script>

FluentValidation message appears, but form submits anyway

Using MVC4 with FluentValidation. I have a field with two rules on it. The NotEmpty rule works as expected. The Matches rule seems to fire, but the form submits anyway, even though the validation message pops up as if it's failing validation.
I have the following view model and validator:
public class ImpactedEntityViewModelValidator : AbstractValidator<ImpactedEntityViewModel>
{
public ImpactedEntityViewModelValidator()
{
RuleFor(x => x.ImpactedEntityDescription)
.Matches("[a-zA-Z0-9/ ]{1,}").WithMessage("Description can only contain letters, numbers, '/', and spaces.")
.NotEmpty().WithMessage("Description is required.");
}
}
[Validator(typeof(ImpactedEntityViewModelValidator))]
public class ImpactedEntityViewModel
{
public int? ImpactedEntityLUID { get; set; }
[Display(Name = "Impacted Entity Description")]
public string ImpactedEntityDescription { get; set; }
public bool? Deleted { get; set; }
}
View:
#model ChangeControlForm.Models.ImpactedEntityViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.EditorFor(model => model)
<p>
<input type="submit" value="Create" class="btn btn-default" />
</p>
}
Added to Global Application_Start:
FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure();
I'm not sure how that's possible. It won't submit if the field is left empty, as expected. If I enter a "%" for example, the message for the Matches rule will pop up but then it will immediately submit after and write the record. Is there something I'm missing that could cause that?
Thank you.
Per Michael Crook's answer:
This solved the issue:
$("form").submit(function () {
var form = $(this);
if (form.valid()) {
// do valid stuff
}
else {
return false;
}
});
Per LeftyX's answer:
Checked my Nuget packages and jQuery had an update available. Updating it to 2.1.4 fixed the issue and I don't need the extra check on submit.
Thanks everyone.
You don't really have to do the check for validation yourself:
$("form").submit(function () {
var form = $(this);
if (form.valid()) {
// do valid stuff
}
else {
return false;
}
});
I mean, you can, but probably you probably already have everything you need in place.
If you check in your Scripts folder you should have:
jquery.validate.js
jquery.validate.unobtrusive.js
and
jquery.unobtrusive-ajax.js (this is only needed if you're POSTing ajax)
and you BundleConfig already bundles the scripts needed for the client-side validation:
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
the default template doesn't add the reference to the bundle automatically but you can add it simply adding:
#Scripts.Render("~/bundles/jqueryval")
to your _Layout.cshtml or wherever you need the client-side validation.
if you check the html for your form you will see that your input:
<input name="ImpactedEntityDescription" class="text-box single-line" id="ImpactedEntityDescription" type="text" value="" data-val="true" data-val-required="Description is required." data-val-regex-pattern="[a-zA-Z0-9/ ]{1,}" data-val-regex="Description can only contain letters, numbers, '/', and spaces.">
have all the unobtrusive attributes set in place:
data-val="true"
data-val-required="Description is required."
data-val-regex-pattern="[a-zA-Z0-9/ ]{1,}"
data-val-regex="Description can only contain letters, numbers, '/', and spaces."
jquery.validate.js checks the form before sumitting for you (line 404):
// http://jqueryvalidation.org/Validator.form/
form: function() {
this.checkForm();
$.extend( this.submitted, this.errorMap );
this.invalid = $.extend({}, this.errorMap );
if ( !this.valid() ) {
$( this.currentForm ).triggerHandler( "invalid-form", [ this ]);
}
this.showErrors();
return this.valid();
},
Check your nuget packages are updated.
You will probably find that FluentValidation (I've only ever used for server side validation, not client side) doesn't have the ability to disable posting. You could try using jquery to search form for validation error classes and then disable the button yourself.
Aside from the possible causes mentioned already above, this line in your view can also cause a submit of the form as soon as a user hits your submit button, even though the form is still invalid:
HtmlHelper.ClientValidationEnabled = false;

ASP.NET MVC razor form with knockout won't validate

I have model like this (simplified)
public class User
{
[Required]
public string UserName { get; set; }
// Other fields
}
An MVC razor view with knockout viewModel that looks like this:
#using (Html.BeginForm("MyAction", FormMethod.Post, new { id = "profileEditorForm" }))
{
#Html.ValidationSummary(false)
#Html.LabelFor(n => n.UserName)
#Html.TextBoxFor(n => n.UserName, new { data_bind = "value: UserName" })
#Html.ValidationMessageFor(n => n.UserName)
#* Other fields *#
<p>
<input type="submit" value="Save" alt="" title="" />
Cancel
</p>
}
<script type="text/javascript">
$(function() {
var vm = new viewModel(#(Html.Raw(Json.Encode(#Model))));
ko.applyBindings(vm);
$("#profileEditorForm").validate({
submitHandler: function(form) {
alert('Validating ' + ko.toJSON(vm));
if (vm.save)
window.location.href = "/";
return false;
}
});
});
var viewModel = function(model) {
var self = this;
self.UserName = ko.observable(model.UserName);
// Other fields
self.save = function() {
alert('Saving ' + ko.toJSON(self));
}
};
};
</script>
I cannot get it to give me client side validation even though it goes through validate function (the alert insertion indicates that) and then straight into save and to posts back to MVC action something that is not valid.
Can you please help me how to enable client side validation (it is enabled in web.confir) on this form. Is it Knockout that ruins the day (although all the bindings are working perfectly fine? Or is it something I am looking at and don't see?
Help highly appreciated!
Validation doesn't work with knockout bindings....
You have to duplicate the validation in client side. The faster way is using knockout validation plugin, but it is slow when you have a lot of data..
https://github.com/ericmbarnard/Knockout-Validation
Or you can just use jquery validation.
Model Validation Will not work in knockout binding you have to download knockoutValidation.js file and use knockout validations
Example :
Validating UserName in KnockoutJS
var self = this;
self.UserName = ko.observable(UserName).extend({
required: {
message:"Please enter User Name"
}
});

Can't serialize Telerik MVC controls in Ajax call

I am having problems passing my model from my view via an Ajax call to my controller. All of the model properties that have Telerik html 'For' controls do not persist in the model. The only way I can access those values in the controller is using Request["control_name"]. All other standard controls like input type=text serialize just fine. What am I doing wrong?
Here is my ajax call:
function ImportLogFile() {
$.ajax({
url: '/Job/ImportLogFile',
type: 'POST',
data: $("form").serialize(),
success: function (data)
{
$('body').css('cursor', 'auto');
alert("Word Counts imported.");
},
error: function (xhr, status, error)
{
alert(status + ": " + strip(xhr.responseText).substring(0, 1000) + "...");
}
});
}
Controller:
[HttpPost]
public ActionResult ImportLogFile(tblJobTask model)
{
...
}
View:
#model viaLanguage.Jams.Data.tblJobTask
<html>
<head></head>
<body>
#using (Html.BeginForm())
{
<label class="editorLabel">CAT Tool Type:</label>
#{ Html.Telerik().ComboBoxFor(model => model.CatToolID)
.Name("JobTask_CatToolID")
.BindTo(new SelectList((IEnumerable)ViewData["CatTools"], "CatToolID", "Description"))
.HtmlAttributes(new { style = "width:220px;" });
}
<input id="btnImport" type="button" onclick="ImportLogFile();" />
}
</body>
</html>
i believe .Name("JobTask_CatToolID") is the source of problem. when you change Name attribute of combobox to something other than property name it will not automatically bound to the property by modelbinder. ModelBinder just looks at posted keys and then searches for matching properties in model and populates them accordingly. and when binder finds the key JobTask_CatToolID it probably finds no matching property in the model and hence it is not assigned to any property. you can check this by omitting the Name(---) method and then posting data to your controller.

Resources