Automating calculations with Knockout
In this section, we will take functions to a deeper level.
In the code bundle of chapter 1 do
folder (/ko_1/do/
), copy the base.htm
file as computed.htm
for this segment. After the leading body
tag, put in the following View code:
<table> <tr> <th style="width:20%;"> Item </th> <th style="width:40%;"> Qty </th> <th> Price </th> <th> Tally </th> </tr> <tr> <td style="width:20%;"> <em data-bind="text:item_1">Item</em> </td> <td style="width:40%;"> <input type="text" data-bind="value:qty_1" /> </td> <td> <span data-bind="text:price_1"></span> </td> <td> <span data-bind="text:tally_1"></span> </td> </tr> </table>
Now we are ready to create the scripted side of the Knockout ViewModel code. Place this code inside the script
tag after the KnockoutJS library is included:
calcModel = function(){ var self = this; self.item_1 = ko.observable('Cell Phone'); self.qty_1 = ko.observable(0); self.price_1 = ko.observable(149.95); self.tally_1 = ko.computed(function(){ var rslt = (+self.qty_1() * self.price_1()).toFixed(2); return rslt; },self); } vm = new calcModel(); ko.applyBindings(vm);
Notice that the computed function is now doing live calculations. We also added a fixed decimal position of two places. Enter a quantity to test:
Create a second row of the table and the ViewModel that matches the features of the first row. I want you to create this on your own to make sure you are getting the skills as you go along here. You could, of course, peek at the example in the done
folder, but you should try it on your own first.
Subtotal calculation
Now add this row to the bottom of the table to create a row for the subtotal. You can also see we created a cell in the table to tally the number of items in qty_1
and qty_2
:
<tr> <td style="width:20%;"> </td> <td style="width:40%;"> <span data-bind="text:t_qty"></span> </td> <td> <em>subTotal</em> </td> <td> <span data-bind="text:subTotal"></span> </td> </tr>
You also need to put the script code into the correct locations:
self.item_2 = ko.observable('Cell Case'); self.qty_2 = ko.observable(0); self.price_2 = ko.observable(19.45); self.tally_2 = ko.computed(function(){ var rslt = (+self.qty_2() * self.price_2()).toFixed(2); return rslt; },self); self.t_qty = ko.computed(function(){ return +(self.qty_1()) + +(self.qty_2()); },self); self.subTotal = ko.computed(function(){ var rslt = (+(self.tally_1()) + +(self.tally_2())).toFixed(2); return rslt; });
You may have noticed that we placed an extra +
before some of the variables. This is a standard JavaScript approach to making sure the number entered into the input box comes out as a number. In some browsers and some conditions the number is the string representation of the number. The addition of +
solves this issue.
Now run the page and enter the quantity to ensure that all of your calculations are working:
Tax time
What fun would buying things be without paying taxes? Well, either way it is a function we all need to get right. Add the following code to the table to have a row for taxes:
<tr> <td style="width:20%;"> </td> <td style="width:40%;"> ( rate in decimal <input data-bind="value: taxRate;" /> ) </td> <td> <em>Tax</em> </td> <td> <span data-bind="text:taxed"></span> </td> </tr>
Now add the code here to the script
section of the page to make our ViewModel run smartly. Our goal is not to write the most efficient code in all of the world but rather to expose you to learning to think the Knockout way:
self.tax = ko.observable(.05); self.taxed = ko.computed(function(){ return +(self.subTotal()*self.tax()).toFixed(2); }); self.taxRate = ko.computed({ read: function(){ return self.tax()*100 + '%'; }, write: function(value){ value = parseFloat(value.replace(/[^\.\d]/g, "")); self.tax(+value/100); } });
If we were coding the page with standard JavaScript or even with jQuery to get the amount of functionality we have achieved, it would take many multiples of the code we have on the page here. This approach is far more elegant and much smaller.
Notice how our taxRate
is done just a little differently. Calculation functions in Knockout can read and write. You can also call other external resources because it is standard script code. Note that in our scripted code we have to place the values in parentheses to do the assignments as we did to the tax value.
You should also notice that we are converting the value in and out of decimal format and adding a percentage to the displayed value of the taxes in the input box. It also smartly strips the percent mark off if you enter it when updating the tax rate.
Tip
Regular expressions like the one used in our write
method are a way to supercharge your apps. Make it a point to learn at least basic regular expressions. If you do not know how to do the advanced stuff, you can often find what you need either in a Google search, or some "happy guru" will volunteer to assist you with a winning answer in some online forum.
Refresh the page with the updates, and you will see that even the actual tax amount has been calculated in the Tally column:
Final total
Here we have the final segment of the View code for our calculation example:
<tr> <td style="width:20%;"> </td> <td style="width:40%;"> </td> <td> <em>Total</em> </td> <td> <span data-bind="text:total"></span> </td> </tr>
The last piece of script code should be added to the ViewModel. There is nothing fancy about this section of code other than its ability to complete our functionality. We add in the value of the taxed item here, and again, we wrap the numbers with a parentheses and use the toFixed
function to set the answer to two decimal places.
self.total = ko.computed(function(){ var rslt = (+self.subTotal() + self.taxed()).toFixed(2); return rslt; });
Now we can run the code and play with the entry boxes to see that everything is working as expected. For new developers, it may not surprise you to see how little code it takes to make a page like this work. It used to take so much code that hardly anyone anywhere would take the time to attempt building tools like this. It was compounded because things worked differently from one browser to the next. While this is still true, libraries like Knockout remove many of those pains and let us concentrate on the results instead of the platforms.
Running the code now should give us results similar to this screen capture: