Introduction
If you’ve used the Kendo DataSource class very much, you have probably noticed that Kendo comes with a nice way to define server data with the Model class. One of the convenient things about a Model is that it lets you define fields with default values, so that when a new instance is created, the instance already has all of the fields you defined, reducing the possibility of referencing a property that is undefined. However, you may soon discover to your sorrow as I did, that this will only occur if you do not pass a values object to the Model constructor. This seemed so bizarre and obviously wrong that it must be a bug, so I reported it, only to be told that this behavior was designed. To this day I do not understand why and no attempt to address my question was given, but it was correctly pointed out that I could create my own Model subclass that implements the behavior I desire. So I did, and I’m publishing the results here so that others may benefit.
Show me the code!
If you know what you’re doing, it takes minimal code to implement the desired behavior:
var PartialDefaultModel = kendo.data.Model.extend({ init: function(options) { kendo.data.Model.fn.init.call(this, $.extend({}, this.defaults, options)); } }); PartialDefaultModel.define = function(options) { return kendo.data.Model.define(PartialDefaultModel, options); };
It’s a little code, but there’s a lot going on, so let’s unpack it. On the first line is a call to Model.extend(). The extend function is introduced by Kendo’s Class type and is how you create inheritance hierarchies in Kendo. The object you pass in will be used to define the new subclass. By convention, all Kendo Class subtypes will call their init function during construction. This is where you add logic that would go in a constructor in an classical object-oriented language. However, by default the base class’ init function is not called, so you must call it manually, as seen in the body of the init function. On any descendant of a Kendo Class, the fn property contains the prototype that is shared by all instances of that Class, so kendo.data.Model.fn refers to the prototype object shared by all instances of Model. That’s where shared methods like init are defined, not on the instances themselves. The init function is going to want its context set to the current object, so you use Function.prototype.call to call it while setting its context (a.k.a. “this”). Lastly, I use jQuery’s extend function to merge the object passed to the constructor with the defaults, which are already stored by Model.define in the defaults property in a name-value object. Finally, to make is easier to define models based on this new Model subclass, I added a define method to it. This call to define looks different than the one in the documentation, but if you look at the source code you’ll see that Model.define actually takes two parameters, the first of which is an optional base class. If only one parameter is passed to Model.define, then Model is used as the base.
I created a JSBin to demonstrate how this new model can be used. It has three parts to it. The first part defines an out-of-the box Kendo Model that demonstrates the undesirable behavior:
var modelOptions = { fields: { name: { type: 'string' }, kind: { type: 'string', defaultValue: 'Cat' } } }; var Pet = kendo.data.Model.define(modelOptions); var pet = new Pet({ name: 'Boris' }); console.log({ name: pet.name, kind: pet.kind });
Output:
[object Object] { kind: undefined, name: "Boris" }
Next I create the new Model class and define a model with it:
var PartialDefaultModel = kendo.data.Model.extend({ init: function(options) { kendo.data.Model.fn.init.call(this, $.extend({}, this.defaults, options)); } }); PartialDefaultModel.define = function(options) { return kendo.data.Model.define(PartialDefaultModel, options); }; var PartialPet = PartialDefaultModel.define(modelOptions); var partialPet = new PartialPet({ name: 'Natasha' }); console.log({ name: partialPet.name, kind: partialPet.kind });
Output:
[object Object] { kind: "Cat", name: "Natasha" }
So we can see that the partial application of the “kind” default is working! Lastly, I created a simple DataSource and bound it to a Grid to show that it plays nicely with the rest of Kendo. The AJAX response contains one object that specifies a kind, and you will see that the grid output correctly shows the default for the objects that didn’t specify it, and shows the kind of the object that did specify it.
Even though I might not understand or agree with Kendo’s maintainers about their decision not to implement this behavior, it’s pretty cool that it’s so easy to extend and drop in a replacement that does what you want!
The post Kendo: Model defaultValues not applied to partial objects appeared first on Falafel Software Blog.