Knockout multiple viewmodels with same name variables conflict? - javascript

I bound multiple ko viewmodels to different panels in the same page, but when the viewmodels have properties with the same name they seem to lose their binding to their own viewModel like:
var Panel1ViewModel = function Panel1ViewModel() {
var self = this;
self.isVisible = ko.observable(false);
self.change1 = function() {
self.isVisible(!self.isVisible());
};
};
ko.applyBindings(Panel1ViewModel(), document.getElementById('panel1'));
var Panel2ViewModel = function Panel1ViewModel() {
var self = this;
self.isVisible = ko.observable(false);
self.change2 = function() {
self.isVisible(!self.isVisible());
};
};
ko.applyBindings(Panel2ViewModel(), document.getElementById('panel2'));
To make it more clear I recreated the problem in jsfiddle.
I know I can nest ViewModels with with but the page is big and some content is loaded dynamically so I want to separate it.
Can someone explain me why this is happening and wat a possible solution is?

You're not initiating your view models correctly. Try it like this:
var Panel1ViewModel = function Panel1ViewModel() {
var self = this;
self.isVisible = ko.observable(false);
self.change1 = function() {
self.isVisible(!self.isVisible());
};
};
ko.applyBindings(new Panel1ViewModel(), document.getElementById('panel1'));
var Panel2ViewModel = function Panel1ViewModel() {
var self = this;
self.isVisible = ko.observable(false);
self.change2 = function() {
self.isVisible(!self.isVisible());
};
};
ko.applyBindings(new Panel2ViewModel(), document.getElementById('panel2'));
http://jsfiddle.net/XWD96/3/
The difference is that the new operator will create a new object (this inside your view model). So by not having the new, this will point to the window in both view models, therefor causing conflicts.
You can read more about Constructor Functions (new) here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Using_a_constructor_function)

Related

asp.net & Knockout : ko.subscribable() - one vm in sitemaster.js (globally) and one vm in mypage.js?

I'm using the ko.subscribable() functionality, to pass an object from knockout in mypage.js used in mypage.aspx (which inherits from site.master) to knockout in sitemaster.js (site.master.aspx) which is loaded and used in every page in my solution.
The problem is.. when following the recommended structure of using this functionality (see this JSFiddle) it states that i have to have one "MasterViewModel" which creates both of the viewmodels, which i find problematic in the scenario where you have two seperate javascript files, either though both are loaded in the destinationpage (mypage.aspx).
This is the necessary code i cannot figure out how to handle :
var masterVM = (function(){
this.viewModel1 = new viewModel1(),
this.viewModel2 = new viewModel2();
})();
ko.applyBindings(masterVM)
Here is my code :
mypage.js
var shouter = new ko.subscribable();
var PhotoModel = function() {
var self = this;
self.photos = ko.observableArray();
self.selectedPhoto = ko.observable();
self.setSelectedPhoto = function(selPhoto) {
self.selectedPhoto.subscribe(function (selPhoto) {
shouter.notifySubscribers(selPhoto, "photoToShow");
});
}
}
var photosModel = new PhotoModel();
ko.applyBindings(photosModel, document.getElementById("latest-photos"));
sitemaster.js
var PhotoViewModel = function(photosModel) {
var self = this;
self.viewPhoto = ko.observable();
shouter.subscribe(function(selPhoto) {
self.viewPhoto(selPhoto);
}, self, "photoToShow");
};
var photoViewModel = new PhotoViewModel();
ko.applyBindings(photoViewModel, document.getElementById("photo-viewer"));

Unable to modify ko observable when function defined with object prototype

