Search code examples
durandalknockout-mapping-plugin

Durandal KO binding when data is fetched in activate


A parameter governs what data is to be displayed. The parameter is retrieved from activationData in the activate method of the view model and used in a call to a Web Api method. Data is returned, and added to the view model like this

define(['durandal/app', 'knockout', 'moment'], 
       function (app, config, ko, moment) {

  var vm = {
    app: app
  };

  vm.activate = function (activationData) {
    vm.ChecklistInstanceId = activationData.ChecklistInstanceId;
    $.ajax({
      url: "api/ChecklistInstance/" + vm.ChecklistInstanceId,
      headers: { Authorization: "Session " + app.SessionToken() }
    }).done(function (data) {
      $.extend(vm, ko.mapping.fromJS(data));
    });    
  };

  return vm;

});

Inspecting the viewmodel immediately after it is extended reveals that it is decorated with observables exactly as expected. For example, vm.Caption() exists and returns the string I expect, and vm.Section() is an appropriately populated observable array, and so on down a fairly elaborate object graph.

The problem is the binding phase has already occurred, and at that time the view model lacks all the observables to which I'm trying to bind.

Two possible strategies suggest themselves:

  • obtain the parameter earlier
  • re-bind

I don't know how to do either of those things. Can anyone tell me how to re-organise my code to allow binding to parametrically fetched data?


Solution

  • A third possibility occurred to me:

    define(['durandal/app', 'knockout', 'moment'], 
           function (app, config, ko, moment) {
    
      var vm = {
        app: app,
        Caption: ko.observable(),
        Section: ko.observableArray()
      };
    
      vm.activate = function (activationData) {
        vm.ChecklistInstanceId = activationData.ChecklistInstanceId;
        $.ajax({
          url: "api/ChecklistInstance/" + vm.ChecklistInstanceId,
          headers: { Authorization: "Session " + app.SessionToken() }
        }).done(function (data) {
          var foo = ko.mapping.fromJS(data);
          vm.Caption(foo.Caption());
          vm.Section(foo.Section());
        });    
      };
    
      return vm;
    
    });
    

    This works because all the observables exist in the binding phase. This might seem surprising given that I describe only the root of a potentially deep object graph, but the fact that the observable array is empty causes the binding phase to exit without a hitch.

    Later in the activate handler, values are added to the observable array after ko.mapping has its way with the data, and binding succeeds.

    I have a sense of dèja vu from this: it is eerily reminiscent of problems solved using forward declarations in TurboPascal back in the eighties. La plus ça change...