9
votes

I've started using (as of, a few hours ago) Durandal with the hope to manage views and allow composition within a single page - the previous approach, also using Knockout, was getting too unwieldy to maintain in a fat HTML file.

I've installed/setup Durandal and I can create views and viewmodels - however, I don't know how to get data into the viewmodel to use as a basis for the new viewmodel.

For instance, I have a "left nav bar" for selecting items - when an item is selected it updates a "selected item" observable in the current model, but it should also load the correct "detail view" in the right: because part of the reason to try Durandal to separate the components/views, this should come from a separate view/viewmodel.

I've read through the documentation including Composition and Using Composition, but the method as to how to pass data to the viewmodel is not clear to me. It seems like I could use a view with an existing model (in the current view/scope), but I'd really like to merely use some of the current model data (i.e. an id) to fetch the "real" model data in the view.

Thus, my questions;

  • How is passing initial data, such as the "selected item" to a viewmodel handled? I would prefer to use the declarative "compose:" binding, as that is what make KO .. KO.

  • Is this notion of having/passing initial data the correct way here, or is there a better alternative? I have seen "activeItem" vaguely mentioned, but the details/useage aludes me.


Update 1: I've found How do we share the data between views/Pass the data view to view, but the responses are lacking in actual implementation. I'd really like to not share the viewmodel between parent-child (distinct view/viewmodel pairs) and I'd like to use a declarative approach (no events). The parent need not know about the child, but the child needs to be fed data from the parent.


Update 2: While above I hinted only the "id" was required, I would like an approach that works with any basis object, which the router is not necessarily suitable for. Deep linking is not a concern in this case. However, if you feel the router is the way to approach this, post an argument for such (with details) as an answer and I'll at least give it an up-vote ..

3

3 Answers

3
votes

If you look at the breeze source code, you can see an example of a master-detail approach.

They are using an activator to compose the child view.

https://github.com/IdeaBlade/Breeze/blob/master/Samples/TempHire/TempHire/App/viewmodels/resourcemgt.js

I don't know if you are using breeze or not, but the approach is independent of how do you get the data.

3
votes

There is a great writeup on how to do this with John Papa's SPA template for Visual Studio by Eric Panorel here: http://ericpanorel.net/blog/hot-towel-spa-master-detail-scenario.

In short, you can take advantage of routing in Durandal, and it would go something like this:

master.js will expose a set of items:

define(function() {
  var self = this;

  this.items = ko.observableArray([]);

  var activate = function() {
     // ... get your items
     self.items([ { id: 0, name: 'Item 1' }, { id: 1, name: 'Item 2' }]);
  }

  return {
    // some properties and ...
    activate: activate
  }
});

master.html will bind them and link to details view:

<section data-bind="foreach: items">
   <a data-bind="text: name, attr: { href: '#/item/' + id() }"></a>
</section>

Now, all that remains it to use route data parameter to pull the ID out in the detail viewmodel, detail.js:

define(function() {
  var self = this;

  this.name = ko.observable();

  var activate = function(routeData) {
     var itemId = routeData.id;

     // ... get your item details
     // var details = ...

     self.name(details.name);
  }

  return {
    // some properties and ...
    activate: activate
  }
});

Finally, you will need to specify the route in your main.js:

define(['durandal/app', 'durandal/viewLocator', 'durandal/system', 'durandal/plugins/router'],
function(app, viewLocator, system, router) {

    //>>excludeStart("build", true);
    system.debug(true);
    //>>excludeEnd("build");

    app.title = 'Durandal app';
    app.start().then(function() {
        toastr.options.positionClass = 'toast-bottom-right';
        toastr.options.backgroundpositionClass = 'toast-bottom-right';

        //Replace 'viewmodels' in the moduleId with 'views' to locate the view.
        //Look for partial views in a 'views' folder in the root.
        viewLocator.useConvention();

        //configure routing
        router.useConvention();
        router.mapRoute('item/:id', 'viewmodels/item', 'Item', false);

        app.adaptToDevice();

        //Show the app by setting the root view model for our application with a transition.
        app.setRoot('viewmodels/shell', 'entrance');
    });
});

And there you go!

0
votes

I think that it's possible to pass any basis object as we do with modal call. ko.compose is passive; with durandal router, you can carry your data to where you like. Have a look to my work-around in this answer and help to perform.