Using internal functions with Knockout
The applyBindings
method in the previously explained bindings section uses a common structure. We included that example in case you cross code written in that style. It will help you understand it if you see it someone else's code that is using that approach. Another style of coding is to declare the Model with a function declaration. The DOM markup or View code does not change for either scenario. Let's convert our binding example to a functional declaration. Copy the binding.htm
file and create a new file called functions.htm
in the same folder. Here is the original declaration:
var viewModel = { myVariable: ko.observable() ,myHTML: ko.observable() ,myLeft: ko.observable() ,myRight: ko.observable() ,myBalance: ko.observable() ,isVisible: ko.observable() };
Note that we could have set the values of the observables immediately along with the declaration. If we had done that it would have looked like this:
var viewModel = { myVariable: ko.observable("Awesome simple!") ,myHTML: ko.observable("<strong>Awesome</strong> simple!") ,myLeft: ko.observable("pullLeft") ,myRight: ko.observable("pushRight") ,myBalance: ko.observable(-47.23) ,isVisible: ko.observable(true) };
We are doing this to tighten our code for the sake of the book. You should use good logic when choosing which way to do this in your code. There are times where using this style of coding is important, but often it is just a matter of the coder's style. Beware of letting your style keep you from considering which one is best as you write the code.
Now we will look at moving the code over to a functional declaration. We start, of course, with a different kind of declaration because it is a function as appears here:
function viewModel() { // add declarations here}
It would be equally valid to declare the ViewModel as follows. There is no significant difference:
viewModel = function() { // add declarations here}
Now, we will look at adding our ViewModel items back in. In the structured approach we just described, the items were entered as collection items with the classic comma separator. In this model, each item is a parameter and is terminated by a semi-colon:
viewModel = function() { this.myVariable = ko.observable("Awesome simple!"); this.myHTML = ko.observable("<strong>Awesome</strong> simple!"); this.myLeft = ko.observable("pullLeft"); this.myRight = ko.observable("pushRight"); this.myBalance = ko.observable(-47.23); this.isVisible = ko.observable(true);}
Note that we declared each of these items with the this
scope declaration, which refers to the ViewModel object. When programming in JavaScript, it is common practice to alias this
to avoid scope confusion. We will rewrite the previous code one more time using self
rather than this
as the base scope:
viewModel = function() { var self = this; self.myVariable = ko.observable("Awesome simple!"); self.myHTML = ko.observable("<strong>Awesome</strong> simple!"); self.myLeft = ko.observable("pullLeft"); self.myRight = ko.observable("pushRight"); self.myBalance = ko.observable(-47.23); self.isVisible = ko.observable(true);}
Tip
Note that we set the self
variable using a var declaration. This prevents issues of external naming conflicts.
Now, if we run the page for functions.htm
from our browser, it should run identical to our binding.htm
file. Yet, there is a difference. This will help you understand why we introduced the developer tools where we did in the course. Open the tools and in the command prompt, enter viewModel.isHTML()
to see what you get as a result:
Preventing hidden features
In the previous screenshot we obtained the what you might think is an unexpected result as the View is obviously bound to the ViewModel. The issue here is an issue of the concept of closure. You are welcome to explore more about closure if you wish but just realize it means parts of an object or the item contents are there but hidden. When this type of declaration is made in this style, you cannot interact with it from JavaScript. The declaration should have been made with new
to create an object from the function as follows:
ko.applyBindings(new viewModel());
If you run the browser and try to connect to the ViewModel now, you will see that it is still having the same issue with closure. We found this is the best way to work around it at my company:
vm = new viewModel(); ko.applyBindings(viewModel);
Now, we will reference the Model using vm
rather than viewModel
and this is the result we will get:
We see that by declaring the object outside the argument passed to our Knockout applyBindings
method, we avoided the closure issue. This is not an issue when using the structured style of ViewModel declaration. Hopefully, this saves you from hours of wondering what is wrong with your code or if Knockout is broken. We will not answer how much time I burned on this the first time it occurred, but it was long after I started using Knockout. It says that even experts can make rookie mistakes. I humbled myself and asked the community for help, and the answer came pretty fast.