{"id":404,"date":"2011-10-30T06:44:33","date_gmt":"2011-10-30T13:44:33","guid":{"rendered":"http:\/\/blog.monnet-usa.com\/?p=404"},"modified":"2011-12-03T08:07:37","modified_gmt":"2011-12-03T15:07:37","slug":"creating-rich-interactive-web-apps-with-knockout-js-part-3","status":"publish","type":"post","link":"https:\/\/blog.monnet-usa.com\/?p=404","title":{"rendered":"Creating Rich Interactive Web Apps With KnockOut.js &#8211; Part 3"},"content":{"rendered":"<style>\n\t\t\t\t\t\th3 {\ttext-decoration: underline; }<\/p>\n<p>\t\t\t\t\t\th5 {margin-left:20px !important;}<\/p>\n<p>\t\t\t\t\t\t.details_table \n\t\t\t\t\t\t{border: 1px solid #f0f0f0;\n\t\t\t\t\t\tborder-spacing: 1px;\n\t\t\t\t\t\tbackground-color:white;\n\t\t\t\t\t\tmargin-left: 40px;\n\t\t\t\t\t\t}<\/p>\n<p>\t\t\t\t\t\t.details_table tr\n\t\t\t\t\t\t{\n\t\t\t\t\t\tvertical-align: top;\n\t\t\t\t\t\tborder-bottom: 1px solid LightGoldenRodYellow;\n\t\t\t\t\t\t}<\/p>\n<p>\t\t\t\t\t\t.details_table th\n\t\t\t\t\t\t{\n\t\t\t\t\t\tbackground-color:lightgray;\n\t\t\t\t\t\t}<\/p>\n<p>\t\t\t\t\t\t.details_table td\n\t\t\t\t\t\t{\n\t\t\t\t\t\tborder-bottom: 1px solid LightGoldenRodYellow;\n\t\t\t\t\t\t}<\/p>\n<p>\t\t\t\t\t\t.download_panel\n\t\t\t\t\t\t{\n\t\t\t\t\t\tbackground-color: #EDD6AD; \n\t\t\t\t\t\tcolor: #555555; \n\t\t\t\t\t\tfont-weight:bold;\n\t\t\t\t\t\ttext-align:center;\n\t\t\t\t\t\tmargin:0 50px 0 50px;\n\t\t\t\t\t\tpadding:6px;\n\t\t\t\t\t\t}<\/p>\n<\/style>\n<h3>Intro<\/h3>\n<p>In <a href=\"https:\/\/blog.monnet-usa.com\/?p=354\" target=\"_blank\">Part 1<\/a>, we introduced the key concepts and patterns including KnockoutJS. In <a href=\"https:\/\/blog.monnet-usa.com\/?p=368\" target=\"_blank\">Part 2<\/a>, we started to build the first increment of our Savings Goal Simulation rich web app. The objectives of this third tutorial are:<\/p>\n<ul>\n<li>Build up the rest of the application<\/li>\n<li>Gain more practice with View Models, View Mediators, and KnockoutJS<\/li>\n<li>Incorporate some basic elements of usability<\/li>\n<\/ul>\n<p>Here is the overall target structure of the application:<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-overview.png\" \/><\/p>\n<h3>Building Our App Step-By-Step<\/h3>\n<p>Here is what the app will look like at the end of this tutorial:<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part2-6.png\" \/><\/p>\n<p>Here is an outline of the approach:<\/p>\n<table class='details_table'>\n<tr>\n<th>#<\/th>\n<th>To Do<\/th>\n<th>Area<\/th>\n<\/tr>\n<tr>\n<td><a href='#todo1'>1<\/a><\/td>\n<td>Create the Consumption Scenarios View<\/td>\n<td>View<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo2'>2<\/a><\/td>\n<td>Create the Consumption Scenarios ViewModel<\/td>\n<td>View Model<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo3'>3<\/a><\/td>\n<td>Create the Consumption Scenarios View Mediator<\/td>\n<td>View Mediator<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo4'>4<\/a><\/td>\n<td>Link the Page and the View Mediator<\/td>\n<td>Main App<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo5'>5<\/a><\/td>\n<td>Create the Coffee Pricing ViewModel<\/td>\n<td>View Model<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo6'>6<\/a><\/td>\n<td>Create the Coffee Pricing ViewMediator<\/td>\n<td>View Mediator<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo7'>7<\/a><\/td>\n<td>Link the Page and the Coffee Pricing View Mediator<\/td>\n<td>Main App<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo8'>8<\/a><\/td>\n<td>Leveraging Independent View Models<\/td>\n<td>View Model<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo9'>9<\/a><\/td>\n<td>Create the Savings Forecast View<\/td>\n<td>View Mediator<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo10'>10<\/a><\/td>\n<td>Create the Savings Forecast ViewModel<\/td>\n<td>View Model<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo11'>11<\/a><\/td>\n<td>Create the Savings Forecast View Mediator<\/td>\n<td>View Mediator<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo12'>12<\/a><\/td>\n<td>Link the Page and the Savings Forecast View Mediator<\/td>\n<td>Main App<\/td>\n<\/tr>\n<\/table>\n<p><\/p>\n<p>\t\t\t\t\t<a name='todo1'><\/a><\/p>\n<h5>1. Create the Consumption Scenarios View<\/h5>\n<p>In our <b>index.html<\/b> page, let&#8217;s add a second section tag to represent our second panel \/ view: the <b>consumption-scenarios-view<\/b>.The view will let us enter current habits and see how modifying these habits can yield savings counting towards our saving goal.<\/p>\n<pre class=\"brush: html\">\r\n&lt;body&gt;\r\n\t&lt;!-- ... --&gt;\r\n\t&lt;section id='consumption-scenarios-view'&gt;\r\n\t&lt;\/section&gt;\r\n&lt;\/body&gt;\r\n<\/pre>\n<p>This is what the view will look like:<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part2-1.png\" \/><\/p>\n<p>Let&#8217;s add a table with 3 columns, one for the dimension to capture and affect (e.g. drink size), one for current habits, and one for our proposed consumption:<\/p>\n<pre class=\"brush: html\">\r\n&lt;body&gt;\r\n\t&lt;!-- ... --&gt;\r\n\t&lt;section id='consumption-scenarios-view'&gt;\r\n\t\t\t&lt;header&gt;Weekly Coffee Consumption&lt;\/header&gt;\r\n\t\t\t\r\n\t\t\t&lt;table&gt;\r\n\t\t\t\t&lt;thead&gt;\r\n\t\t\t\t\t&lt;tr&gt;\r\n\t\t\t\t\t\t&lt;th&gt;Coffee Options&lt;\/th&gt;\r\n\t\t\t\t\t\t&lt;th&gt;Current Habits&lt;\/th&gt;\r\n\t\t\t\t\t\t&lt;th&gt;Proposed Change&lt;\/th&gt;\r\n\t\t\t\t\t&lt;\/tr&gt;\r\n\t\t\t\t&lt;\/thead&gt;\r\n\t\t\t\t\r\n\t\t\t\t&lt;tbody&gt;\r\n\t\t\t\t\t&lt;tr&gt;\t\t\t\t\t\r\n\t\t\t\t\t\t&lt;td&gt;Type:&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t&lt;\/tr&gt;\r\n\t\t\t\t\t\r\n\t\t\t\t\t&lt;tr&gt;\r\n\t\t\t\t\t\t&lt;td&gt;Size:&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t&lt;\/tr&gt;\r\n\t\t\t\t\t\r\n\t\t\t\t\t&lt;tr&gt;\r\n\t\t\t\t\t\t&lt;td&gt;Frequency:&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t&lt;\/tr&gt;\r\n\t\t\t\t\t\r\n\t\t\t\t\t&lt;tr&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;label for=\"drinks-per-day\"&gt;Drinks per Day:&lt;\/label&gt;&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t&lt;\/tr&gt;\r\n\r\n\t\t\t\t\t&lt;tr&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;label for=\"cost-per-week\"&gt;Cost Per Week:&lt;\/label&gt;&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t\t&lt;td&gt;&lt;\/td&gt;\r\n\t\t\t\t\t&lt;\/tr&gt;\r\n\t\t\t\t&lt;\/tbody&gt;\r\n\t\t\t&lt;\/table&gt;\r\n\t&lt;\/section&gt;\r\n&lt;\/body&gt;\r\n<\/pre>\n<p>Let&#8217;s fill the [Drink] Type row, consisting of sets of radio buttons for Regular, Latte, and Espresso, in both Current and Proposed columns:<\/p>\n<pre class=\"brush: html\">\r\n&lt;tbody&gt;\r\n\t&lt;tr&gt;\t\t\t\t\t\r\n\t\t&lt;td&gt;Type:&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t&lt;div id=\"current-drink-type\"&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"current-drink-regular\"  name=\"CurrentDrinkType\" value=\"Regular\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"current-drink-regular\"&gt;Regular&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"current-drink-latte\"  name=\"CurrentDrinkType\" value=\"Latte\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"current-drink-latte\"&gt;Latte&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"current-drink-espresso\"  name=\"CurrentDrinkType\" value=\"Espresso\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"current-drink-espresso\"&gt;Espresso&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t&lt;\/div&gt;\r\n\t\t&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t&lt;div id=\"proposed-drink-type\"&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"proposed-drink-regular\"  name=\"ProposedDrinkType\" value=\"Regular\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"proposed-drink-regular\"&gt;Regular&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"proposed-drink-latte\"  name=\"ProposedDrinkType\" value=\"Latte\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"proposed-drink-latte\"&gt;Latte&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"proposed-drink-espresso\"  name=\"ProposedDrinkType\" value=\"Espresso\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"proposed-drink-espresso\"&gt;Espresso&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t&lt;\/div&gt;\r\n\t\t&lt;\/td&gt;\r\n\t&lt;\/tr&gt;\r\n\t&lt;!-- ... --&gt;\r\n&lt;\/tbody&gt;\r\n<\/pre>\n<p>Let&#8217;s fill the [Drink] Size row, consisting of sets of radio buttons for Tall, Grande, and Venti, in both Current and Proposed columns:<\/p>\n<pre class=\"brush: html\">\r\n&lt;tbody&gt;\r\n\t&lt;!-- ... --&gt;\r\n\t&lt;tr&gt;\t\t\t\t\t\r\n\t\t&lt;td&gt;Size:&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t\t&lt;div id=\"current-drink-size\"&gt;\r\n\t\t\t\t\t&lt;input type=\"radio\" id=\"current-size-tall\" \t\tname=\"CurrentDrinkSize\" value=\"Tall\" \/&gt;\r\n\t\t\t\t\t\t&lt;label for=\"current-size-tall\"&gt;Tall&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t\t&lt;input type=\"radio\" id=\"current-size-grande\" \tname=\"CurrentDrinkSize\" value=\"Grande\" \/&gt;\r\n\t\t\t\t\t\t&lt;label for=\"current-size-grande\"&gt;Grande&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t\t&lt;input type=\"radio\" id=\"current-size-venti\" \tname=\"CurrentDrinkSize\" value=\"Venti\" \/&gt;\r\n\t\t\t\t\t\t&lt;label for=\"current-size-venti\"&gt;Venti&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;\/div&gt;\t\t\t\t\t\t\r\n\t\t&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t\t&lt;div id=\"proposed-drink-size\"&gt;\r\n\t\t\t\t\t&lt;input type=\"radio\" id=\"proposed-size-tall\" \t\tname=\"ProposedDrinkSize\" value=\"Tall\" \/&gt;\r\n\t\t\t\t\t\t&lt;label for=\"proposed-size-tall\"&gt;Tall&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t\t&lt;input type=\"radio\" id=\"proposed-size-grande\" \tname=\"ProposedDrinkSize\" value=\"Grande\" \/&gt;\r\n\t\t\t\t\t\t&lt;label for=\"proposed-size-grande\"&gt;Grande&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t\t&lt;input type=\"radio\" id=\"proposed-size-venti\" \tname=\"ProposedDrinkSize\" value=\"Venti\" \/&gt;\r\n\t\t\t\t\t\t&lt;label for=\"proposed-size-venti\"&gt;Venti&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;\/div&gt;\t\t\t\t\t\t\r\n\t\t&lt;\/td&gt;\r\n\t&lt;\/tr&gt;\r\n\t&lt;!-- ... --&gt;\r\n&lt;\/tbody&gt;\r\n<\/pre>\n<p>Let&#8217;s fill the [Drink] Frequency row, consisting of sets of radio buttons for Everyday, Monday-through-Friday, and Other (i.e. N days per week), in both Current and Proposed columns:<\/p>\n<pre class=\"brush: html\">\r\n&lt;tbody&gt;\r\n\t&lt;!-- ... --&gt;\r\n\t&lt;tr&gt;\t\t\t\t\t\r\n\t\t&lt;td&gt;Frequency:&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t&lt;div id=\"current-drink-frequency\"&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"current-frequency-everyday\"\tname=\"CurrentDrinkFrequency\" value=\"Everyday\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"current-frequency-everyday\"&gt;Everyday&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"current-frequency-workdays\" name=\"CurrentDrinkFrequency\" value=\"WorkDays\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"current-frequency-workdays\"&gt;Mon-Fri&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"current-frequency-other\" \t\tname=\"CurrentDrinkFrequency\" value=\"Other\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"current-frequency-other\"&gt;Other:&lt;\/label&gt;\r\n\t\t\t\t\t&lt;input type=\"text\" id=\"current-custom-frequency\" name=\"CurrentCustomFrequency\" \/&gt;&lt;br\/&gt;\r\n\t\t\t&lt;\/div&gt;\t\t\t\t\t\t\r\n\t\t&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t&lt;div id=\"proposed-drink-frequency\"&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"proposed-frequency-everyday\"\tname=\"ProposedDrinkFrequency\" value=\"Everyday\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"proposed-frequency-everyday\"&gt;Everyday&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"proposed-frequency-workdays\" name=\"ProposedDrinkFrequency\" value=\"WorkDays\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"proposed-frequency-workdays\"&gt;Mon-Fri&lt;\/label&gt;&lt;br\/&gt;\r\n\t\t\t\t&lt;input type=\"radio\" id=\"proposed-frequency-other\" \t\tname=\"ProposedDrinkFrequency\" value=\"Other\" \/&gt;\r\n\t\t\t\t\t&lt;label for=\"proposed-frequency-other\"&gt;Other:&lt;\/label&gt;\r\n\t\t\t\t\t&lt;input type=\"text\" id=\"proposed-custom-frequency\" name=\"ProposedCustomFrequency\" \/&gt;&lt;br\/&gt;\r\n\t\t\t&lt;\/div&gt;\t\t\t\t\t\t\r\n\t\t&lt;\/td&gt;\r\n\t&lt;\/tr&gt;\r\n\t&lt;!-- ... --&gt;\r\n&lt;\/tbody&gt;\r\n<\/pre>\n<p>Let&#8217;s fill the Drinks Per Day row consisting of an simple input field, in both Current and Proposed columns:<\/p>\n<pre class=\"brush: html\">\r\n&lt;tbody&gt;\r\n\t&lt;!-- ... --&gt;\r\n\t&lt;tr&gt;\t\t\t\t\t\r\n\t\t&lt;td&gt;&lt;label for=\"drinks-per-day\"&gt;Drinks per Day:&lt;\/label&gt;&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t\t&lt;input type=\"text\" id=\"current-drinks-per-day\" name=\"CurrentDrinksPerDay\" \/&gt;\r\n\t\t&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t\t&lt;input type=\"text\" id=\"proposed-drinks-per-day\" name=\"ProposedDrinksPerDay\" \/&gt;\r\n\t\t&lt;\/td&gt;\r\n\t&lt;\/tr&gt;\r\n\t&lt;!-- ... --&gt;\r\n&lt;\/tbody&gt;\r\n<\/pre>\n<p>Let&#8217;s fill the Cost Per Week row consisting of &lt;div&#038;gt: results elements, in both Current and Proposed columns:<\/p>\n<pre class=\"brush: html\">\r\n&lt;tbody&gt;\r\n\t&lt;!-- ... --&gt;\r\n\t&lt;tr&gt;\t\t\t\t\t\r\n\t\t&lt;td&gt;&lt;label for=\"cost-per-week\"&gt;Cost Per Week:&lt;\/label&gt;&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t\t&lt;div id=\"current-cost-per-week\"&gt;&lt;\/div&gt;\r\n\t\t&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t\t&lt;div id=\"proposed-cost-per-week\"&gt;&lt;\/div&gt;\r\n\t\t&lt;\/td&gt;\r\n\t&lt;\/tr&gt;\r\n\t&lt;!-- ... --&gt;\r\n&lt;\/tbody&gt;\r\n<\/pre>\n<p>Let&#8217;s fill the Savings Per Week row consisting of &lt;div&#038;gt: results elements, in the Proposed column:<\/p>\n<pre class=\"brush: html\">\r\n&lt;tbody&gt;\r\n\t&lt;!-- ... --&gt;\r\n\t&lt;tr&gt;\t\t\t\t\t\r\n\t\t&lt;td&gt;&lt;label for=\"savings-per-week\"&gt;Savings Per Week:&lt;\/label&gt;&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t\t&nsbsp;\r\n\t\t&lt;\/td&gt;\r\n\t\t&lt;td&gt;\r\n\t\t\t\t&lt;div id=\"savings-per-week\"&gt;&lt;\/div&gt;\r\n\t\t&lt;\/td&gt;\r\n\t&lt;\/tr&gt;\r\n\t&lt;!-- ... --&gt;\r\n&lt;\/tbody&gt;\r\n<\/pre>\n<p>\t\t\t\t\t<a name='todo2'><\/a><\/p>\n<h5>2. Create the Consumption Scenarios ViewModel<\/h5>\n<p>In the <b>ViewModels<\/b> folder under the <b>scripts<\/b> folder, create a file named: <b>sgs.model.consumption-scenarios.js<\/b>.And let&#8217;s add a statement to load our new module in in our <b>LAB.js<\/b> section:\n\t\t\t\t\t<\/p>\n<pre class=\"brush: javascript\">\r\n$LAB\r\n\t\/\/ ...\r\n\t.script(\"scripts\/viewmodel\/sgs.model.savings-goal.js\").wait()\r\n\t.script(\"scripts\/viewmodel\/sgs.model.savings-goal.js\").wait()\r\n\t.script(\"scripts\/viewmediator\/sgs.mediator.savings-goal.js\").wait()\r\n\t.script(\"scripts\/viewmodel\/sgs.model.consumption-scenarios.js\").wait()\r\n\t\/\/ ...\r\n<\/pre>\n<p>Let&#8217;s add the code snippet to lazy-create the namespace:<\/p>\n<pre class=\"brush: javascript\">\r\n\/\/ Lazy initialize our namespace context: sgs.model.consumptionscenarios\r\nif (typeof(sgs) == 'undefined') sgs = { }\r\nif (typeof(sgs.model) == 'undefined') sgs.model = { }\r\nif (typeof(sgs.model.consumptionscenarios) == 'undefined') sgs.model.consumptionscenarios = { }\r\n<\/pre>\n<p>Let&#8217;s create our <b>initializeViewModel<\/b> function to initialize the view model and its three value models:<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-todo2a.png\" \/><\/p>\n<ul>\n<li><b>currentConsumption<\/b> will track our current coffee drinking habits<\/li>\n<li><b>proposedConsumption<\/b> will allow us to experiment to see how much we could save<\/li>\n<li><b>savingsPerWeek<\/b> will show us the difference between proposed and current costs and will be implemented as a dependent observable function<\/li>\n<li><b>savingsPerMonth<\/b>(self explanatory)<\/li>\n<\/ul>\n<p>For now let&#8217;s just assign currentConsumption and proposedConsumption to a <b>ko.observable(null)<\/b> for now &#8211; we&#8217;ll change that <a href=\"#todo2c\">later<\/a> (once we create the needed sub-view model).<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.consumptionscenarios.initializeViewModel = function (pageSettings) {\r\n\t\/\/ We can use properties of the pageSettings as default values for any of our ValueModels\r\n\t\/\/ If pageSettings are not provided we'll initialize an empty object\r\n\tif (typeof(pageSettings) == 'undefined') var pageSettings = { }\r\n\t\r\n\tvar viewModel = {\r\n\t\tcurrentConsumption: \tko.observable(null),\r\n\t\tproposedConsumption: \tko.observable(null)\r\n\t};\r\n\t\r\n\t\/\/ Stub implementation to be fully implemented later on\r\n\tviewModel.savingsPerWeek = ko.dependentObservable(function() {\r\n\t\tvar result = 0;\r\n\t\treturn result;\r\n\t}, viewModel);\r\n\r\n\tviewModel.savingsPerMonth = ko.dependentObservable(function() {\r\n\t\tvar perMonth = this.savingsPerWeek() * 4;\r\n\t\tvar result = Math.round(perMonth * 100) \/ 100;\r\n\t\treturn result;\r\n\t}, viewModel);\r\n\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>We need a way to model our coffee consumption.<br \/>\n\t\t\t\t\tSo we&#8217;ll create a new module file named <b>sgs.model.coffee-consumption.js<\/b> under <b>scripts\/viewmodel<\/b>.<br \/>\n\t\t\t\t\tAnd let&#8217;s add a statement to load our new module in in our <b>LAB.js<\/b> section (right before consumption-scenarios):\n\t\t\t\t\t<\/p>\n<pre class=\"brush: javascript\">\r\n$LAB\r\n\t\/\/ ...\r\n\t.script(\"scripts\/viewmodel\/sgs.model.coffee-consumption.js\").wait()\r\n\t.script(\"scripts\/viewmodel\/sgs.model.consumption-scenarios.js\").wait()\r\n\t\/\/ ...\r\n<\/pre>\n<p>Let&#8217;s add the code snippet to lazy-create the namespace:<\/p>\n<pre class=\"brush: javascript\">\r\n\/\/ Lazy initialize our namespace context: sgs.model.coffeeconsumption\r\nif (typeof(sgs) == 'undefined') sgs = { }\r\nif (typeof(sgs.model) == 'undefined') sgs.model = { }\r\nif (typeof(sgs.model.coffeeconsumption) == 'undefined') sgs.model.coffeeconsumption = { }\r\n\r\nif (typeof(console) != 'undefined' && console) console.info(\"sgs.model.coffeeconsumption loading!\");\r\n<\/pre>\n<p>Let&#8217;s create our <b>initializeViewModel<\/b> function to initialize the view model based on the pageSettins as well as a scenario name. We&#8217;ll have the following value models:<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-todo2b.png\" \/><\/p>\n<ul>\n<li>scenarioName &#8211; will be either Current or Proposed<\/li>\n<li>drinkType<\/li>\n<li>drinkSize<\/li>\n<li>drinkFrequency<\/li>\n<li>customFrequency<\/li>\n<li>drinksPerDay<\/li>\n<\/ul>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeeconsumption.initializeViewModel = function (pageSettings, scenarioName) {\r\n\t\/\/ We can use properties of the pageSettings as default values for any of our ValueModels\r\n\t\/\/ If pageSettings are not provided we'll initialize an empty object\r\n\tif (typeof(pageSettings) == 'undefined') var pageSettings = { }\r\n\t\r\n\tvar viewModel = {\r\n\t\tscenarioName: \t\tko.observable(scenarioName), \r\n\t\tdrinkType: \t\t\tko.observable(\"Regular\"), \r\n\t\tdrinkSize: \t\t\tko.observable(\"Tall\"), \r\n\t\tdrinkFrequency: \tko.observable(\"Everyday\"), \r\n\t\tcustomFrequency:\tko.observable(1),\r\n\t\tdrinksPerDay: \t\tko.observable(1)\r\n\t}\r\n\t\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>Let&#8217;s add two dependent observable functions named <b>drinkHasStandardSize<\/b> (will allow us to later restrict the drink size options based on the drink type) and <b>drinkDaysPerWeek<\/b> (will allow to calculate the cost per week):<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeeconsumption.initializeViewModel = function (pageSettings, scenarioName) {\r\n\t\/\/ ...\r\n\t\r\n\tvar viewModel = {\r\n\t\t\/\/ ...\r\n\t}\r\n\t\r\n\tviewModel.drinkHasStandardSize = ko.dependentObservable(function() {\r\n\t\treturn this.drinkType() != 'Espresso';\r\n\t}, viewModel);\r\n\t\r\n\tviewModel.drinkDaysPerWeek = ko.dependentObservable(function() {\r\n\t\tvar count = 0;\r\n\t\t\r\n\t\tswitch(this.drinkFrequency()) {\r\n\t\t\tcase \"Everyday\":\r\n\t\t\t\tcount = 7;\r\n\t\t\t\tbreak;\r\n\t\t\t\t\r\n\t\t\tcase \"WorkDays\":\r\n\t\t\t\tcount = 5;\r\n\t\t\t\tbreak;\r\n\t\t\t\t\r\n\t\t\tcase \"Other\":\r\n\t\t\t\tcount = this.customFrequency();\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t\t\r\n\t\treturn count;\r\n\t}, viewModel);\r\n\t\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>I encourage you to test as you develop along , whether or not you are using a TDD or BDD approach (and framework like <a href=\"#js-jasmie\">Jasmine<\/a>). So refresh your browser and in the Firebug (or Webkit browser) console, instantiate a <b>sgs.model.coffeeconsumption<\/b> view model and invoke its <b>drinkDaysPerWeek<\/b> dependent observable like so:<\/p>\n<pre class=\"brush: javascript\">\r\nvar cs = sgs.model.coffeeconsumption.initializeViewModel()\r\ncs.drinkDaysPerWeek()\r\n<\/pre>\n<p>You can also step through the code in the debugger if you really want to check out what happens.<\/p>\n<p>Now let&#8217;s move on and add a stub for a third dependent observable function named: <b>costPerWeek<\/b><\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeeconsumption.initializeViewModel = function (pageSettings, scenarioName) {\r\n\t\/\/ ...\r\n\t\r\n\tvar viewModel = {\r\n\t\t\/\/ ...\r\n\t}\r\n\t\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Stub implementation to be fully implemented later on\r\n\tviewModel.costPerWeek = ko.dependentObservable(function() {\r\n\t\tvar result = 0;\r\n\t\treturn result;\r\n\t}, viewModel);\r\n\t\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>Later on we&#8217;ll come back to this view model and complete the stub implementation of the <b>costPerWeek<\/b> dependentObservable function to calculate the actual cost of the consumption model (based on pricing).<\/p>\n<p>\t\t\t\t\t<a name=\"todo2c\"><\/a><\/p>\n<p>Now let&#8217;s go back to the <b>initializeViewModel<\/b> of our <b>sgs.model.consumptionscenarios<\/b> module and update the value model definitions to use custom instantiations of the sgs.model.coffeeconsumption view model so that the relationship between the two view models looks like this:<\/p>\n<p>\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-todo2c.png\" \/><\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.consumptionscenarios.initializeViewModel = function (pageSettings) {\r\n\t\/\/ We can use properties of the pageSettings as default values for any of our ValueModels\r\n\t\/\/ If pageSettings are not provided we'll initialize an empty object\r\n\tif (typeof(pageSettings) == 'undefined') var pageSettings = { }\r\n\t\r\n\tvar current \t= sgs.model.coffeeconsumption.initializeViewModel (pageSettings, \"Current\");\r\n\tvar proposed \t= sgs.model.coffeeconsumption.initializeViewModel (pageSettings, \"Proposed\");\r\n\t\r\n\tvar viewModel = {\r\n\t\tcurrentConsumption: \tko.observable(current),\r\n\t\tproposedConsumption: \tko.observable(proposed)\r\n\t}\r\n\t\t\r\n\t\/\/ Stub implementation to be fully implemented later on\r\n\tviewModel.savingsPerWeek = ko.dependentObservable(function() {\r\n\t\tvar result = 0;\r\n\t\treturn result;\r\n\t}, viewModel);\r\n\r\n\tviewModel.savingsPerMonth = ko.dependentObservable(function() {\r\n\t\tvar perMonth = this.savingsPerWeek() * 4;\r\n\t\tvar result = Math.round(perMonth * 100) \/ 100;\r\n\t\treturn result;\r\n\t}, viewModel);\r\n\t\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>So now you have implemented an example of hierarchical view model! Again I invite you to test it using the browser Javascript console and debugger.<\/p>\n<p>\t\t\t\t\t<a name='todo3'><\/a><\/p>\n<h5>3. Create the Consumption Scenarios View Mediator<\/h5>\n<p>Let&#8217;s tackle the view mediator and connect the parts!<br \/>In the <b>ViewMediator<\/b> folder under the <b>scripts<\/b> folder, create a file named: <b>sgs.mediator.consumption-scenarios.js<\/b>.<br \/>\n\t\t\t\t\tThis is the module where we&#8217;ll place our data-binding logic as well as any code related to mediating access between<br \/>\n\t\t\t\t\tthe page, the consumption scenario view, and its view model.<br \/>\n\t\t\t\t\tAnd let&#8217;s add a statement to load our new module in in our <b>LAB.js<\/b> section:\n\t\t\t\t\t<\/p>\n<pre class=\"brush: javascript\">\r\n$LAB\r\n\t\/\/ ...\r\n\t.script(\"scripts\/viewmodel\/sgs.model.consumption-scenarios.js\").wait()\r\n\t.script(\"scripts\/viewmediator\/sgs.mediator.consumption-scenarios.js\").wait()\r\n\t\/\/ ...\r\n<\/pre>\n<p>Let&#8217;s define our namespace:<\/p>\n<pre class=\"brush: javascript\">\r\n\/\/ Lazy initialize our namespace context: sgs.mediator.consumptionscenarios\r\nif (typeof(sgs) == 'undefined') sgs = { }\r\nif (typeof(sgs.mediator) == 'undefined') sgs.mediator = { }\r\nif (typeof(sgs.mediator.consumptionscenarios) == 'undefined') sgs.mediator.consumptionscenarios = { }\r\n\r\nif (typeof(console) != 'undefined' && console) console.info(\"sgs.mediator.consumptionscenarios loading!\");\r\n<\/pre>\n<p>Let&#8217;s create our <b>createViewMediator<\/b> function (per our convention) which we will eventually invoke from the <b>InitializeApplication<\/b> method in <b>application.js<\/b>. The createViewMediator function will have 4 responsibilities:<\/p>\n<ol>\n<li>Instantiate a view model for the consumption scenarios view<\/li>\n<li>Declare the data-binding between the HTML elements of the view and their corresponding value models in the view model<\/li>\n<li>Ask KnockoutJS to make the bindings effective (they will be live right after that)<\/li>\n<li>Save off the view model so we can access it from other parts of the app<\/li>\n<\/ol>\n<p>So let&#8217;s implement the first responsibility to instantiate a view model for the consumption scenario view (using the <b>initializeViewModel<\/b> function we just created in the consumption scenario view model module).<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.consumptionscenarios.createViewMediator = function (pageSettings) {\r\n\t\/\/ Create the view Consumption Scenarios view-specific view model\r\n\tvar viewModel = sgs.model.consumptionscenarios.initializeViewModel(pageSettings);\r\n}\r\n<\/pre>\n<p>Now let&#8217;s declare the data-bindings we need. For <b>input<\/b> elements we used the <b>value<\/b> attribute in our <b>data-bind<\/b> declaration, but since we have some radio buttons, it is the <b>checked<\/b> attribute of <b>each radio button<\/b> we need to target. Note that our binding will have a level of indirection via the <b>currentConsumption<\/b> value model, which is itself a view model with value models like <b>drinkType<\/b>. Here is what a snippet looks like for the radio buttons located inside the <b>current-drink-type<\/b> div:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.consumptionscenarios.createViewMediator = function (pageSettings) {\r\n\t\/\/ Create the view Consumption Scenarios view-specific view model\r\n\tvar viewModel = sgs.model.consumptionscenarios\r\n\t\t.initializeViewModel(pageSettings);\r\n\t\r\n\t\/\/ Declare the HTML element-level data bindings for the Current Habits column\r\n\t$(\"#current-drink-type input[type=radio]\")\r\n\t\t.attr(\"data-bind\",\r\n\t\t\t\t\"checked: currentConsumption().drinkType\");\r\n\r\n<\/pre>\n<p>Let&#8217;s finish up the rest of the bindings:<\/p>\n<pre class=\"brush: javascript\">\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Declare the HTML element-level data bindings for the Current Habits column\r\n\t$(\"#current-drink-type  input[type=radio]\")\r\n\t\t.attr(\"data-bind\",\"checked: currentConsumption().drinkType\");\r\n\t$(\"#current-drink-size input[type=radio]\")\r\n\t\t.attr(\"data-bind\",\"checked: currentConsumption().drinkSize\");\r\n\t$(\"#current-drink-frequency input[type=radio]\")\r\n\t\t.attr(\"data-bind\",\"checked: currentConsumption().drinkFrequency\");\r\n\t$(\"#current-custom-frequency\")\t\t\t\t\t\t\r\n\t\t.attr(\"data-bind\",\"value: currentConsumption().customFrequency\");\r\n\t$(\"#current-drinks-per-day\")\r\n\t\t.attr(\"data-bind\",\"value: currentConsumption().drinksPerDay\");\r\n\t$(\"#current-cost-per-week\")\r\n\t\t.attr(\"data-bind\",\"text: currentConsumption().costPerWeek\");\r\n\t\r\n\t\/\/ Declare the HTML element-level data bindings for the Proposed Change column\r\n\t$(\"#proposed-drink-type input[type=radio]\")\r\n\t\t.attr(\"data-bind\",\"checked: proposedConsumption().drinkType\");\r\n\t$(\"#proposed-drink-size input[type=radio]\")\r\n\t\t.attr(\"data-bind\",\"checked: proposedConsumption().drinkSize\");\r\n\t$(\"#proposed-drink-frequency input[type=radio]\")\r\n\t\t.attr(\"data-bind\",\"checked: proposedConsumption().drinkFrequency\");\r\n\t$(\"#proposed-custom-frequency\")\t\t\t\t\t\t\t \r\n\t\t.attr(\"data-bind\",\"value: proposedConsumption().customFrequency\");\r\n\t$(\"#proposed-drinks-per-day\")\t\t\t\t\t\t\t\t \r\n\t\t.attr(\"data-bind\",\"value: proposedConsumption().drinksPerDay\");\r\n\t$(\"#proposed-cost-per-week\")\r\n\t\t.attr(\"data-bind\",\"text: proposedConsumption().costPerWeek\");\r\n\t$(\"#savings-per-week\")\r\n\t\t.attr(\"data-bind\",\"text: savingsPerWeek\");\r\n\r\n\t\/\/ ...\r\n<\/pre>\n<p>Let&#8217;s add a new kind of binding attribute: &#8220;<b>enable<\/b>&#8220;, which allow us to control whether or not the data-bound HTML element should be enabled. This is a very useful binding as it will simplify enabling\/disabling rules a lot. Here we&#8217;ll use it to control whether or not the drink size radio buttons should be enabled, since an espresso does not typically come in a Tall \/ Grande \/ Venti size! (Wow imagine the buzz on Venti!)<\/p>\n<p>\t\t\t\t\t<u>Reminder:<\/u> The syntax of data-bind in KnockoutJS is:\n\t\t\t\t\t<\/p>\n<pre class=\"brush: javascript\">\r\ndatabind=\"attribute1: valueModel1, attribute2: valueModel2, etc.\" \/\/ comma-separated list\r\n<\/pre>\n<p>This means that we will need to <b>comma-separate<\/b> and <b>concatenate<\/b> our new &#8220;<b>attribute: valueModel<\/b>&#8221; binding pair at the end of our existing declaration for the &#8220;checked&#8221; attribute like so:\n<\/p>\n<pre class=\"brush: javascript\">\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Declare the HTML element-level data bindings for the Current Habits column\r\n\t\/\/ ...\r\n\t$(\"#current-drink-size input[type=radio]\")\r\n\t\t.attr(\"data-bind\",\"checked: currentConsumption().drinkSize \" +\r\n\t\t\t\t\t\t\", enable: currentConsumption().drinkHasStandardSize\");\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Declare the HTML element-level data bindings for the Proposed Change column\r\n\t\/\/ ...\r\n\t$(\"#proposed-drink-size input[type=radio]\")\r\n\t\t.attr(\"data-bind\",\"checked: proposedConsumption().drinkSize\" +\r\n\t\t\t\t\t\t\", enable: proposedConsumption().drinkHasStandardSize\");\r\n\t\r\n\t\/\/ ...\r\n<\/pre>\n<p><u>Caution<\/u>: the name of the attribute is &#8220;<b>enable<\/b>&#8221; <i>(not &#8220;enabled&#8221; &#8211; I have been bit by that many times)<\/i>!<\/p>\n<p>Now we&#8217;ll ask KnockoutJS to &#8220;apply&#8221;, i.e. register \/ enable our bindings. <\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.consumptionscenarios.createViewMediator = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Ask KnockoutJS to data-bind the view model to the view\r\n\tvar viewNode = $('#consumption-scenarios-view')[0];\r\n\tko.applyBindings(viewModel, viewNode);\r\n}\r\n<\/pre>\n<p>Now let&#8217;s add the 2 functions to store retrieve our consumption scenarios view model:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.consumptionscenarios.getViewModel = function() {\r\n\treturn $(document).data(\"sgs.model.consumptionscenarios.viewmodel\");\r\n}\r\n\r\nsgs.mediator.consumptionscenarios.setViewModel = function(viewModel) {\r\n\t$(document).data(\"sgs.model.consumptionscenarios.viewmodel\", viewModel);\r\n}\r\n<\/pre>\n<p>Now we can call the <b>setViewModel<\/b> function from inside <b>createViewMediator<\/b> to save off our freshly-created view model.<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.consumptionscenarios.createViewMediator = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Save the view model\r\n\tsgs.mediator.consumptionscenarios.setViewModel(viewModel);\t\r\n\r\n\tif (typeof(console) != 'undefined' && console) console.info(\"sgs.mediator.coffeeconsumption ready!\");\r\n}\r\n<\/pre>\n<p>If you want to test the mediator, just refresh the browser, and invoke the <b>createViewMediator<\/b> in the browser Javascript console like so:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.consumptionscenarios.createViewMediator({})\r\n<\/pre>\n<p>You will then see the radio buttons get set to their default value. And clicking on the Espresso radio button should cause the Size radio buttons to become disabled.<\/p>\n<p>\t\t\t\t\t<a name='todo4'><\/a><\/p>\n<h5>4. Link the Page and the Consumptions Scenarios View Mediator<\/h5>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-todo4.png\" \/><\/p>\n<p>To instantiate our new mediator, we just need to add a call to the overall page <b>InitializeApplication<\/b> function (located in our <b>scripts\/application.js<\/b>) to our consumption scenario <b>createViewMediator<\/b> function like so:<\/p>\n<pre class=\"brush: javascript\">\r\nfunction InitializeApplication() {\r\n\tif (typeof(console) != 'undefined' && console) \r\n\t\tconsole.info(\"InitializeApplication starting ...\");\r\n\t\t\r\n\t\/\/ Initialize our page-wide settings\r\n\tvar pageSettings = { defaultSavingsGoal: 500 }\r\n\t\r\n\t\/\/ Create \/ launch our view mediator(s)\r\n\tsgs.mediator.savingsgoal.createViewMediator(pageSettings);\r\n\tsgs.mediator.consumptionscenarios.createViewMediator(pageSettings);\r\n\r\n\tif (typeof(console) != 'undefined' && console) \r\n\t\tconsole.info(\"InitializeApplication done ...\");\t\r\n}\r\n<\/pre>\n<p>So now you should be able to load your index.html page in Firefox with the Firebug console on and watch the debug statement and finally our new consumption scenarios view displays with our default values:\n\t\t\t\t\t<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part2-2.png\" \/><\/p>\n<p>\t\t\t\t\t<a name='todo5'><\/a><\/p>\n<h5>5. Create the Coffee Pricing ViewModel<\/h5>\n<p>Now we need a pricing engine! It needs to ake into account : the drink type and drink size. And we can apply frequency and drinks per day to calculate the cost. To keep logic encapsulated, let&#8217;s create a new module file in the <b>scripts\/viewmodel<\/b> folder named <b>sgs.model.coffee-pricing.js<\/b>. The new module will be responsible for:\n\t\t\t\t\t<\/p>\n<ol>\n<li>having a default example pricing list for the various coffee options<\/li>\n<li>providing its own view model with a <b>pricing<\/b> value model<\/li>\n<li>loading \/ saving its content to the browser local storage (using <a href=\"#js-jstorage\">jStorage<\/a>, a cross-browser HTML 5 local storage abstraction library)<\/li>\n<\/ol>\n<p>Let&#8217;s add the code snippet to lazy-create the namespace:<\/p>\n<pre class=\"brush: javascript\">\r\n\/\/ Lazy initialize our namespace context: sgs.model.coffeeconsumption\r\nif (typeof(sgs) == 'undefined') sgs = { }\r\nif (typeof(sgs.model) == 'undefined') sgs.model = { }\r\nif (typeof(sgs.model.coffeepricing) == 'undefined') sgs.model.coffeepricing = { }\r\n\r\nif (typeof(console) != 'undefined' && console) console.info(\"sgs.model.coffeepricing loading!\");\r\n<\/pre>\n<p>Update the LAB.js section of the index.html page so we can load the new module (before sgs.model.coffee-consumption):<\/p>\n<pre class=\"brush: html\">\r\n\t$LAB\r\n\t\t\/\/ ...\r\n\t\t.script(\"scripts\/viewmodel\/sgs.model.coffee-pricing.js\").wait()\r\n\t\t.script(\"scripts\/viewmodel\/sgs.model.coffee-consumption.js\").wait()\r\n\t\t\/\/ ...\r\n<\/pre>\n<p>Now we&#8217;ll create an <b>examplePricing<\/b> function which will return a hash of key-value pairs where the key will be a composite of the coffee type and size, and the value an amount.<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeepricing.examplePricing = function() {\r\n\tvar priceList = {\r\n\t\t\"Regular-Tall\": 1.40,\r\n\t\t\"Regular-Grande\": 1.60,\r\n\t\t\"Regular-Venti\": 1.70,\r\n\r\n\t\t\"Latte-Tall\": 2.55,\r\n\t\t\"Latte-Grande\": 3.10,\r\n\t\t\"Latte-Venti\": 3.40,\r\n\r\n\t\t\"Espresso\": 1.75,\r\n\t\t\"EspressoShot\": 0.25\r\n\t}\r\n\t\r\n\treturn priceList;\r\n}\r\n<\/pre>\n<p>Now let&#8217;s define our initializeViewModel function with a place holder for the price list:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeepricing.initializeViewModel = function (pageSettings) {\r\n\t\/\/ We can use properties of the pageSettings as default values for any of our ValueModels\r\n\t\/\/ If pageSettings are not provided we'll initialize an empty object\r\n\tif (typeof(pageSettings) == 'undefined') var pageSettings = { }\r\n\t\r\n\tvar priceList = { }\r\n\tvar viewModel = {\r\n\t\tpricing: \t\tko.observable(priceList)\r\n\t}\r\n\t\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>Ok, now we want to check if we have a price list in local storage and lazy-initialize it otherwise. We&#8217;ll encapsulate the logic in a function named: <b>getPriceList<\/b>. We&#8217;ll try to retrieve the &#8220;<b>coffee-price-list<\/b>&#8221; using <b>$.jStorage.get(key)<\/b> API:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeepricing.getPriceList = function () {\r\n\t\/\/ Check if we have ever stored the price list locally\r\n\tvar priceList = $.jStorage.get(\"coffee-price-list\");\r\n\t\r\n\tif (priceList == null) {\r\n\t\t\/\/ If not create an example\r\n\t\tpriceList = sgs.model.coffeepricing.examplePricing();\r\n\t\t\r\n\t\t\/\/ Save it off\r\n\t\t$.jStorage.set(\"coffee-price-list\", priceList);\r\n\t}\r\n\t\r\n\treturn priceList;\r\n}\r\n<\/pre>\n<p>So now we can edit our in <b>initializeViewModel<\/b> function and plug in the <b>getPriceList<\/b> for the initialization of the <b>pricing<\/b> value model:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeepricing.initializeViewModel = function (pageSettings) {\r\n\t\/\/ We can use properties of the pageSettings as default values for any of our ValueModels\r\n\t\/\/ If pageSettings are not provided we'll initialize an empty object\r\n\tif (typeof(pageSettings) == 'undefined') var pageSettings = { }\r\n\t\r\n\t\/\/ Lazy-initialize the price list\r\n\tvar priceList = sgs.model.coffeepricing.getPriceList();\r\n\t\r\n\tvar viewModel = {\r\n\t\tpricing: \t\tko.observable(priceList)\r\n\t}\r\n\t\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>To encapsulate pricing data requests, let&#8217;s add a &#8220;normal&#8221; function (i.e. not an dependent observable) named <b>getCoffeeBeveragePrice<\/b> to our view model:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeepricing.initializeViewModel = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\r\n\tviewModel.getCoffeeBeveragePrice = function (drinkType, drinkSize) {\r\n\t\tvar key = drinkType;\r\n\t\t\r\n\t\tif (drinkType != 'Espresso' && drinkSize) {\r\n\t\t\tkey += '-' + drinkSize;\r\n\t\t}\r\n\t\t\r\n\t\tvar price = viewModel.pricing()[key];\r\n\t\treturn price;\r\n\t}\r\n\t\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>Once reloading the index.html page, you should be able to experiment with jStorage. To see all keys stored with jStorage (should be an empty array initially):<\/p>\n<pre class=\"brush: javascript\">\r\n$.jStorage.index()\r\n<\/pre>\n<p>Now, let&#8217;s make a call to <b>sgs.model.coffeepricing.getPriceList()<\/b>. This will lazy-initialize the price list and store it. So when asking for the jStorage index a second time the console should display an array with our &#8220;<b>coffee-price-list<\/b>&#8221; key. And we can access the value for our key using:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeepricing.getPriceList()\r\n$.jStorage.index()\r\n$.jStorage.get(\"coffee-price-list\")\r\n<\/pre>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part2-3.png\" \/><\/p>\n<p>\t\t\t\t\t<a name='todo6'><\/a><\/p>\n<h5>6. Create the Coffee Pricing ViewMediator<\/h5>\n<p>In the <b>Viewmediator<\/b> folder under the <b>scripts<\/b> folder, create a file named: <b>sgs.mediator.coffee-pricing.js<\/b>. This is the module where we&#8217;ll place our data-binding logic as well as any code related to mediating access between the page, a future pricing editing view, and its view model.<br \/> And let&#8217;s add a statement to load our new module in in our <b>LAB.js<\/b> section:\n\t\t\t\t\t<\/p>\n<pre class=\"brush: javascript\">\r\n$LAB\r\n\t\/\/ ...\r\n\t.script(\"scripts\/viewmodel\/sgs.model.coffee-pricing.js\").wait()\r\n\t.script(\"scripts\/viewmediator\/sgs.mediator.coffee-pricing.js\").wait()\r\n\t\/\/ ...\r\n<\/pre>\n<p>Let&#8217;s define our namespace:<\/p>\n<pre class=\"brush: javascript\">\r\n\/\/ Lazy initialize our namespace context: sgs.mediator.coffeepricing\r\nif (typeof(sgs) == 'undefined') sgs = { }\r\nif (typeof(sgs.mediator) == 'undefined') sgs.mediator = { }\r\nif (typeof(sgs.mediator.coffeepricing) == 'undefined') sgs.mediator.coffeepricing = { }\r\n\r\nif (typeof(console) != 'undefined' && console) console.info(\"sgs.mediator.coffeepricing loading!\");\r\n<\/pre>\n<p>Let&#8217;s create our <b>createViewMediator<\/b> function (per our convention) which we will eventually invoke from the <b>InitializeApplication<\/b> method in <b>application.js<\/b>. The createViewMediator function will have 3 responsibilities:<\/p>\n<ol>\n<li>Instantiate a view model for the future pricing editor view<\/li>\n<li>Ask KnockoutJS to make the bindings effective (they will be live right after that)<\/li>\n<li>Save off the view model so we can access it from other parts of the app<\/li>\n<\/ol>\n<p>Note: this mediator will not need to setup any data-bindings with the view, since we are not planning on displaying the pricing. However, the <b>consumption scenario mediator<\/b> will collaborate with it in order to access its <b>pricing view model<\/b>.<\/p>\n<p>So let&#8217;s implement the first responsibility to instantiate a view model<br \/>\n\t\t\t\t\t(using the <b>initializeViewModel<\/b> function we just created in the coffee pricing view model module).<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.coffeepricing.createViewMediator = function (pageSettings) {\r\n\t\/\/ Create the view Pricing Editor view-specific view model\r\n\tvar viewModel = sgs.model.coffeepricing.initializeViewModel(pageSettings);\r\n}\r\n<\/pre>\n<p>Now let&#8217;s add the 2 functions to store retrieve our coffee pricing view model:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.coffeepricing.getViewModel = function() {\r\n\treturn $(document).data(\"sgs.model.coffeepricing.viewmodel\");\r\n}\r\n\r\nsgs.mediator.coffeepricing.setViewModel = function(viewModel) {\r\n\t$(document).data(\"sgs.model.coffeepricing.viewmodel\", viewModel);\r\n}\r\n<\/pre>\n<p>Now we can use the setViewModel function inside createViewMediator to save off our freshly-created view model.<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.coffeepricing.createViewMediator = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Save the view model\r\n\tsgs.mediator.coffeepricing.setViewModel(viewModel);\t\r\n\r\n\tif (typeof(console) != 'undefined' && console) console.info(\"sgs.mediator.coffeepricing ready!\");\r\n}\r\n<\/pre>\n<p>\t\t\t\t\t<a name='todo7'><\/a><\/p>\n<h5>7. Link the Page and the Coffee Pricing View Mediator<\/h5>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-todo7.png\" \/><\/p>\n<p>To instantiate our new mediator, we just need to add a call to the overall page <b>InitializeApplication<\/b> function (located in our <b>scripts\/application.js<\/b>) to our coffee pricing <b>createViewMediator<\/b> function like so:<\/p>\n<pre class=\"brush: javascript\">\r\nfunction InitializeApplication() {\r\n\tif (typeof(console) != 'undefined' && console) \r\n\t\tconsole.info(\"InitializeApplication starting ...\");\r\n\t\t\r\n\t\/\/ Initialize our page-wide settings\r\n\tvar pageSettings = { defaultSavingsGoal: 500 }\r\n\t\r\n\t\/\/ Create \/ launch our view mediator(s)\r\n\tsgs.mediator.savingsgoal.createViewMediator(pageSettings);\r\n\tsgs.mediator.coffeepricing.createViewMediator(pageSettings);\r\n\tsgs.mediator.consumptionscenarios.createViewMediator(pageSettings);\r\n\r\n\tif (typeof(console) != 'undefined' && console) \r\n\t\tconsole.info(\"InitializeApplication done ...\");\t\r\n}\r\n<\/pre>\n<p>\t\t\t\t\t<a name='todo8'><\/a><\/p>\n<h5>8. Leveraging Independent View Models<\/h5>\n<p>At this point we have created the consumption, scenarios, and pricing view models, but we have not yet &#8220;connected&#8221; all the parts to allow for the <b>costPerWeek<\/b> value model of <b>coffeeconsumption<\/b> to work. Before we proceed let&#8217;s review a few requirements and guiding principles:<\/p>\n<ul>\n<li>The consumptionscenarios view model knows about the <b>current<\/b> and <b>proposed<\/b> coffee consumptions<\/li>\n<li>The coffeeconsumption view model has the <b>costPerWeek<\/b> dependent observable function<\/li>\n<li>The coffeepricing view model has the <b>pricing<\/b><\/li>\n<li>The coffeepricing view mediator manages access to its view model<\/li>\n<li>We want to keep the view models from knowing about the mediators<\/li>\n<li>Over time, we may want to test different pricing models in each scenario<\/li>\n<\/ul>\n<p>So to maintain isolation but yet gain some flexibility we will:<\/p>\n<ol>\n<li>Add a <b>pricing<\/b> value model to the <b>coffeeconsumption<\/b> view model &#8211; this way costPerWeek will have access to it<\/li>\n<li>Add a <b>pricing<\/b> value model to the <b>consumptionscenarios<\/b> view model &#8211; this way it can pass it on each of the current and proposed consumptions models<\/li>\n<li>Add a <b>subscription<\/b> to the <b>pricing change<\/b> in the <b>consumptionscenarios<\/b> view model so we can update the pricing of the current and proposed consumptions models<\/li>\n<li>Have the consumption-scenario and coffee pricing view mediators <b>share the pricing<\/b> view model<\/li>\n<\/ol>\n<p>Here is what the relationship between the various mediators and model will look like:<\/p>\n<p>\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-todo8.png\" \/><\/p>\n<p>First, let&#8217;s add a <b>pricing<\/b> value model to the <b>coffeeconsumption<\/b> view model (located in <b>scripts\/viewmodel\/sgs.model.coffeeconsumption.js<\/b>):<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeeconsumption.initializeViewModel = function (pageSettings, scenarioName) {\r\n\t\/\/ ...\r\n\t\r\n\tvar viewModel = {\r\n\t\tpricing: \t\tko.observable(null), \r\n\t\tscenarioName: \tko.observable(scenarioName), \r\n\t\t\/\/ ...\r\n\t}\r\n\r\n\t\/\/ ...\r\n}\r\n<\/pre>\n<p>Second, let&#8217;s add a <b>pricing<\/b> value model to the <b>consumptionscenarios<\/b> view model (located in <b>scripts\/viewmodel\/sgs.model.consumptionscenarios.js<\/b>)<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.consumptionscenarios.initializeViewModel = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\r\n\tvar viewModel = {\r\n\t\tpricing: \t\t\t\tko.observable(null), \r\n\t\tcurrentConsumption: \tko.observable(current),\r\n\t\tproposedConsumption: \tko.observable(proposed)\r\n\t}\r\n\r\n\t\/\/ ...\r\n}\r\n<\/pre>\n<p>Third, let&#8217;s add a <b>subscription<\/b> to <b>pricing<\/b> value model so we can re-act to pricing model changes and pass the new pricing down to our 2 consumption models:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.consumptionscenarios.initializeViewModel = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\t\r\n\tviewModel.pricing.subscribe(function(newPricing) {\r\n\t\tviewModel.currentConsumption().pricing(newPricing);\r\n\t\tviewModel.proposedConsumption().pricing(newPricing);\r\n\t});\r\n\t\r\n\t\/\/ ...\r\n}<\/pre>\n<p>Fourth, let&#8217;s have the <a href=\"\">consumption-scenario view mediator<\/a> set the <b>pricing<\/b> based on the view model of the <b>coffee pricing view mediator<\/b>:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.consumptionscenarios.createViewMediator = function (pageSettings) {\r\n\t\/\/ ...\r\n\r\n\t\/\/ Save the view model\r\n\tsgs.mediator.consumptionscenarios.setViewModel(viewModel);\t\r\n\r\n\t\/\/ Set the pricing based on the Coffee Pricing view model\r\n\tvar priceList = sgs.mediator.coffeepricing.getViewModel();\r\n\tviewModel.pricing(priceList);\r\n\t\r\n\t\/\/ ...\r\n}\r\n<\/pre>\n<p>So now we have the bits in place to fully implement the <b>costPerWeek<\/b> function of the <b>coffeeconsumption<\/b> view model. (Remember we had just put in a stub implementation returning 0 in step <a href=\"#todo2\">&#8220;2. Create the Consumption Scenarios ViewModel&#8221;<\/a>). It&#8217;s a matter of performing the calculations like so:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.coffeeconsumption.initializeViewModel = function (pageSettings, scenarioName) {\r\n\t\/\/ ...\r\n\t\r\n\tviewModel.costPerWeek = ko.dependentObservable(function() {\r\n\t\t\/\/ Get the base price\r\n\t\tif (this.pricing() == null) {\r\n\t\t\treturn 0;\r\n\t\t}\r\n\t\t\r\n\t\tvar basePrice \t= this.pricing().getCoffeeBeveragePrice(this.drinkType(), this.drinkSize());\r\n\t\tvar dailyCost \t= basePrice * this.drinksPerDay();\r\n\t\tvar weeklyCost\t= dailyCost * this.drinkDaysPerWeek();\r\n\t\tvar result \t\t= Math.round(weeklyCost * 100) \/ 100;\r\n\t\t\r\n\t\treturn result;\r\n\t}, viewModel);\r\n\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>And finally we can go back and finish the implementation of the <b>savingsPerWeek<\/b> function in the <b>consumptionscenarios<\/b> view model. (Remember we had just put in a stub implementation returning 0 in step <a href=\"#todo2\">&#8220;2. Create the Consumption Scenarios ViewModel&#8221;<\/a>)<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.consumptionscenarios.initializeViewModel = function (pageSettings) {\r\n\t\/\/ ...\r\n\r\n\tviewModel.savingsPerWeek = ko.dependentObservable(function() {\r\n\t\tvar costDifference = this.currentConsumption().costPerWeek() - this.proposedConsumption().costPerWeek();\r\n\t\tvar result = Math.round(costDifference * 100) \/ 100;\r\n\t\treturn result;\r\n\t}, viewModel);\r\n\r\n\t\/\/ ...\r\n\t\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>Now we&#8217;re ready to refresh the index.html page, select Grande Latte under Current Habits and Tall Regular under Proposed Changes and you should see the costs update as well as the savings. We now have a functioning Weekly Coffee Consumption panel! :-)<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part2-4.png\" \/><\/p>\n<p>I will leave the following usability additions for you to do:<\/p>\n<ul>\n<li>Formatting of the costs and saving amounts (dependent observables and Accounting.js &#8211; see <a href=\"https:\/\/blog.monnet-usa.com\/?p=368#todo9\" target='_blank'>Part 1 ToDo #9 &#8211; Applying Formatting Rules<\/a>)<\/li>\n<li>Add visual feedback (using the jQuery highlight effect) for the costs and savings fields see <a href=\"https:\/\/blog.monnet-usa.com\/?p=368#todo10\" target='_blank'>Part 1 ToDo #10 &#8211; Applying Visual Feedback Clues<\/a><\/li>\n<li>Overall styling<\/li>\n<\/ul>\n<p>\t\t\t\t\t<a name='todo9'><\/a><\/p>\n<h5>9. Create the Savings Forecast View<\/h5>\n<p>In our <b>index.html<\/b> page, let&#8217;s add a second section tag to represent our third panel \/ view:<br \/>\n\t\t\t\t\tthe <b>savings-forecast-view<\/b>.<br \/>\n\t\t\t\t\tThe view will help us compare projected savings against our goal.<\/p>\n<pre class=\"brush: html\">\r\n&lt;body&gt;\r\n\t&lt;!-- ... --&gt;\r\n\t&lt;section id='savings-forecast-view'&gt;\r\n\t&lt;\/section&gt;\r\n&lt;\/body&gt;\r\n<\/pre>\n<p>This is what the view will look like:<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part2-5.png\" \/><\/p>\n<p>The view will have three pairs of labels and spans to show calculated values for:<\/p>\n<ul>\n<li>Savings forecast per month<\/li>\n<li>Forecast variance from the target monthly savings goal<\/li>\n<li>Forecasted number of months to achieve the savings goal<\/li>\n<\/ul>\n<pre class=\"brush: html\">\r\n\t\t\t&lt;label for=\"savings-forecast-per-month\"&gt;Savings Forecast Per Month:&lt;\/label&gt;\r\n\t\t\t&lt;span id=\"savings-forecast-per-month\" &gt;&lt;\/span&gt;&lt;br\/&gt;\r\n\r\n\t\t\t&lt;label for=\"forecast-variance-per-month\"&gt;Forecast Variance:&lt;\/label&gt;\r\n\t\t\t&lt;span id=\"forecast-variance-per-month\"&gt;&lt;\/span&gt;&lt;br\/&gt;\r\n\r\n\t\t\t&lt;label for=\"time-to-goal-in-months\"&gt;Months To Savings Goal:&lt;\/label&gt;\r\n\t\t\t&lt;span id=\"time-to-goal-in-months\"&gt;&lt;\/span&gt;&lt;br\/&gt;\r\n<\/pre>\n<p>\t\t\t\t\t<a name='todo10'><\/a><\/p>\n<h5>10. Create the Savings Forecast ViewModel<\/h5>\n<p>In the <b>ViewModels<\/b> folder under the <b>scripts<\/b> folder, create a file named: <b>sgs.model.savings-forecast.js<\/b>.<br \/>\n\t\t\t\t\tAnd let&#8217;s add a statement to load our new module in in our <b>LAB.js<\/b> section:\n\t\t\t\t\t<\/p>\n<pre class=\"brush: javascript\">\r\n$LAB\r\n\t\/\/ ...\r\n\t.script(\"scripts\/viewmodel\/sgs.model.savings-forecast.js\").wait()\r\n\t\/\/ ...\r\n<\/pre>\n<p>Let&#8217;s add the code snippet to lazy-create the namespace:<\/p>\n<pre class=\"brush: javascript\">\r\n\/\/ Lazy initialize our namespace context: sgs.model.savingsforecast\r\nif (typeof(sgs) == 'undefined') sgs = { }\r\nif (typeof(sgs.model) == 'undefined') sgs.model = { }\r\nif (typeof(sgs.model.savingsforecast) == 'undefined') sgs.model.savingsforecast = { }\r\n\r\nif (typeof(console) != 'undefined' && console) console.info(\"sgs.model.savingsforecast loading!\");\r\n<\/pre>\n<p>Let&#8217;s create our <b>initializeViewModel<\/b> function to initialize the view model.<br \/>\n\t\t\t\t\tWe need two dependent observable functions:<\/p>\n<ul>\n<li><b>forecastVariancePerMonth<\/b> will allow us to experiment to see how much we could save<\/li>\n<li><b>timeToGoalInMonths<\/b> will show us the difference between proposed and current costs and will be implemented as a dependent observable function<\/li>\n<\/ul>\n<p>\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-todo10.png\" \/><\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.savingsforecast.initializeViewModel = function (pageSettings) {\r\n\t\/\/ We can use properties of the pageSettings as default values for any of our ValueModels\r\n\t\/\/ If pageSettings are not provided we'll initialize an empty object\r\n\tif (typeof(pageSettings) == 'undefined') var pageSettings = { }\r\n\t\r\n\tvar viewModel = {\r\n\t};\t\r\n\r\n\tviewModel.forecastVariancePerMonth = ko.dependentObservable(function() {\r\n\t\t\/\/ TBD\r\n\t}, viewModel);\r\n\r\n\tviewModel.timeToGoalInMonths = ko.dependentObservable(function() {\r\n\t\t\/\/ TBD\r\n\t}, viewModel);\r\n\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>Design Note: in this series of tutorial I have promoted the idea of <b>decoupling all views, models, and mediators<\/b> (as possible) as it makes the application <b>more modular<\/b> and it is <b>easier to test<\/b> all parts independently. So for this view model, instead of requesting the value models from other mediators namely <b>savingsGoalAmount<\/b>, <b>savingsTargetPerMonth<\/b> and  <b>savingsPerMonth<\/b>, we will create dedicated value models which will later synchronize with using subscriptions in our mediators &#8211; since it is ok for the mediators to collaborate.\n\t\t\t\t\t<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.savingsforecast.initializeViewModel = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\r\n\tvar viewModel = {\r\n\t\tsavingsGoalAmount: ko.observable(0), \r\n\t\tsavingsTargetPerMonth: ko.observable(0), \r\n\t\tsavingsPerMonth: ko.observable(0)\r\n\t};\t\r\n\r\n\t\/\/ ...\r\n\t\r\n\treturn viewModel;\r\n}\r\n<\/pre>\n<p>Let&#8217;s flesh-out our calculations:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.model.savingsforecast.initializeViewModel = function (pageSettings) {\r\n\t\/\/ ...\t\r\n\r\n\tviewModel.forecastVariancePerMonth = ko.dependentObservable(function() {\r\n\t\tvar variance = this.savingsPerMonth() - this.savingsTargetPerMonth();\r\n\t\tvar result = Math.round(variance * 100) \/ 100;\r\n\t\treturn result;\r\n\t}, viewModel);\r\n\r\n\tviewModel.timeToGoalInMonths = ko.dependentObservable(function() {\r\n\t\tvar timeToGoal = 0;\r\n\t\tvar savingsPerMonth = this.savingsPerMonth();\r\n\t\tvar savingsGoalAmount = this.savingsGoalAmount();\r\n\t\t\r\n\t\tif (savingsPerMonth != 0) {\r\n\t\t\ttimeToGoal = savingsGoalAmount \/ savingsPerMonth;\r\n\t\t}\r\n\t}, viewModel);\r\n\r\n\t\/\/ ...\r\n}\r\n<\/pre>\n<p>\t\t\t\t\t<a name='todo11'><\/a><\/p>\n<h5>11. Create the Savings Forecast View Mediator<\/h5>\n<p>In the <b>Viewmediator<\/b> folder under the <b>scripts<\/b> folder, create a file named: <b>sgs.mediator.savings-forecast.js<\/b>. This is the module where we&#8217;ll place our data-binding logic as well as any code related to mediating access between the page, the consumption scenario view, and its view model.<br \/> And let&#8217;s add a statement to load our new module in in our <b>LAB.js<\/b> section:\n\t\t\t\t\t<\/p>\n<pre class=\"brush: javascript\">\r\n$LAB\r\n\t\/\/ ...\r\n\t.script(\"scripts\/viewmodel\/sgs.model.savings-forecast.js\").wait()\r\n\t.script(\"scripts\/viewmediator\/sgs.mediator.savings-forecast.js\").wait()\r\n\t\/\/ ...\r\n<\/pre>\n<p>Let&#8217;s define our namespace:<\/p>\n<pre class=\"brush: javascript\">\r\n\/\/ Lazy initialize our namespace context: sgs.mediator.savingsforecast\r\nif (typeof(sgs) == 'undefined') sgs = { }\r\nif (typeof(sgs.mediator) == 'undefined') sgs.mediator = { }\r\nif (typeof(sgs.mediator.savingsforecast) == 'undefined') sgs.mediator.savingsforecast = { }\r\n\r\nif (typeof(console) != 'undefined' && console) console.info(\"sgs.mediator.savingsforecast loading!\");\r\n<\/pre>\n<p>Let&#8217;s create our <b>createViewMediator<\/b> function (per our convention) which we will eventually invoke from the <b>InitializeApplication<\/b> method in <b>application.js<\/b>. The createViewMediator function will have 5 responsibilities:<\/p>\n<ol>\n<li>Instantiate a view model for the savings forecast view<\/li>\n<li>Establish subscriptions for various value models to keep the main view model synchronized<\/li>\n<li>Declare the data-binding between the HTML elements of the view and their corresponding value models in the view model<\/li>\n<li>Ask KnockoutJS to make the bindings effective (they will be live right after that)<\/li>\n<li>Save off the view model so we can access it from other parts of the app<\/li>\n<\/ol>\n<p>So let&#8217;s implement the first responsibility to instantiate a view model for the consumption scenario view  (using the <b>initializeViewModel<\/b> function we just created in the consumption scenario view model module).<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.savingsforecast.createViewMediator = function (pageSettings) {\r\n\t\/\/ Create the view Savings Forecast view-specific view model\r\n\tvar viewModel = sgs.model.savingsforecast.initializeViewModel(pageSettings);\r\n}\r\n<\/pre>\n<p>Now we need to establish <b>3 subscriptions<\/b> so that we can keep our saving forecast view model in synch with value models from the <b>savings goal<\/b>  and <b>consumption scenarios<\/b> view models. Here is a graphical depiction of what we need:<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-todo11.png\" \/><\/p>\n<p>To make this happen we need the <b>savingsforecast mediator<\/b> to request specific value models from the view models of the <b>savingsgoal<\/b> and <b>consumptionsscenarios<\/b> mediators:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.savingsforecast.createViewMediator = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Subscribe to changes in savingsGoalAmount and savingsTargetPerMonth \r\n\t\/\/ to synchronize our own equivalent value models\r\n\tvar savingsGoalModel = sgs.mediator.savingsgoal.getViewModel();\r\n\t\r\n\t\/\/ Initialize the current savingsGoalAmount\r\n\tviewModel.savingsGoalAmount(savingsGoalModel.savingsGoalAmount());\r\n\r\n\tsavingsGoalModel.savingsGoalAmount.subscribe(function(newValue) {\r\n\t\tviewModel.savingsGoalAmount(newValue);\r\n\t});\r\n\r\n\tsavingsGoalModel.savingsTargetPerMonth.subscribe(function(newValue) {\r\n\t\tviewModel.savingsTargetPerMonth(newValue);\r\n\t});\r\n\r\n\t\/\/ Subscribe to changes in savingsPerMonth to synchronize our own equivalent value model\r\n\tvar consumptionscenariosModel = sgs.mediator.consumptionscenarios.getViewModel();\r\n\tconsumptionscenariosModel.savingsPerMonth.subscribe(function(newValue) {\r\n\t\tviewModel.savingsPerMonth(newValue);\r\n\t});\r\n\t\r\n\t\/\/ ...\r\n}\r\n<\/pre>\n<p>Now let&#8217;s declare the data-bindings we need.<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.savingsforecast.createViewMediator = function (pageSettings) {\r\n\t\/\/ Create the view Savings Forecast view-specific view model\r\n\tvar viewModel = sgs.model.savingsforecast\r\n\t\t.initializeViewModel(pageSettings);\r\n\t\r\n\t\/\/ Declare the HTML element-level data bindings \r\n\t$(\"#savings-forecast-per-month\")\r\n\t\t.attr(\"data-bind\",\"text: savingsPerMonth\");\r\n\t$(\"#forecast-variance-per-month\")\t\r\n\t\t.attr(\"data-bind\",\"text: forecastVariancePerMonth\");\r\n\t$(\"#time-to-goal-in-months\")\t\r\n\t\t.attr(\"data-bind\",\"text: timeToGoalInMonths\");\r\n\r\n\t\/\/ ...\r\n<\/pre>\n<p>Now we&#8217;ll ask KnockoutJS to &#8220;apply&#8221;, i.e. register \/ enable our bindings. <\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.savingsforecast.createViewMediator = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Ask KnockoutJS to data-bind the view model to the view\r\n\tvar viewNode = $('#savings-forecast-view')[0];\r\n\tko.applyBindings(viewModel, viewNode);\r\n}\r\n<\/pre>\n<p>Now let&#8217;s add the 2 functions to store retrieve our savings forecast view model:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.savingsforecast.getViewModel = function() {\r\n\treturn $(document).data(\"sgs.model.savingsforecast.viewmodel\");\r\n}\r\n\r\nsgs.mediator.savingsforecast.setViewModel = function(viewModel) {\r\n\t$(document).data(\"sgs.model.savingsforecast.viewmodel\", viewModel);\r\n}\r\n<\/pre>\n<p>Now we can use the setViewModel function inside createViewMediator to save off our freshly-created view model.<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.savingsforecast.createViewMediator = function (pageSettings) {\r\n\t\/\/ ...\r\n\t\r\n\t\/\/ Save the view model\r\n\tsgs.mediator.savingsforecast.setViewModel(viewModel);\t\r\n\r\n\tif (typeof(console) != 'undefined' && console) console.info(\"sgs.mediator.savingsforecast ready!\");\r\n}\r\n<\/pre>\n<p>Again you can try your new mediator using the browser console:<\/p>\n<pre class=\"brush: javascript\">\r\nsgs.mediator.savingsforecast.createViewMediator({})\r\n<\/pre>\n<p>Playing with the simulation should now update the savings-forecast view.<\/p>\n<p>\t\t\t\t\t<a name='todo12'><\/a><\/p>\n<h5>12. Link the Page and the Savings Forecast View Mediator<\/h5>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part3-todo12.png\" \/><\/p>\n<p>To instantiate our new mediator, we just need to add a call to the overall page <b>InitializeApplication<\/b> function (located in our <b>scripts\/application.js<\/b>) to our consumption scenario <b>createViewMediator<\/b> function like so:<\/p>\n<pre class=\"brush: javascript\">\r\nfunction InitializeApplication() {\r\n\tif (typeof(console) != 'undefined' && console) \r\n\t\tconsole.info(\"InitializeApplication starting ...\");\r\n\t\t\r\n\t\/\/ Initialize our page-wide settings\r\n\tvar pageSettings = { defaultSavingsGoal: 500 }\r\n\t\r\n\t\/\/ Create \/ launch our view mediator(s)\r\n\tsgs.mediator.savingsgoal.createViewMediator(pageSettings);\r\n\tsgs.mediator.coffeepricing.createViewMediator(pageSettings);\r\n\tsgs.mediator.consumptionscenarios.createViewMediator(pageSettings);\r\n\tsgs.mediator.savingsforecast.createViewMediator(pageSettings);\r\n\r\n\tif (typeof(console) != 'undefined' && console) \r\n\t\tconsole.info(\"InitializeApplication done ...\");\t\r\n}\r\n<\/pre>\n<p>Now you can refresh your page and should be able to see the new savings forecast view update as you experiment!<\/p>\n<p>\t\t\t\t\t<img decoding=\"async\" src=\".\/wp-content\/media\/knockoutjs\/savings-goal-simulator-part2-6.png\" \/><\/p>\n<hr>\n<h5>Overall Recap<\/h5>\n<p>In this part 3 of the tutorial we have added quite a bit more functionality and complexity. Hopefully the modularization, separation into different files and folders, the namespacing, and various patterns are now starting to reveal their purpose and value. ;-)<br \/> So here is a quick recap of the steps we followed:<\/p>\n<table class='details_table'>\n<tr>\n<th>#<\/th>\n<th>To Do<\/th>\n<th>Area (Module)<\/th>\n<\/tr>\n<tr>\n<td><a href='#todo1'>1<\/a><\/td>\n<td>Create the Consumption Scenarios View<\/td>\n<td>View<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo2'>2<\/a><\/td>\n<td>Create the Consumption Scenarios ViewModel<\/td>\n<td>View Model<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo3'>3<\/a><\/td>\n<td>Create the Consumption Scenarios View Mediator<\/td>\n<td>View Mediator<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo4'>4<\/a><\/td>\n<td>Link the Page and the View Mediator<\/td>\n<td>Main App<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo5'>5<\/a><\/td>\n<td>Create the Coffee Pricing ViewModel<\/td>\n<td>View Model<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo6'>6<\/a><\/td>\n<td>Create the Coffee Pricing ViewMediator<\/td>\n<td>View Mediator<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo7'>7<\/a><\/td>\n<td>Link the Page and the Coffee Pricing View Mediator<\/td>\n<td>Main App<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo8'>8<\/a><\/td>\n<td>Leveraging Independent View Models<\/td>\n<td>View Model<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo9'>9<\/a><\/td>\n<td>Create the Savings Forecast View<\/td>\n<td>View Mediator<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo10'>10<\/a><\/td>\n<td>Create the Savings Forecast ViewModel<\/td>\n<td>View Model<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo11'>11<\/a><\/td>\n<td>Create the Savings Forecast View Mediator<\/td>\n<td>View Mediator<\/td>\n<\/tr>\n<tr>\n<td><a href='#todo12'>12<\/a><\/td>\n<td>Link the Page and the Savings Forecast View Mediator<\/td>\n<td>Main App<\/td>\n<\/tr>\n<\/table>\n<p>\t\t\t\t\t<a name='sowhat'><\/a><\/p>\n<h3>So What?<\/h3>\n<p>This part 3 gave you a lot more practice with the core patterns and also showed you the following:<\/p>\n<ul>\n<li>Create hierarchical view models<\/li>\n<li>Setup custom subscriptions to keep independent view models synchronized<\/li>\n<li>Configure multiple attributes inside a data-bind declaration (e.g. checked and enabled)<\/li>\n<li>Leverage jStorage for local browser data caching<\/li>\n<\/ul>\n<p>If we step back and look at the <b>modularization<\/b> of our application so far, we&#8217;ll notice that although our models and view mediators are modular, the actual index.html web page has become larger and is not modular at all. Wouldn&#8217;t it be nice if we could extract each of our views into external modules? Well, this is exactly what <a href=\"#\">part 4<\/a> will cover!<\/p>\n<ul>\n<li>Leveraging jQuery templates<\/li>\n<li>Extracting views into templates (and in separate files)<\/li>\n<li>&#8230; and more!<\/li>\n<\/ul>\n<p>So stay tuned!<\/p>\n<p>\t\t\t\t\t<a name=\"referencesandresources\" ><\/a><\/p>\n<h3>References and Resources<\/h3>\n<h5>Patterns<\/h5>\n<ul>\n<li><a name='mvvmp' href='http:\/\/en.wikipedia.org\/wiki\/Model_View_ViewModel'>Model View &#8211; ViewModel Pattern<\/a><\/li>\n<li><a name='mvcp' href='http:\/\/heim.ifi.uio.no\/~trygver\/themes\/mvc\/mvc-index.html'>MVC Xerox Parc 1978-79<\/a><\/li>\n<li><a name='mvcp' href='http:\/\/c2.com\/cgi\/wiki?ModelViewController'>MVC Pattern<\/a><\/li>\n<li><a name='obp' href='http:\/\/c2.com\/cgi\/wiki?ObserverPattern'>Observer Pattern<\/a><\/li>\n<li><a name='psp' href='http:\/\/c2.com\/cgi\/wiki?PublishSubscribeModel'>Publish Subscribe Pattern<\/a><\/li>\n<li><a name='vmp' href='http:\/\/c2.com\/cgi\/wiki?ValueModel'>Value Model Pattern<\/a><\/li>\n<li><a name='dbp' href='http:\/\/c2.com\/cgi\/wiki?DataBinding'>Data Binding Pattern<\/a><\/li>\n<li><a name='ns-summary' href='http:\/\/enterprisejquery.com\/2010\/10\/how-good-c-habits-can-encourage-bad-javascript-habits-part-1\/'>Summary of Namespacing Approaches (by Elijah Manor)<\/a><\/li>\n<li><a name='ns-objectliteral' href='http:\/\/stackoverflow.com\/questions\/881515\/javascript-namespace-declaration\/881556#881556'>Namespacing using Object Literals<\/a><\/li>\n<li><a name='ns-function' href='http:\/\/stackoverflow.com\/questions\/881515\/javascript-namespace-declaration\/881611#881611'>Namespacing using Functions<\/a><\/li>\n<\/ul>\n<h5>Frameworks And Blogs<\/h5>\n<ul>\n<li><a name='ko' href='http:\/\/knockoutjs.com\/'>KnockoutJS<\/a><\/li>\n<li><a name='lko' href='http:\/\/learn.knockoutjs.com\/'>Learn KnockoutJS (Interactive Tutorial \/ Playground)<\/a><\/li>\n<li><a name='jq' href='http:\/\/jquery.com\/'>jQuery<\/a><\/li>\n<li><a name='jqui' href='http:\/\/jqueryui.com\/'>jQuery UI<\/a><\/li>\n<li><a name='jqt' href='http:\/\/api.jquery.com\/jQuery.template\/'>jQuery Template<\/a><\/li>\n<li><a name='jqtapi' href='http:\/\/api.jquery.com\/jQuery.template\/'>jQuery Template API<\/a><\/li>\n<li><a name='ss' href='http:\/\/blog.stevensanderson.com\/'>Knock Me Out (Steve Sanderson&#8217;s Blog)<\/a><\/li>\n<li><a name='kmo' href='http:\/\/www.knockmeout.net\/'>Knock Me Out (Ryan Niemeyer&#8217;s Blog)<\/a><\/li>\n<li><a name='pflsjaa' href='http:\/\/addyosmani.com\/blog\/patterns-for-large-scale-javascript-application-architecture\/'>Patterns For Large-Scale Javascript Architecture (Addy Osmani)<\/a><\/li>\n<\/ul>\n<p>\t\t\t\t\t<a name=\"javascript-loaders\" ><\/a><\/p>\n<h5>Javascript Loaders<\/h5>\n<ul>\n<li><a name='headjs' href='http:\/\/headjs.com\/' target='_blank'>HeadJS<\/a><\/li>\n<li><a name='labjs' href='http:\/\/labjs.com\/' target='_blank'>LABjs<\/a><\/li>\n<li><a name='lazyload' href='https:\/\/github.com\/rgrove\/lazyload' target='_blank'>LazyLoad<\/a><\/li>\n<li><a name='jquery-cookie' href='https:\/\/github.com\/carhartl\/jquery-cookie\/blob\/master\/jquery.cookie.js' target='_blank'>jQuery Cookie<\/a><\/li>\n<\/ul>\n<p>\t\t\t\t\t<a name=\"jquery-plugins\" ><\/a><\/p>\n<h5>jQuery Plugins<\/h5>\n<ul>\n<li><a name='jquery-blockui' href='http:\/\/jquery.malsup.com\/block\/' target='_blank'>jQuery BlockUI<\/a><\/li>\n<li><a name='jquery-cookie' href='https:\/\/github.com\/carhartl\/jquery-cookie\/blob\/master\/jquery.cookie.js' target='_blank'>jQuery Cookie<\/a><\/li>\n<li><a name='jquery-datatables' href='http:\/\/datatables.net\/' target='_blank'>jQuery DataTables<\/a><\/li>\n<li><a name='jquery-hotkeys' href='https:\/\/github.com\/tzuryby\/jquery.hotkeys' target='_blank'>jQuery HotKeys<\/a><\/li>\n<li><a name='jquery-tree' href='http:\/\/www.jstree.com\/' target='_blank'>jQuery jsTree<\/a><\/li>\n<li><a name='jquery-validate' href='http:\/\/plugins.jquery.com\/project\/jqueryvalidate' target='_blank'>jQuery Validate<\/a><\/li>\n<\/ul>\n<p>\t\t\t\t\t<a name=\"other-js-libraries\" ><\/a><\/p>\n<h5>Other Javascript Libraries<\/h5>\n<ul>\n<li><a name='js-modernizr' href='http:\/\/www.modernizr.com\/' target='_blank'>Modernizr<\/a><\/li>\n<li><a name='js-blockui' href='http:\/\/jquery.malsup.com\/block\/' target='_blank'>BlockUI<\/a><\/li>\n<li><a name='js-accounting' href='http:\/\/josscrowcroft.github.com\/accounting.js\/' target='_blank'>Accounting.js<\/a><\/li>\n<li><a name='js-currencymask' href='https:\/\/github.com\/techarch\/CurrencyMaskJS' target='_blank'>CurrencyMask JS<\/a><\/li>\n<li><a name='jquery-maskedinput' href='http:\/\/digitalbush.com\/projects\/masked-input-plugin\/' target='_blank'>Masked Input<\/a><\/li>\n<li><a name='js-raphael' href='http:\/\/raphaeljs.com\/' target='_blank'>Raphael JS<\/a><\/li>\n<li><a name='js-graphael' href='http:\/\/g.raphaeljs.com\/' target='_blank'>gRaphael JS<\/a><\/li>\n<li><a name='js-storage' href='https:\/\/github.com\/andris9\/jStorage' target='_blank'>jStorage<\/a><\/li>\n<li><a name='js-underscore' href='http:\/\/documentcloud.github.com\/underscore\/' target='_blank'>Underscore<\/a><\/li>\n<li><a name='js-fixtures' href='http:\/\/jupiterjs.com\/news\/organizing-a-jquery-application#news\/ajax-fixtures-plugin-for-jquery' target='_blank'>Ajax Fixtures<\/a><\/li>\n<\/ul>\n<p>\t\t\t\t\t<a name=\"js-test-frameworks\" ><\/a><\/p>\n<h5>Javascript Test Frameworks<\/h5>\n<ul>\n<li><a name='js-jasmine' href='http:\/\/http:\/\/pivotal.github.com\/jasmine\/' target='_blank'>Jasmine, BDD for Javascript<\/a><\/li>\n<li><a name='js-jasmine-species' href='http:\/\/rudylattae.github.com\/jasmine-species\/' target='_blank'>Jasmine Species, BDD Grammar Extensions (Given, When, Then, etc.)<\/a><\/li>\n<\/ul>\n<h5>My Other Related Posts:<\/h5>\n<ul name=\"myotherrelatedposts\">\n<li><a name='ko-part1' href='https:\/\/blog.monnet-usa.com\/?p=354'>Creating Rich Interactive Web Apps With KnockOut.js &#8211; Part 1<\/a><\/li>\n<li><a name='ko-part2' href='https:\/\/blog.monnet-usa.com\/?p=368'>Creating Rich Interactive Web Apps With KnockOut.js &#8211; Part 2<\/a><\/li>\n<\/ul>\n<p>\t\t\t\t\t<a name='kodemosrc'><\/a><\/p>\n<h5>Full Source Of The Savings Goal Simulator (KnockoutJS Demo)<\/h5>\n<p>The whole application is available on Github under <a href='https:\/\/github.com\/techarch\/savings-goal-simulator' target='_blank'>techarch \/ savings-goal-simulator<\/a>.<\/p>\n<div class=\"blog-msm-ad\">\n<div class=\"blog-msm-ad1\">\n\t\t\t\t\t\t\tIf you enjoyed this post, I would love it if you could check out <a href=\"http:\/\/bit.ly\/myskillsmap\">mySkillsMap<\/a>, my skills management app.\n\t\t\t\t\t\t<\/div>\n<div class=\"blog-msm-ad2\">\n\t\t\t\t\t\t\t\t<a href=\"http:\/\/www.myskillsmap.com\/take-charge-of-your-skills-development\"><br \/>\n\t\t\t\t\t\t\t\t\t<img src='.\/wp-content\/media\/msm\/start-your-free-trial-now.png'  \/><br \/>\n\t\t\t\t\t\t\t\t<\/a>\n\t\t\t\t\t\t<\/div>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Intro In Part 1, we introduced the key concepts and patterns including KnockoutJS. In Part 2, we started to build the first increment of our Savings Goal Simulation rich web app. The objectives of this third tutorial are: Build up the rest of the application Gain more practice with View Models, View Mediators, and KnockoutJS [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[59,30,48,46],"tags":[],"class_list":["post-404","post","type-post","status-publish","format-standard","hentry","category-javascript","category-jquery","category-knockoutjs","category-rich-web-apps"],"_links":{"self":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts\/404","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=404"}],"version-history":[{"count":4,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts\/404\/revisions"}],"predecessor-version":[{"id":410,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts\/404\/revisions\/410"}],"wp:attachment":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=404"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=404"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=404"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}