Function based view models not converting to JSON - javascript

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

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"));

JavaScript Call or Apply using Knockout ViewModelBase

I have this base view model:
var baseViewModel = function () {
var self = this;
// <!----- AJAX SAVING ------!> \\
self.saving = ko.observable();
// <!----- SEARCHING ------!> \\
self.fields = ko.observableArray();
self.selectedField = ko.observable();
self.searchTerm = ko.observable().extend({ throttle: 150 });
}
And I inherit it using this:
var viewModel = function () {
baseViewModel.call(this);
var self = this;
//stufff
}
viewModel.prototype = new baseViewModel();
And it works perfectly. Quite pleased with it.
Now, I want to setup the self.fields property with some initial data, that I want to send through the line baseViewModel.call(this) and I'm not sure whether to do this:
var viewModel = function () {
baseViewModel.call(this, new userModel()); // just a function object
var self = this;
}
OR:
var viewModel = function () {
baseViewModel.apply(this, new userModel()); // just a function object
var self = this;
}
So that the baseViewModel will do this:
var baseViewModel = function (data) {
var self = this;
// <!----- AJAX SAVING ------!> \\
self.saving = ko.observable();
// <!----- SEARCHING ------!> \\
self.fields = ko.observableArray().getKeys(data); // viewModel parameter passed here
self.selectedField = ko.observable();
self.searchTerm = ko.observable().extend({ throttle: 150 });
}
I have read this Difference between call and apply still not sure where to go and I have read the official documentation.
EDIT
I have just tried call because as I understand it the only difference is either putting in a bunch or args (with call) or putting in an array of args (with apply)
Its worked with call so far, just wondering if there are going to be any caveats with choosing this method?
Unless there are any caveats, the only difference is whether the args come as and array or separate objects
Call Link
Apply Link
with call you do baseViewModel.call(this [, arg1, arg2, .... argn])
with apply you do baseViewModel.apply(this [, arg_array[] ])

Knockout multiple viewmodels with same name variables conflict?

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)

ko.computed on an ko.observableArray

I'm trying to use a computed to calculate the total of some product.
function productViewModel(){
self = this;
function productModel(data)
{
var self=this;
self.id=ko.observable(data.id);
self.codigo=ko.observable(data.codigo);
self.recurso=ko.observable(data.recurso);
self.unidad=ko.observable(data.unidad);
self.precio_unitario=ko.observable(0);
self.cantidad=ko.observable(0);
self.total=ko.computed(function()
{
return self.precio_unitario()*self.cantidad();
},productModel);
}
self.products = ko.observableArray([]);
self.addProduct = function(product)
{
self.products.push(new productModel(product));
};
self.removeProduct = function()
{
self.products.remove(this);
};
}
orden = new productViewModel()
ko.applyBindings(orden);
But when precio_unitario and cantidad are changed. total doesn't update.
function productModel(data)
{
var self=this;
...
self.total=ko.computed(function()
{
return self.precio_unitario()*self.cantidad();
},this);
}
You should be binding the ko.computed to this not to the function. You want it to be bound to the object thats created, not to the constructor, which won't have those properties on it. Since you're using self, this will actually be taken care of by default, and if you like you can omit the second argument entirely.
Within the constructor function, this or self will refer to the object that is created when you use the new operator. So all the properties will be created on that object.
self = this; should be var self = this;; otherwise you're overwriting the global self. Also take out ,productModel on the computed; it's not necessary.
Important parts:
function productViewModel() {
var self = this;
function productModel(data) {
var self = this;
...
self.total = ko.computed(function() {
return self.precio_unitario()*self.cantidad();
});
}
...
}
Also it's important make sure you're always using the correct format for writing to observables. It should be self.catidad(newValue); and not self.catidad = newValue;

Knockout JS html binding returning weird code instead of html string

function tournamentViewModel(){
var self= this;
self.name = ko.observable();
self.districts = ko.observableArray([new district('Provo',1),new district('Salt Lake City',2),new district('St. George',3)]);
self.district = ko.observableArray();
self.regions = ko.observableArray([new region('Utah',1),new region('Idaho',2)]);
self.region = ko.observableArray();
self.location = ko.observable();
self.date = ko.observable();
self.startTime = ko.observable();
self.image = ko.observable();
self.flyer = ko.computed(function(){return '<h1>'+self.name+'</h1>'+self.image},self);
self.clearImage = function(){
self.image('');
}
self.tournamentID = ko.computed(function(){return 't_'+self.district+'_'+self.region+'_'+self.date}, self);
};
The above knockout.js view model seems to be fine except for when I want to bind something to the computed observable flyer. Instead, all I see is the following text:
<h1>function c(){if(0<arguments.length){if(!c.equalityComparer||!c.equalityComparer(d,arguments[0]))c.I(),d=arguments[0],c.H();return this}a.U.La(c);return d}</h1>function c(){if(0<arguments.length){if(!c.equalityComparer||!c.equalityComparer(d,arguments[0]))c.I(),d=arguments[0],c.H();return this}a.U.La(c);return d}
I don't know what's going on here. Below is the binding I'm applying it to. I've tried both html and text bindings.
<span data-bind="text: flyer"></span>
BTW the computed observable tournamentID works great and the syntax seems identical. I think the problem occurs when I use self.name in the computed observable. Any ideas?
Think about it. What do you get? You get the function definition. Because you passed function to your computed. And you need to pass values. You should use:
self.flyer = ko.computed(function(){
return '<h1>'+self.name()+'</h1>'+self.image();
});
since both name and image are observables (from JavaScript point of view: functions).
I'm not sure why tournamentID is working for you. It shouldn't.
BTW If you are using var self = this;, then you can omit the second argument of computed.
try this
<span data-bind="text: flyer()"></span>

Categories

Resources