66

I am building an application that contains two complex, significantly different (yet with some shared components) views. One view allows the user to run queries and look at search results, and the other view gives an overview of recent activity. A related example might be a PIM app that has an email screen and a contacts screen. The two sets of operations are quite different, and yet there are also structural similarities between then. In building out my application, I have started with the search results view. I now need to create the second one, and am wondering about best practices in organizing the code.

Do I create a separate object (sub-view model, I guess) for each application "view" and toggle between them with if/ifnot bindings? One commonality between the views is that each has a scrollable, filterable, pageable list of objects. Should I try to factor out the differences between the lists so that I can have a common sort/filter UI, or do I just create two parallel interfaces that only share my custom bindings?

Thanks,

Gene

Gene Golovchinsky
  • 6,101
  • 7
  • 53
  • 81
  • 27
    @andrew-barber - I believe the question IS demonstrating a minimal understanding. 30+ upvotes on the question and 50+ upvotes on the answer. Clearly a useful question to the SO community. Please re-open. – j-a Mar 21 '14 at 09:04

1 Answers1

107

There are a few directions that you could go with this one.

One option is to call ko.applyBindings with distinct view models against separate DOM elements like:

var viewModelA = { name: "Bob" };
var viewModelB = { price: 50 };

ko.applyBindings(viewModelA, document.getElementById("aContainer"));
ko.applyBindings(viewModelB, document.getElementById("bContainer"));

http://jsfiddle.net/9abgxn8k/

In this case, you would want to make sure that the elements are not ancestors of each other (don't want to bind anything twice)

Another option is to use sub view models:

var subModelA = { name: "Bob" };
var subModelB = { price: 50 };

var viewModel = {
  subModelA: { name: "Bob" },
  subModelB: { price: 50 }
};

ko.applyBindings(viewModel);

In this method, you would then use with bindings on the areas that you want to display with each view model. You can control visibility with flags on the sub models or on the top model.

Another option that I like is to give your "views" a little bit of structure and do something like:

var View = function(title, templateName, data) {
   this.title = title;
   this.templateName = templateName;
   this.data = data; 
};

var subModelA = {
    items: ko.observableArray([
        { id: 1, name: "one" },
        { id: 2, name: "two" },
        { id: 3, name: "three" }
      ])
};

var subModelB = {
    firstName: ko.observable("Bob"),
    lastName: ko.observable("Smith") 
};


var viewModel = {
    views: ko.observableArray([
        new View("one", "oneTmpl", subModelA),
        new View("two", "twoTmpl", subModelB)
        ]),
    selectedView: ko.observable()    
};

ko.applyBindings(viewModel);

http://jsfiddle.net/rniemeyer/PctJz/

jflaga
  • 4,610
  • 2
  • 24
  • 20
RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • Thanks! This is along the lines of what I was thinking I would do. – Gene Golovchinsky Dec 30 '11 at 19:35
  • 1
    When I tried to refactor my code by pushing the existing markup into templates, I got the following error: `This template engine does not support the 'with' binding within its templates` – Gene Golovchinsky Dec 30 '11 at 19:35
  • The error occurs in line 2555 of knockout-2.0.0.debug.js – Gene Golovchinsky Dec 30 '11 at 19:39
  • Looks like it happens when I have the jQuery template included. No works without the template. – Gene Golovchinsky Dec 30 '11 at 19:44
  • 1
    the control flow bindings aren't supported when using jQuery Templates. The jQuery Template engine is enabled when Knockout detects that it is referenced. So, you can remove your reference to jQuery Templates and use the native template engine, as long as you are not using any of its features. This technique could certainly be applied to jQuery Templates, just need to use `template: { name: 'viewTmpl', data: selectedView }` type syntax along with a template instead of `with` (if necessary). – RP Niemeyer Dec 31 '11 at 04:32
  • I'm using the distinct view model approach. I'm curious to know if there's a built in way to access the binding context from ko.applyBindings(viewModel, context) inside the view model? – Rudy Jun 19 '12 at 21:54
  • You can't directly access the element that was bound, but you could use the `ko.dataFor` API to determine what context that an element is bound against as described here: http://knockoutjs.com/documentation/unobtrusive-event-handling.html – RP Niemeyer Jun 21 '12 at 03:37
  • Great answer! Strangely this doesn't work when I changed jQuery in jsFiddle to 1.9.1, but then works again in 2.0.0b3! – Tim Apr 15 '13 at 22:43
  • Is this also possible when using the mappings plugin? – Nick N. May 22 '13 at 08:52
  • Thanks for the answer. I extentended you last option with dynamic loading of markup and script code to make it possible to load html/js lazy and still use KO. http://jsfiddle.net/PctJz/409/ – guido Oct 30 '14 at 12:58
  • I don't have it clear where you use things like ` self.loading = ko.observable(true);`. It seems all your models are just objects. – Alvaro Jun 30 '15 at 16:18
  • 1
    What id you have data that needs to be shared between the two view models? This seems trivial with your second option that shares a parent view model, but what about the first one? Is it possible with that approach too? – Zero3 Apr 20 '16 at 15:28