Reputation: 50
Pretty unsure if I used to understand and now do not understand it, or if I didn't understand it and now I'm starting to... or if I didn't and still don't.
I'm building an app that lets users book appointments.
Screen 1: Select a Location and Service Type (Options lists, button to confirm and move on)
Screen 2: Select an available Appointment time (table, button to confirm and move on)
Screen 3: Enter Name and Contact Info (text inputs, button to confirm and move on)
Screen 4: Enter verification code (texted to user, button to confirm and move on)
Screen 5: Displays confirmation code
I'm refactoring because I realized everything was in my ViewModel and unsure of what belongs in my Model and what belongs in my ViewModel. I just put selectedLocation (the item the user picks from the list) in the Model but I need it to be accessible by the View, so I did this:
self.selectedLocation = ko.pureComputed({
read: function() {
return self.model.selectedLocation();
},
write: function(value){
self.model.selectedLocation(value);
}
});
Which just seems bonkers.
Should this just be in the ViewModel? Should I have a selectedLocation variable in the Model and a currentlySelectedLocation in the ViewModel that then updates model.selectedLocation when the user confirms the Location and Service Type?
Should each separate screen have it's own viewmodel? I'm using a single .html file and updating what is displayed rather than going from page to page.
I'm sure I'm not including some kind of vital piece of information and I apologize, happy to answer any questions.
Any help would be greatly appreciated.
Upvotes: 1
Views: 101
Reputation: 448
It would be a good idea to separate your model and viewmodel. They way I look at it is the model is a representation of the data that you're working with and the viewmodel pulls together the various models and adds controller-like functionality to run the logic of your page (handling UI interactions, fetching data, data binding, etc.). Your models will simply be a bundle of properties, and maybe some helper properties that do things like provide a formatted display text from the model's raw data. But the model won't have any real logic in it. If you need the two-way data binding on the model properties themselves, then you simply define them as a KO observable.
Given that you want to do this all in one html file (basically a SPA), you'll definitely want to break this out into several "viewmodels". I put quotes around "viewmodels" in this case because Knockout can only apply bindings to a single viewmodel object, but you can nest Javascript objects with KO observables n-levels deep and KO can follow the object graph (as stated in Michael Best's answer). Let's call these nested "viewmodels" sub-viewmodels.
I recommend creating a master viewmodel which represents the app as a whole, and at least one sub-viewmodel per page of the app. So it might look something like this:
var AppViewModel = function (screen1Vm, screen2Vm, ...) {
var self = this;
self.screen1ViewModel = screen1Vm;
self.screen2ViewModel = screen2Vm;
...
}
var Screen1ViewModel = function () {
var self = this;
self.selectedLocation = ko.observable(null); //Initialize as null
self.locations = ko.observableArray([]); //assuming they're selecting from a list of Location objects. Initialize as empty array.
...
//More logic to handle UI events, like a KO click event binding when one of the locations is selected, which is where self.selectedLocation would get set. Something like this
self.locationSelected = function (location) {
//When bound to a control inside a KO foreach context, the actual object in the KO observable array will be passed in.
self.selectedLocation(location);
}
}
var Location = function (description, city, state, ...) {
var self = this;
self.description = ko.observable(description);
self.city = ko.observable(city);
self.state = ko.observable(state);
...
}
...
var screen1Vm = new Screen1ViewModel();
var screen2Vm = new Screen2ViewModel();
...
var myAppVm = new AppViewModel(screen1Vm, screen2Vm, ...)
ko.applyBindings(myAppVm);
Then you'd have some HTML that looked something like this (obviously changing DOM elements to what you need for your view):
<div data-bind="foreach: screen1ViewModel.locations">
<!--Bind the click event to the viewmodel handler, and display the description of the location in the button-->
<button data-bind="click: $parent.locationSelected"><span data-bind="text: description"></span></button>
</div>
One final point. Since you're doing this all in one page, I suggest using KO templates as much as possible to organize the HTML, and put each of the sub-viewmodels in their own .js file, but under a global namespace that you define for your app.
Upvotes: 1
Reputation: 16688
The cool thing about observables is that you can just use them in other objects:
self.selectedLocation = self.model.selectedLocation;
But I would suggest that it's also fine to just bind directly to model.selectedLocation
. In Knockout, there's no need to have a model/view-model distinction; everything is a view-model.
Upvotes: 3