Showing posts with label Knockout. Show all posts
Showing posts with label Knockout. Show all posts

Thursday, October 25, 2012

Knockoutjs ~ Int16 data mapping fails from JSON to .NET

We design class comprising of different data types based on our requirement. Assume there is a requirement to store the age of a person. In this case, we can specify any numerical datatype for Age like byte, short or int. In most cases we end up choosing int (Int32) datatype. But sometimes developer opts for either byte datatype or short (Int16) datatype.

Let’s assume that we have the following Person class with Age datatype as short (Int16).

public class Person
    {
        public Int32 Id { get; set; }
        public string Name { get; set; }
        public Int16 Age { get; set; }
    }

The corresponding class definition on the cshtml view (javascript side) will be defined as follows:

function Person (person) {
        var self = this;
        self.Id=ko.observable(person.Id);
        self.Name=ko.observable(person.Name);
        self.Age=ko.observable(person.Age);
    }

The following would normally be our method to send the information back to the server:

        self.save = function (person) {               
            $.ajax({
                type: "POST",
                url: "Save",
                data: ko.toJSON(self),
                dataType: 'json',
                contentType: 'application/json;charset=UTF-8',
                success: function (result) {
                    alert("success");
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    alert("save error status: " + xhr.status);
                    alert("save error thrown: " + thrownError);
                }
            });
        };

And the following would be our ASP.NET code to capture the information:

        [HttpPost]
        public JsonResult Save(Person person)
        {
            if (person == null) throw new ArgumentNullException("person");

            var personBusiness = new PersonBusiness();

            var result = person.Id == 0 ? personBusiness.Insert(person) : personBusiness.Update(person);

            return Json(result);
        }

In the above case we would expect that the person object would have the information mapped properly provided by the user but unfortunately Age will hold value of 0 no matter what value is supplied by the user. It seems like the mapping of short datatype is not properly implemented. This becomes evident when we change the datatype of Age from Int16 to Int32. Now on the save action we can see that the Age attribute will has the value as set by the user.

Hope this solves someone facing such issue.

Thursday, October 18, 2012

"Dynamic" deep validation with KnockoutJS model

In this article, Knockout Model and Validating "inner" Models, we have seen how deep validation is done. Here is a snippet of that validation:
 
