1

I'm using knockout js to construct my application with asp.net mvc 4 on the backend. We are using knockout.mapping to populate the observables from MVC Service calls

Now I'm trying to add validation using the Knockout.Validation plugin as follows:

self.sDetail(ko.mapping.fromJS(s));
var validationOptions = { 
                          insertMessages: true, 
                          decorateElement: true,      
                          errorElementClass: 'errorFill' 
                         };
ko.validation.init(validationOptions);
self.sDetail.Year.extend({ required: true });

When I run it I get the following error:

Error: Unable to get property 'extend' of undefined or null reference

When I debug I find that the sDetail appears empty. When I applyBindings the inputs are populated correctly. At what point to I have access to the propertys of the ko viewmodel. Is there a way around this issue?

Saju
  • 3,201
  • 2
  • 23
  • 28
Seth Greenstein
  • 153
  • 2
  • 9

3 Answers3

2

Found a solution here: Knockout Mapping Validation

basically have to extend the properties in the call to mapping

var validationMapping = {
// customize the creation of the name property so that it provides validation
Year: {
    create: function(options) {
        return ko.observable(options.data).extend( {required: true} );
    }
}
};


  self.sDetail(ko.mapping.fromJS(s,validationMapping));
 var validationOptions = { 
                      insertMessages: true, 
                      decorateElement: true,      
                      errorElementClass: 'errorFill' 
                     };
  ko.validation.init(validationOptions);

self.sDetail.Year.extend({ required: true });

Community
  • 1
  • 1
Seth Greenstein
  • 153
  • 2
  • 9
1

The actual problem in your original code was that you were trying to access the Year property on the observable holding your mapped model, not on the model itself (assuming sDetail is an observable on self which I can only assume is your actual view model). This was what I meant on my comment. You mapped the value the observable holds so you need to read off of that value. You attempted to extend() a property that doesn't exist on observables hence the error.

self.sDetail; // <- your observable holding the model
self.sDetail.Year; // <- undefined, observables don't have a `Year` property
self.sDetail.Year.extend(...); // <- error

The fix there would be to invoke your observable to get the model that it holds and then you can access whatever you want.

self.sDetail().Year.extend({ required: true }); // this should work

That's not to take away from your eventual solution, extending from within the mapping settings would be preferrable as it leads to cleaner code (IMHO).

Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
1

For a cleaner and more readable solution use explicit declared VM's

DetailsViewModel = function(data) {
   this.year = ko.observable().extend({ required: true });
   ko.mapping.fromJS(data, {}, this);
};

Used like

new DetailsViewModel(data);

A good practice is to explicit declare all members that do actual domain logic. The presentation only members can be automapped automatic by ko.mapping.

Update: Got downvoted by some douch, but this solution is very valid and readable

http://jsfiddle.net/hww8vk1n/1/

Anders
  • 17,306
  • 10
  • 76
  • 144
  • 1
    What happens to `this.year` when you call `ko.mapping.fromJS(data, {}, this);`? Since it is already defined does the mapping plugin just assign a value to it at that point? – Homer Apr 11 '14 at 15:00
  • Yes, it will just execute the observable and assign value, it will keep the extensions etc. Got a downvote and noticed your comment, thats why the late reply :D – Anders Aug 11 '15 at 16:10
  • I like this example, what if I call `new DetailsViewModel();` – Heberda Feb 05 '16 at 09:22
  • You could do ko.mapping.fromJS(data || { defaultProp: "defaultvalue" }, {}, this); http://jsfiddle.net/hww8vk1n/6/ – Anders Feb 05 '16 at 09:54