TheMook
TheMook

Reputation: 1541

Durandal and Knockout difference between activate method and ctor.prototype.activate?

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;

    });

Upvotes: 3

Views: 3096

Answers (2)

Brett
Brett

Reputation: 4269

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.

Upvotes: 5

TheMook
TheMook

Reputation: 1541

Well, in the absence of more learned solutions, I have at least managed to get it working. I had to remove the ctor function and make it a plain page variable. Had to remove all reference to "self" as in the last durandal event "this" was something other than the window object.

As "compositionComplete isn't firing in my viewmodel I used "attached" instead.

Working code:

define(['globalVar', 'services/datacontext', 'services/calccontext', "jquery", "knockout"], function (globalVar, datacontext, calcContext, $, ko) {

    var chartConfig = {
        data: ko.observableArray([]),
        title: ko.observable(),
        seriesColors: ko.observableArray([]),
        series: ko.observableArray([]),
        ticks: ko.observableArray([])
    };

    // subscriptions
    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
            chartConfig.data([
                [300, 295, 290],
                [320, 320, 320],
                [260, 265, 260]
            ]);
            chartConfig.title("Forecast net operating costs");
            chartConfig.seriesColors(["#0026FF", "#FF6A00", "#606060"]);
            chartConfig.series([
                { label: "Foo" },
                { label: "Bar" },
                { label: "Baz" }
            ]);
            chartConfig.ticks(["Apr 13", "May 13", "Jun 13"]);
        };

        var activate = function () {
            setChartVars();
            return datacontext.newEntity(articleResults, "articleVersionResults");


        };

        var compositionComplete = function () {
            //var self = this;
            var c = chartConfig;

            jqChart = $.jqplot("chart", c.data());

            // example to change the data
            setTimeout(function () {
                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,
            attached: compositionComplete,
            calcAll: calcAll,
            editArticleVersion: globalVar.editArticleVersion,
            articleResults: articleResults
        };

        return vm;

    });

Upvotes: 0

Related Questions