10
votes

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>
4

4 Answers

11
votes

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!

4
votes

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.

2
votes

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();
});
2
votes

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.