Quick question:
Will KnockoutJS provide a solid ground for developing a large web app? I am afraid of having one huge viewModel that will become unmaintainable.
Background info
I'll be building a web app that will be heavily client-side based. The backend will just be a RESTful endpoint. The entire interface of the web app will be built in pure HTML/CSS/JS - no server side scripting involved.
The web app itself will consist of several smaller apps with one general login (kind of like Google's web apps where you have Gmail, Docs, Calendar, Reader, etc.).
Each of those web apps will have some common functionality (such as a sidebar tree view, a top bar menu view, a notifications system), and some app-unique features. Usually I break my apps down to encapsulate functionality, something like:
var myNamespace = {
common: {
settings: {},
user: {},
notifications: {}
},
app1: {},
app2: {},
app3: {}
};
Now, I really enjoy working with KnockoutJS and figured that it will be helpful when building some elements of my project (such as the notification system, or an advanced grid view with auto-refresh as the app will support collaboration). But I just can't figure out where to put my viewModel into this structure.
I can only find trivial examples of how to build apps with KnockoutJS. Can you actually build something more advanced than a Twitter reader with it? Are there any good examples of how to break down a lot of functionality in the viewModel, or perhaps into many viewModels?
Proposed solution
While the more theoretical question (the Quick question) is still kind of unanswered here, I think I've found a solution that works in practice. @Simon 's answer gave me some food for thought, and here's what I've got so far:
// First: a collection of Observables that I want to share
ld.collectionOfObservables = {
notifications: ko.observableArray([]),
};
// Now let's define a viewModel. I put all my stuff inside the
// 'ld' namespace to avoid cluttering the global object.
ld.viewModel1 = function (args) {
// Look inside args and bind all given parameters
// Normally you will want args to be an object of Observables.
for (var key in args) {
if (args.hasOwnProperty(key)) {
this[key] = args[key];
}
};
// So, by now we already have some observables in
// 'this', if there were any supplied in 'args'.
// Additionally, we define some model-unique properties/observables
this.folders = [ 'Inbox', 'Archive', 'Sent', 'Spam' ];
this.selectedFolder = ko.observable('Inbox');
};
// *** Let's pretend I create similar class and call it ld.viewModel2 ***
ld.viewModel2 = function (args) { .... }
// OK, now go on and instantiate our viewModels!
// This is the fun part: we can provide 0-many observables here, by providing them in an object
// This way we can share observables among viewModels by simply suppling the same observables to different viewModels
var vm1 = new ld.viewModel1({
notifications: ld.collectionOfObservables.notifications, // we take an Observable that was defined in the collection
});
var vm2 = new ld.viewModel2({
notifications: ld.collectionOfObservables.notifications, // shared with vm1
});
// Of course, we could just send the entire ld.collectionOfObservables as an array
// but I wanted to show that you can be more flexible and chose what to share.
// Not easy to illustrate with *one* shared Observable - notifications -
// but I hope you get the point. :)
// Finally, initiate the new viewModels in a specified scope
ko.applyBindings(vm1, document.getElementById('leftPane'));
ko.applyBindings(vm2, document.getElementById('bottomPane'));
Now, if JS had real inheritance it'd be even better cause right now I feel that all my viewModels start with this:
for (var key in args) {
if (args.hasOwnProperty(key)) {
this[key] = args[key];
}
};
But that's just a minor inconvenience. Let me know what you think!
Edit 1:
Could the solution be as simple as using the with:
binding? See "1. Control flow bindings" for an example.
Edit 2:
I think my last edit was too quick. with:
binding may help with the structure of your code, but AFAIK it doesn't help you share observables between those different parts. So the proposed solution above is still the way to go.
I've used partial views (in Nancy rather than MVC), each with their own knockout task, each with their own view model. I think it works beautifully - a complicated page broken apart into many simple independent partials. Most of the partial views have their own module/controller/endpoint so the modules are 'skinny' too.
It's a shame about the jQuery templates being dropped, but that's a different issue.
Sorry, I just re-read your post: no server side stuff, so no way to break up the page? Ouch. I still think lots of view models is the way to go.
You can use partial views and share observables between them.
I've been using this aproach to maintain a list photos in a gallery application, where one view renders thumbnails and another view takes care of uploads.
In my view we could use KO and share View Models to the scope of functional module( say functional widget having multiple html controls). We could look into using TIBCO Page bus (Pub/Sub) for communicating between these functional modules in the page to keep the functional modules in a page decoupled and managable manner.
This is an old post but recently I have built a framework for exactly the same purpose in this repository that I call gcc-knockout. Everything is a component and there is even a view manager to switch views completely and keep history at the sametime. I have not properly documented it yet but the repo comes with an example that demonstrates some of its features.
Note that I also used Google Closure Compiler. You can safely use it in Advanced Mode given that you properly export the properties you will use in html templates. Components communicate using goog.events and everything is pretty clean right now. I have not used knockout's subscribe utility. Feel free to check it out and contribute! I occasionally update it.