I understand it is in general bad practice to modify prototypes with the object.prototype.whatever syntax when using knockout, but I'm trying to understand why this isn't working at all:
var Foo = function() {
var self = this;
self.bar = ko.observable("bar");
};
Foo.prototype.capitalizer = function() {
self.bar("Bar");
};
var vm = function() {
var self = this;
self.whatever = new Foo();
};
js fiddle here: http://jsfiddle.net/vvdo7z70/8/
when this works as expected:
var Foo = function() {
var self = this;
self.bar = ko.observable("bar");
self.capitalizer = function() {
self.bar("Bar");
}
};
var vm = function() {
var self = this;
self.whatever = new Foo();
};
js fiddle here: http://jsfiddle.net/vvdo7z70/10/
Is it just not possible to pass relevant ko bindings with the object.prototype syntax? or is there another way to do it?
For one, self isn't defined in Foo.prototype.capitalizer. Once that's fixed, you need to note that the binding click: whatever.capitalizer is using the function, not the method, which is to say, the whatever context is not provided. Instead, vm is provided as the context. This will work:
Foo.prototype.capitalizer = function () {
this.whatever.bar("Bar");
};
or
click: whatever.capitalizer.bind(whatever)

Understanding backboneJS: View.extend()

Until today I thought using backbones functionality like
var PageView = Backbone.View.extend({
template: $.Deferred,
getTemplate: function(tplName) {
var self = this;
ajaxCall(tplName).then(function(hbs) {
self.template.resolve(hbs);
});
}
});
var home = new PageView();
home.getTemplate('home.hbs');
is similar to a pure JS OOP approach like
var PageView = function() {
this.template = $.Deferred();
}
PageView.prototype.getTemplate = function(tplName) {
var self = this;
ajaxCall(tplName).then(function(hbs) {
self.template.resolve(hbs);
});
}
var home = new PageView();
home.getTemplate('home.hbs');
However, I'm currently trying to rebuild a web-app with backbone and it seems like solving that deferred with
home.getTemplate('home.hbs');
will resolve this for all instances of the Backbone view PageView while in pure JS this would only resolve for that one particular instance home.
When I do a second instance:
var page1 = new PageView();
the template property is already resolved with the home.hbs template in backbone. Which is very weird to me.
So my guess is that I'm fundamentally misunderstanding how backbone views work.
Can someone enlighten me?
The difference is in the first snippet template property of all the instances refer to the same object and in the second snippet the property is set using a different/new instance of Deferred when the constructor function is executed. If you set the template property within the initialize function of the constructor you will get the same result as the second snippet:
var PageView = Backbone.View.extend({
// template: $.Deferred,
initialize: function() {
this.template = $.Deferred;
},
getTemplate: function(tplName) {
var self = this;
ajaxCall(tplName).then(function(hbs) {
self.template.resolve(hbs);
});
}
});
In the second example if you set the template to the prototype of the constructor then it will behave like the backbone constructor:
var PageView = function() {
// this.template = $.Deferred();
}
PageView.prototype.template = $.Deferred();
var instance1 = new PageView();
var instance2 = new PageView();
console.log( instance1.template === instance2.template ); // true

Function based view models not converting to JSON

I have a nested viewmodel setup that need to be expressed as functions due to having to have instances (unless I am missing something). Everything works in the UI, I can nest viewmodels that have nested viewmodels and so on.
However in its current form I get an error
JavaScript runtime error: Pass a function that returns the value of the ko.computed
when trying to call ko.toJSON(x);.
This worked when I had the vms defined without the need for instances however my nesting did not hence the change.
Here is an example of how it is currently.
var CityViewModel = function() {
var self = this;
self.Name = ko.observable("");
self.ATMs = ko.observableArray();
self.AddATM = function () {
self.ATMs.push(new ATMViewModel);
}
self.GetJson = function() {
alert(ko.toJSON(self)); //Area of interest
}
}
var ATMViewModel = function() {
var self = this;
self.PostCode = ko.observable("");
self.Features = ko.observableArray();
self.AddFeature = function () {
self.Features.push(new FeaturesViewModel());
}
}
var FeaturesViewModel = function () {
var self = this;
self.Name = ko.observable("");
self.Reference = ko.observable("");
}
ko.applyBindings(CityViewModel);
Took me a while to spot it as well.. a missing new keyword
ko.applyBindings(new CityViewModel());
// ====
As a Fiddle: http://jsfiddle.net/Quango/zf0dLLyr/
I'd recommend Ryan's suggestion for debugging:
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
It was the fact that returned nothing gave me the hint. See
http://www.knockmeout.net/2013/06/knockout-debugging-strategies-plugin.html

Knockout ViewModel base class, Javascript inheritance

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.

Categories

Resources