self.save = function () {
if (self.errors().length != 0) { //verifying the errors attribute using the count of errors
self.errors.showAllMessages(); //displaying the corresponding error messages
return; //returning the control providing user a chance to correct the issues
}
 
In the above method, we are simply invoking the showAllMessages method from the errors member. This will work for on occasion but sometimes this will fail and the validation error messages will not be displayed. In this situation, there is a workaround using which we can achieve this validation functionality working. The workaround is to add errors member to javascript class and then invoike the showAllMessage method for each of the instance of the class as shown below:
 
function Contact(id, phoneType, phone) {
var self = this;
self.ClientId = ko.observable(id);
self.PhoneType = ko.observable(phoneType).extend({ required: true });
self.Phone = ko.observable(phone).extend({ required: true });
self.errors = ko.validation.group(self); //adding the errors object for javascript class making each instance to hold the error individually
};
 
function ClientModel (id, firstName, lastName, email, contacts) {
var self = this;
self.Id = ko.observable(id);
self.FirstName = ko.observable(firstName).extend({ required: true, maxLength: 50 });
self.LastName = ko.observable(lastName).extend({ required: true, maxLength: 50 });
self.Email = ko.observable(email).extend({ maxLength: 50, email: true });
 
self.Contacts = ko.observableArray(ko.utils.arrayMap(contacts, function (aContact) { return aContact; }));
 }
 
Next we will update the save method as follows to perform the validation:
 
self.save = function () {
       var errorCount = 0;
ko.utils.arrayForEach(self.Contacts(), function(contact){
contact.errors.showAllMessages();
errorCount = errorCount + 1;
});
 
if (errorCount != 0) {
return;
       }
 
       //save code block
}
 
Hope this helps.

Wednesday, September 05, 2012

Knockout Model and Validating "inner" Models

More and more technologies, projects and products are basing their foundation on MVC and MVVM principles. The reason is that it makes the project or product to be maintained easily without much headache. The latest trend in client based web development is also shifting towards MVVM model and the tool that is getting most widely used is the Knockout javascript library. It’s a great tool that eases binding making even the complex javascript tasks to be designed pretty easily like for example dynamically rendering design for multiple contacts and collecting details, etc..,

Basic Knockout Model Validation

Validating a basic knockout model is a straight forward task. We just have to use the extend method on the observable attributes and set the necessary parameters. Consider the following model:

function ClientModel (id, firstName, lastName, email) {
        var self = this;
        self.Id = ko.observable(id);
        self.FirstName = ko.observable(firstName).extend({ required: true, maxLength: 50 });
        self.LastName = ko.observable(lastName).extend({ required: true, maxLength: 50 });
        self.Email = ko.observable(email).extend({ maxLength: 50, email: true });

        self.save = function () {
            if (self.errors().length != 0) { //verifying the errors attribute using the count of errors
                self.errors.showAllMessages(); //displaying the corresponding error messages
                return; //returning the control providing user a chance to correct the issues
            }

            $.ajax({
                type: "POST",
                url: "Save",
                data: ko.toJSON(self),
                contentType: 'application/json',
                success: function (mydata) {
                    alert("Success");
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    alert("save method error status: " + xhr.status);
                    alert("save method error thrown: " + thrownError);
                }
            });
        };
};

$(document).ready(function () {
var clientModel = new ClientModel(1, "Hello", "World", "hell0@world.com");
clientModel.errors = ko.validation.group(clientModel); //setting up the errors attribute to the model to capture the validation error message
ko.applyBindings(clientModel);
});

The above is a basic model with validations added for the attributes using the extend method (More validation can be found here). The validation error messages are shown when the save method of the model is executed.

Deep Knockout Model Validation

Often we face situation where we will have a requirement for a complex model than the basic one. For example: our client may request us to design a solution to allow multiple contact numbers for a customer to be collected. In this case, the underlying model is changed as follows.

function Contact(id, phoneType, phone) {
        var self = this;
        self.ClientId = ko.observable(id);
        self.PhoneType = ko.observable(phoneType).extend({ required: true });
        self.Phone = ko.observable(phone).extend({ required: true });
};

function ClientModel (id, firstName, lastName, email, contacts) {
       var self = this;
       self.Id = ko.observable(id);
       self.FirstName = ko.observable(firstName).extend({ required: true, maxLength: 50 });
       self.LastName = ko.observable(lastName).extend({ required: true, maxLength: 50 });
       self.Email = ko.observable(email).extend({ maxLength: 50, email: true });

self.Contacts = ko.observableArray(ko.utils.arrayMap(contacts, function (aContact) { return aContact; }));

       self.save = function () {
            if (self.errors().length != 0) { //verifying the errors attribute using the count of errors
                self.errors.showAllMessages(); //displaying the corresponding error messages
                return; //returning the control providing user a chance to correct the issues
            }

            $.ajax({
                type: "POST",
                url: "Save",
                data: ko.toJSON(self),
                contentType: 'application/json',
                success: function (mydata) {
                    alert("Success");
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    alert("save method error status: " + xhr.status);
                    alert("save method error thrown: " + thrownError);
                }
            });
       };
};

$(document).ready(function () {
       var contacts = { new Contact(1, "Home", "1234567890"), new Contact(1, "Work", "9876543210") };
var clientModel = new ClientModel(1, "Hello", "World", "hell0@world.com", contacts);
clientModel.errors = ko.validation.group(clientModel); //setting up the errors attribute to the model to capture the validation error message
ko.applyBindings(clientModel);
});

The above is a also a model with validations added for the attributes using the extend method. The validation error messages excepts the contacts will be displayed when the save method of the model is executed. The reason is that we have not made the validation “deep” so the inner validations are not executed. In order to achieve this, we will have to include the following Knockout initialization block:

ko.validation.init({grouping: { deep: true }, messagesOnModified: false });

Adding the above line of code should make the inner validations work in most cases. But if it still doesn’t work, then we will have to add the errors attribute to the Contact model just like we have added for the ClientModel. The below listing shows the updated Contact model with errors attribute added:

function Contact(id, phoneType, phone) {
        var self = this;
        self.ClientId = ko.observable(id);
        self.PhoneType = ko.observable(phoneType).extend({ required: true });
        self.Phone = ko.observable(phone).extend({ required: true });

self.errors = ko.validation.group(self); //setting up the errors attribute to the Contact model to capture the validation error message
};

Having added the errors attribute to the Contact model, we also need to update the save method of the ClientModel to verify the errors collection attribute and return if in case there are errors. The updated save method is listed below:

self.save = function () {
       var errorExists = false;

if (self.Contacts().errors().length != 0) { //verifying the errors attribute from the Contact model
              self.Contacts().errors.showAllMessages(); //displaying the corresponding error messages
              errorExists = true; //set error flag to stop further processing
       }

if (self.errors().length != 0) { //verifying the errors attribute using the count of errors
              self.errors.showAllMessages(); //displaying the corresponding error messages
              errorExists = true; //set error flag to stop further processing
       }

if (errorExists) {
              return; //returning the control providing user a chance to correct the issues
       }

      
       $.ajax({
              type: "POST",
              url: "Save",
              data: ko.toJSON(self),
              contentType: 'application/json',
              success: function (mydata) {
                     alert("Success");
              },
              error: function (xhr, ajaxOptions, thrownError) {
                     alert("save method error status: " + xhr.status);
                    alert("save method error thrown: " + thrownError);
              }
            });
};

From the above updated save method, all the errors corresponding to the main model (ClientModel) and the child model (Contact) will be displayed.
Hope this helps!!!