I'm trying to use jqplot with Durandal and knockout. I've found a post by Rob on the Durandal group (https://groups.google.com/forum/#!topic/durandaljs/WXBiSK3WmIs) that addresses this but it uses a construction for the viewmodel that completely confuses me as it adds a "prototype.activate" method to a constructor and that's completely new to me (and doesn't work when I try and use it).
Can anyone please try and tell me how I could go about using Rob's example with my revealing module pattern as below?
My viewmodel:
define(['globalVar', 'services/datacontext', 'services/calccontext"], function (globalVar, datacontext, calcContext) {
var activate = function () {
return datacontext.newEntity(articleResults, "articleVersionResults");
};
var calcAll = function () {
//can be called externally and recalcs all
return individualImpactCalc('byWeightAndFactor', 'CO2e', articleResults().material_CO2e);
//will be more calls in here soon
};
var individualImpactCalc = function (calcName, fac, calcObservable) {
return calcContext.indCalc(calcName, fac, calcObservable, globalVar.components()); //puts result straight into observable
};
var vm = {
activate: activate,
calcAll: calcAll
};
return vm;
});
Rob's code sample:
define(["jquery", "knockout"], function($, ko){
// constructor
var ctor = function () {
var self = this;
// properties
self.chartConfig = {};
};
ctor.prototype.activate = function(id, page) {
var self = this;
// get data from database
self.chartConfig.data([
[300, 295, 290],
[320, 320, 320],
[260, 265, 260]
]);
self.chartConfig.title("Forecast net operating costs");
self.chartConfig.seriesColors(["#0026FF", "#FF6A00", "#606060"]);
self.chartConfig.series([
{ label: "Foo" },
{ label: "Bar" },
{ label: "Baz" }
]);
self.chartConfig.ticks(["Apr 13", "May 13", "Jun 13"]);
};
ctor.prototype.compositionComplete = function (view, parent) {
var self = this;
var c = self.chartConfig;
self.jqChart = $.jqplot("chart", c.data(), ...
// example to change the data
setTimeout(function () {
self.chartConfig.data([
[280, 280, 280],
[280, 280, 280],
[280, 280, 280]
]);
}, 4000);
};
return ctor;
});
Edit 1: The problem is that compositioncomplete is not firing. This viewmodel is a subview within the main page. No idea if that's affecting things?
My viewmodel is now:
define(['globalVar', 'services/datacontext', 'services/calccontext', "jquery", "knockout"], function (globalVar, datacontext, calcContext, $, ko) {
// constructor
var ctor = function () {
var self = this;
// properties
self.chartConfig = {
data: ko.observableArray([]),
title: ko.observable(),
seriesColors: ko.observableArray([]),
series: ko.observableArray([]),
ticks: ko.observableArray([])
};
// subscriptions
self.chartConfig.data.subscribe(function (newValue) {
var opts = {
data: newValue
};
if (self.jqChart) {
self.jqChart.replot(opts);
console.log("chart replotted", opts);
}
});
};
function setChartVars() {
var self = this;
// get data from database
self.chartConfig.data([
[300, 295, 290],
[320, 320, 320],
[260, 265, 260]
]);
self.chartConfig.title("Forecast net operating costs");
self.chartConfig.seriesColors(["#0026FF", "#FF6A00", "#606060"]);
self.chartConfig.series([
{ label: "Foo" },
{ label: "Bar" },
{ label: "Baz" }
]);
self.chartConfig.ticks(["Apr 13", "May 13", "Jun 13"]);
};
var activate = function () {
ctor();
setChartVars();
return datacontext.newEntity(articleResults, "articleVersionResults");
};
var compositionComplete = function () {
var self = this;
var c = self.chartConfig;
self.jqChart = $.jqplot("chart", c.data());
// example to change the data
setTimeout(function () {
self.chartConfig.data([
[280, 280, 280],
[280, 280, 280],
[280, 280, 280]
]);
}, 4000);
};
var calcAll = function () {
//can be called externally and recalcs all
//return res_mat_gwp();
return individualImpactCalc('byWeightAndFactor', 'CO2e', articleResults().material_CO2e);
};
var individualImpactCalc = function (calcName, fac, calcObservable) {
return calcContext.indCalc(calcName, fac, calcObservable, globalVar.components()); //puts result straight into observable
};
var vm = {
activate: activate,
compositionComplete: compositionComplete,
calcAll: calcAll,
editArticleVersion: globalVar.editArticleVersion,
articleResults: articleResults,
ctor: ctor
};
return vm;
});
You have to understand the fundamental difference between returning a singleton viewmodel and a function viewmodel. You also need to understand how inheritance works in JavaScript to grasp the purpose behind the prototype property and why it's used.
You already know singleton viewmodels - this is how you have been constructing your viewmodels all this time, so, it is most familiar to you. The other way, which you don't understand very well, is a way to return an "interface" to a viewmodel, with the purpose of instantiating one or more of them. If you return a viewmodel as a function, you must use the new keyword to instantiate it. Let me illustrate this using an example.
This example viewmodel returns a function, not a singleton:
define([], function () {
var example = function (title) {
this.title = title;
};
example.prototype.activate = function (params) {
// do something with the params
};
return example;
});
This viewmodel returns a singleton and requires the previous one as a dependency:
define(['viewmodels/example'], function (Example) {
return {
exampleOne: new Example('This is a test!'),
exampleTwo: new Example('This is another test!')
};
});
Since 'viewmodels/example' returns a function, we must use the new keyword to instantiate it (NOTE: Durandal will do this for you if you are using composition binding in your view).
Both exampleOne and exampleTwo have unique titles; however, they share the same activate() method. That's the advantage of the prototype property. This strategy can be applied to views you want duplicated, but share the same activation code, like modal dialogs or widgets.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With