Using Knockout.JS, I'm trying to determine how to best extend objects in the view model when they will be both loaded via the mapping plugin and dynamically added. In this example, I'm adding a method addChild to the Person object.
Extending during mapping:
var myPersonModel = function (data) {
ko.mapping.fromJS(data, {}, this);
this.addChild = function () {
this.children.push(new Child());
} .bind(this);
}
var mapping = {
'people': {
create: function (options) {
return new myPersonModel(options.data);
},
}
}
var viewModel = ko.mapping.fromJS(data, mapping);
Extending during dynamic creation:
function Person(id, name, children) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.children = ko.observable(children);
this.addChild = function () {
this.Forms.push(new Child());
} .bind(this);
}
But it seems to me there must be an easier way to do so such that I don't need to repeat myself, and both mapped and new objects get the addChild method.
Take a look at this answer, especially the live example on JSFiddle.
It looks like your code is close to what it needs to be.
I think you just need to fold mapping and ko.mapping.fromJS into your Person constructor.
Related
I implement a class (I call it RowsEditor) and its subclass (I call it DateRowsEditor) with the jQuery code similar to this:
function create_RowsEditor(tableId) {
var rowsEditor = {
tableId: tableId,
quit_row: function(ctl) { /*...*/ }
};
$('#'+tableId).click(function(event) {
var tr = event.delegateTarget;
rowsEditor.quit_row(tr);
});
return rowsEditor;
}
function create_DateRowsEditor(tableId) {
var rowsEditor = $.extend({}, create_RowsEditor(tableId), {
quit_row: function(ctl) { /*...*/ }
};
return rowsEditor;
}
Then I create an object of type DateRowsEditor():
var rowsEditor = create_DateRowsEditor('come'):
The trouble is that on user's click quit_row() is called with an object created by create_RowsEditor() not with the derived object created with create_DateRowsEditor() as it should.
How to do it properly in a structured and object oriented way?
I've solved the problem by adding the .init() method:
var rowsEditor = {
tableId: tableId,
init: function() {
var self = this;
$('#'+tableId).handleRowsBlur(function(event) {
var tr = event.delegateTarget;
self.quit_row(tr);
});
return this;
},
...
and creating objects like this:
/*var rowsEditor =*/ create_ComeEditor('come').init();
It is the proper way to solve my problem because this way initialization of event handlers is called only when it is should be called.
The trouble is that on user's click quit_row() is not called with the derived object
Then don't use extend for that. Just create the object, and overwrite its quit_row method:
function create_DateRowsEditor(tableId) {
var rowsEditor = create_RowsEditor(tableId);
rowsEditor.quit_row = function(ctl) { /*...*/ };
return rowsEditor;
}
I have been using Knockout.js for a lot of projects lately, and I am writing a lot of repetitive code. I would like to be able to define a BaseViewModel class and have my page-specific ViewModels inherit from it. I am a bit confused about how to do this is Javascript. Here is my basic BaseViewModel:
(function (ko, undefined) {
ko.BaseViewModel = function () {
var self = this;
self.items = ko.observable([]);
self.newItem = {};
self.dirtyItems = ko.computed(function () {
return self.items().filter(function (item) {
return item.dirtyFlag.isDirty();
});
});
self.isDirty = ko.computed(function () {
return self.dirtyItems().length > 0;
});
self.load = function () { }
};
}(ko));
I would like to be able to list signatures for methods like load in the BaseViewModel and then give them definitions in the inheriting ViewModel. Is any of this possible? I have found a few solutions online but they all rely on defining functions/classes to make the inheritance work.
Since your BaseViewModel is just adding all of the properties/methods to this (and not using prototype) then it is pretty easy:
In your new view models, just call BaseViewModel:
var MyVM = function () {
var self = this;
ko.BaseViewModel.call(self);
self.somethingElse = ko.observable();
self.itemCount = ko.computed(function() { return self.items().length; });
self.items([1, 2, 3]);
};
// ...
var vm = new MyVM();
Javascript inheritance is done in two pieces. The first is in the constructor, and the second is on the prototype (which you aren't using, so you could skip).
var ViewModel = function(data) {
BaseViewModel.call(this);
};
//you only need to do this if you are adding prototype properties
ViewModel.prototype = new BaseViewModel();
To your last point, about overriding load, its no different that putting a load function on your viewmodel normally. Javascript allows you to override any objects properties with anything, there are no special steps here.
Here is a fiddle demonstrating the inheritance.
Right when I thought I had a hold on how prototypal inheritance works in JavaScript, I run into an issue I hadn't before considered.
Take a look at the following simple JavaScript code:
var Observable = function () {
this.events = [];
};
Observable.prototype.addEvent = function (e) {
this.events.push(e);
};
var Model = function () {};
Model.prototype = new Observable();
var appModel = new Model();
var taskModel = new Model();
appModel.addEvent('Hello');
taskModel.addEvent('World');
Looking at either appModel.events or taskModel.events yields the same array: ['Hello', 'World']. What I am looking to do is have each new Model have its own events array in as clean a fashion as possible. The following implementation of Model works:
var Model = function () {
this.events = [];
};
Model.prototype = new Observable();
However, as more properties are added to Observable this becomes more unwieldy. I thought I could fix this as follows:
var Model = function () {
this.prototype.constructor.apply(this, arguments);
};
Model.prototype = new Observable();
Alhtough as I'm sure those of you who are more experienced in JavaScript realize this throws an error: TypeError: Cannot read property 'constructor' of undefined.
In summary I am looking for a way for each new Model to inherit properties from Observable and for each Model to have its own events. I realize that this is very class-like, but I would like to know how to do this using only JavaScript prototype-based inheritance.
It is worth noting that I have looked at Dean Edward's Base.js. The following works:
var Observable = Base.extend({
constructor: function () {
this.events = [];
},
addEvent: function (e) {
this.events.push(e);
}
});
var Model = Observable.extend({
constructor: function () {
this.base();
}
});
var appModel = new Model();
var taskModel = new Model();
appModel.addEvent('Hello');
taskModel.addEvent('World');
But the following doesn't:
var Observable = Base.extend({
events: [],
addEvent: function (e) {
this.events.push(e);
}
});
var Model = Observable.extend({
constructor: function () {
this.base();
}
});
var appModel = new Model();
var taskModel = new Model();
appModel.addEvent('Hello');
taskModel.addEvent('World');
Besides the point, I would like to learn how to do this with JavaScript prototypes using no class libraries.
What I have understood here is that you want each instance to have their separate events array, If so follow the answer:
function Model(){
this.events = [];
this.addEvent = function(eventName){
this.events.push(eventName);
};
this.getEvents = function(){
return this.events;
}
}
var m1 = new Model;
var m2 = new Model;
m1.addEvent("eve-1");
m2.addEvent("eve-2");
m1.getEvents(); //["eve-1"]
m2.getEvents(); //["eve-2"]
In your case you are adding events directly to the prototype not the instances hence they are added across all the instances...I hope this should help
I had tried this to fix my problem:
var Model = function () {
this.prototype.constructor.apply(this, arguments);
};
Model.prototype = new Observable();
The actual solution is to do this:
var Model = function () {
Model.prototype.constructor.apply(this, arguments);
};
Model.prototype = new Observable();
This will give each Model its own events array as well as give each Model any other properties that the Observable constructor creates. The small overhead here is that Model.prototype.events is an empty array that is never accessed.
The reason why your first bit of code there causes each instance of Model to have a common events array is because they have a prototypical reference to a single common instance of Observable. In other words, by doing
Model.prototype = new Observable();
You are basically telling JavaScript "make all instances of Model act like this specific instance of Observable".
There are a couple of different ways to correctly perform prototypical inheritance in JavaScript. If you can get by with the recently standardized ECMAScript 6, you can use the handy new class and extends keywords designed for this very purpose.
class Observable {
constructor() {
this.events = [];
}
addEvent() {
this.events.push(e);
}
}
class Model extends Observable {
// Model definition
};
Unfortunately support for ECMAScript 6 still isn't great. ECMAScript 5 added Object.create which has been the preferred method of doing inheritance since (at least as far as I'm aware).
var Observable = function () {
this.events = [];
};
Observable.prototype.addEvent = function (e) {
this.events.push(e);
};
var Model = function () {
Observable.call(this);
};
Model.prototype = Object.create(Observable.prototype);
MDN has a pretty nice article on OOP in JavaScript using this technique.
I have a Knockout Viewmodel that uses the mapping plugin. After mapping the JSON object, I create some computed values, like so:
/* viewmodel setup and mapping */
myViewModel
.formattedGiftAmount = ko.computed({
read: function () {
return parseInt(this.giftAmount(), 10).formatMoney();
}
, write: function (value) {
this.giftAmount(value.unformatMoney());
}
, owner: this
})
.formattedGoal = ko.computed({
read: function () {
return parseInt(this.goalAmount(), 10).formatMoney();
}
, write: function (value) {
this.goalAmount(value.unformatMoney());
}
, owner: this
});
Don't worry so much about what the code does, I'm more concerned with the pattern. As you can see, the two properties formattedGiftAmount() and formattedGoal() have nearly identical objects that define them. The only difference is what properties they modify. I'm potentially going to have many more instances like this, so I was wondering if there was any way to make this more reusable. I can imagine doing something like this[prop](), but I can't quite figure out how to inject that into the object and get it to work.
PS: I've seen this but it still doesn't quite do what I'm looking for.
You can also modify fn to add a function to your ko.observable to create it, this will allow you to add the properties in your constructor in a descriptive way (fiddle):
ko.observable.fn.formatAsMoney = function() {
var base = this;
return ko.computed({
read: function() {
return formatMoney(parseFloat(base()));
},
write: function(newValue) {
base(unformatMoney(newValue));
}
});
};
function ViewModel() {
var self = this;
self.number = ko.observable(10.5);
self.money = self.number.formatAsMoney();
};
You can add a function to your view model that creates a formatted property based on an unformatted property:
myViewModel.addFormattedProperty = function(formattedName, unformattedName) {
this[formattedName] = ko.computed({
read: function() {
return parseInt(this[unformattedName](), 10).formatMoney();
},
write: function(value) {
this[unformattedName](value.unformatMoney());
},
owner: this
});
};
Then you could call it for your properties:
myViewModel.addFormattedProperty('formattedGiftAmount', 'giftAmount');
myViewModel.addFormattedProperty('formattedGoalAmount', 'goalAmount');
In knockout js I see View Models declared as either:
var viewModel = {
firstname: ko.observable("Bob")
};
ko.applyBindings(viewModel );
or:
var viewModel = function() {
this.firstname= ko.observable("Bob");
};
ko.applyBindings(new viewModel ());
What's the difference between the two, if any?
I did find this discussion on the knockoutjs google group but it didn't really give me a satisfactory answer.
I can see a reason if I wanted to initialise the model with some data, for example:
var viewModel = function(person) {
this.firstname= ko.observable(person.firstname);
};
var person = ... ;
ko.applyBindings(new viewModel(person));
But if I'm not doing that does it matter which style I choose?
There are a couple of advantages to using a function to define your view model.
The main advantage is that you have immediate access to a value of this that equals the instance being created. This means that you can do:
var ViewModel = function(first, last) {
this.first = ko.observable(first);
this.last = ko.observable(last);
this.full = ko.computed(function() {
return this.first() + " " + this.last();
}, this);
};
So, your computed observable can be bound to the appropriate value of this, even if called from a different scope.
With an object literal, you would have to do:
var viewModel = {
first: ko.observable("Bob"),
last: ko.observable("Smith"),
};
viewModel.full = ko.computed(function() {
return this.first() + " " + this.last();
}, viewModel);
In that case, you could use viewModel directly in the computed observable, but it does get evaluated immediate (by default) so you could not define it within the object literal, as viewModel is not defined until after the object literal closed. Many people don't like that the creation of your view model is not encapsulated into one call.
Another pattern that you can use to ensure that this is always appropriate is to set a variable in the function equal to the appropriate value of this and use it instead. This would be like:
var ViewModel = function() {
var self = this;
this.items = ko.observableArray();
this.removeItem = function(item) {
self.items.remove(item);
}
};
Now, if you are in the scope of an individual item and call $root.removeItem, the value of this will actually be the data being bound at that level (which would be the item). By using self in this case, you can ensure that it is being removed from the overall view model.
Another option is using bind, which is supported by modern browsers and added by KO, if it is not supported. In that case, it would look like:
var ViewModel = function() {
this.items = ko.observableArray();
this.removeItem = function(item) {
this.items.remove(item);
}.bind(this);
};
There is much more that could be said on this topic and many patterns that you could explore (like module pattern and revealing module pattern), but basically using a function gives you more flexibility and control over how the object gets created and the ability to reference variables that are private to the instance.
I use a different method, though similar:
var viewModel = (function () {
var obj = {};
obj.myVariable = ko.observable();
obj.myComputed = ko.computed(function () { return "hello" + obj.myVariable() });
ko.applyBindings(obj);
return obj;
})();
Couple of reasons:
Not using this, which can confusion when used within ko.computeds etc
My viewModel is a singleton, I don't need to create multiple instances (i.e. new viewModel())