With binding magic, Make Parent and Child Structure in your app with knockoutJs

January 1st, 2014 — 15 min read

With binding magic, Make Parent and Child Structure in your app with knockoutJs

Knockoutjs is a Javascript Library that lets you write and handle dynamic web user interface.When using knockout you can say ‘By by JQuery and Welcome knockout’. Well here i am going to define how you can use it in the application in a structural way. If you haven’t have any idea you can visit knockoutjs.com. And to get skilled in knockout usage you can see Rniemeyer’s Blog where he has written many posts regarding its better usage.

What am i going to present in this post?

The problem

To understand the problem let’s have a scanrio. I have an application with three pages home.html , form.html and list.html. Each page has a viewmodel associated with it. And each page has a function in viewmodel that redirects to the home page. Each viewmodel has a property title that should be displayed in the app.

First lets see the application structure

/myapp/
    /js/
        /jquery.js
        /knockout-2.3.0.js
    /css/
        /style.css
    /home.html
    /form.html
    /list.html

Let’s see the pages.

Home.html

<!DOCTYPE html>
<html>
    <head>
        <title>Knockout! Parent Child Usage </title>
        <link rel="stylesheet" type="text/css" href="css/style.css" />
        <script type="text/javascript" src="js/jquery.js" ></script>
        <script type="text/javascript" src="js/knockout-2.3.0.js" ></script>
        <script type="text/javascript">
        var page = function() {
            var self = this
 
            self.Title      =   ko.observable('Knockout Techniques')
            self.FirstName  =   ko.observable()
            self.LastName   =   ko.observable()
 
            self.LoadData   =   function(){
                self.FirstName('Muhammad')
                self.LastName('Raheel')
            }
 
            self.Navigate = function(page){
                if(page == 'list'){
                    window.location =   'list.html'
                }else if(page == 'form'){
                    window.location =   'form.html'
                }else{
                    window.location =   'home.html'
                }
            }
 
            /*  Call LoadData for Initialization    */
            self.LoadData()
        }
 
        $( document ).ready(function() {
            ko.applyBindings(new page())
        })
        </script>
    </head>
    <body>
        <h2 data-bind='text:Title'></h2>
        <h3 data-bind='text:FirstName'></h3>
        <h3 data-bind='text:LastName'></h3>
        <table>
            <tr>
                <td>Go to Form Page </td>
                <td><a data-bind="click:Navigate.bind($data,'form'),text:'Form'" /></td>
            </tr>
            <tr>
                <td>Go to List Page </td>
                <td><a data-bind="click:Navigate.bind($data,'list'),text:'List'"></a></td>
            </tr>
        </table>
    </body>
</html>

Here is form.html

<!DOCTYPE html>
<html>
    <head>
        <title>Knockout! Parent Child Usage </title>
        <link rel="stylesheet" type="text/css" href="css/style.css" />
        <script type="text/javascript" src="js/jquery.js" ></script>
        <script type="text/javascript" src="js/knockout-2.3.0.js" ></script>
        <script type="text/javascript">
        var page = function() {
            var self = this
 
            self.Title      =   ko.observable('Knockout Techniques')
            self.FirstName  =   ko.observable()
            self.LastName   =   ko.observable()
            self.FullName   =   ko.computed(function(){
                return self.FirstName() +' '+self.LastName()
            })
 
            self.LoadData   =   function(){
                self.FirstName('Muhammad')
                self.LastName('Raheel')
            }
 
            self.Navigate = function(page){
                if(page == 'list'){
                    window.location =   'list.html'
                }else if(page == 'form'){
                    window.location =   'form.html'
                }else{
                    window.location =   'home.html'
                }
            }
 
            /*  Call LoadData for Initialization    */
            self.LoadData()
        }
 
        $( document ).ready(function() {
            ko.applyBindings(new page())
        })
        </script>
    </head>
    <body>
        <h2 data-bind='text:Title'></h2>
        <input type="text" data-bind='value:FirstName' />
        <br />
        <input type="text" data-bind='value:LastName' />
        <br />
        <h2 data-bind='text:FullName'></h2>
 
        <table>
            <tr>
                <td>Go to Home Page </td>
                <td><a data-bind="click:Navigate.bind($data,'home'),text:'Home'"></a></td>
            </tr>
            <tr>
                <td>Go to List Page </td>
                <td><a data-bind="click:Navigate.bind($data,'list'),text:'List'"></a></td>
            </tr>
        </table>
    </body>
</html>

And here is list.html

<!DOCTYPE html>
<html>
    <head>
        <title>Knockout! Parent Child Usage </title>
        <link rel="stylesheet" type="text/css" href="css/style.css" />
        <script type="text/javascript" src="js/jquery.js" ></script>
        <script type="text/javascript" src="js/knockout-2.3.0.js" ></script>
        <script type="text/javascript">
        var page = function() {
            var self = this
 
            self.Title  =   ko.observable('Knockout Techniques')
            self.Users  =   ko.observableArray([
                { Name : 'Raheel',Age:27},
                { Name : 'Saleem',Age:24},
                { Name : 'Aamir',Age:25}
            ])
 
            self.Navigate = function(page){
                if(page == 'list'){
                    window.location =   'list.html'
                }else if(page == 'form'){
                    window.location =   'form.html'
                }else{
                    window.location =   'home.html'
                }
            }
        }
 
        $( document ).ready(function() {
            ko.applyBindings(new page())
        })
        </script>
    </head>
    <body>
        <h2 data-bind='text:Title'></h2>
 
        <table>
            <thead>
                <tr>
                    <td>Name</td>
                    <td>Age</td>
                </tr>
            </thead>
            <tbody data-bind='foreach:Users'>
                <tr>
                    <td data-bind='text:$data.Name'></td>
                    <td data-bind='text:$data.Age'></td>
                </tr>
            </tbody>
        </table>
 
        <table>
            <tr>
                <td>Go to Home Page </td>
                <td><a data-bind="click:Navigate.bind($data,'home'),text:'Home'"></a></td>
            </tr>
            <tr>
                <td>Go to Form Page </td>
                <td><a data-bind="click:Navigate.bind($data,'form'),text:'Form'" /></td>
            </tr>
        </table>
    </body>
</html>

OK. The structure of all pages is pretty simple. But some things here are to be noted.
1.Title property is duplicating in every viewmodel.
2.Navigate function is duplicating in ever viewmodel.

The Solution

So we are now going to work around to write succinct and DRY code.
In the first step we are going to create a parent viewmodel.

<script type="text/javascript">
    function VM() {
        var self = this;
 
        self.Title  =   ko.observable('Knockout! Parent and Child Usage Techniques')
        self.Home   =   ko.observable()
        self.Form   =   ko.observable()
        self.List   =   ko.observable()
 
        self.ParentMethod = function(data){
            console.log('Hi! i am called from a child');
        }
 
        self.Navigate = function(page){
            if(page == 'list'){
                window.location =   'list.html'
            }else if(page == 'form'){
                window.location =   'form.html'
            }else{
                window.location =   'home.html'
            }
        }
    }
 
    var vm = new VM()
</script>

OK. In this viewmodel we have moved navigation function and Title property as we will be using it on every page. Here i have created observable property for every page. Home property for home.html Form property for form.html and so on. Next i have written Title property we will be calling on every page. And Navigation function we will be calling on every page. This lets us write common code between page to write only on one place.

Now lets see pages then i will describe how the magic works.

Home.html

<!DOCTYPE html>
<html>
    <head>
        <title>Knockout! Parent Child Usage </title>
        <link rel="stylesheet" type="text/css" href="css/style.css" />
        <script type="text/javascript" src="js/jquery.js" ></script>
        <script type="text/javascript" src="js/knockout-2.3.0.js" ></script>
        <script type="text/javascript" src="js/parentModel.js" ></script>
        <script type="text/javascript">
        var page = function(parent) {
            var self = this
 
            self.FirstName  =   ko.observable()
            self.LastName   =   ko.observable()
 
            self.LoadData   =   function(){
                self.FirstName('Muhammad')
                self.LastName('Raheel')
            }
 
            self.HomePageMethod =   function(){
                console.log('I am called from Home page.')
            }
 
            /*  Call LoadData for Initialization    */
            self.LoadData()
        }
 
        $( document ).ready(function() {
            vm.Home(new page(vm))
            ko.applyBindings(vm)
        })
        </script>
    </head>
    <body data-bind='with:Home'>
        <h2 data-bind='text:$root.Title'></h2>
        <h3 data-bind='text:FirstName'></h3>
        <h3 data-bind='text:LastName'></h3>
        <p data-bind='text:"ClickMe",click:HomePageMethod'></p>
        <table>
            <tr>
                <td>Go to Form Page </td>
                <td><a data-bind="click:$root.Navigate.bind($data,'form'),text:'Form'" /></td>
            </tr>
            <tr>
                <td>Go to List Page </td>
                <td><a data-bind="click:$root.Navigate.bind($data,'list'),text:'List'"></a></td>
            </tr>
        </table>
    </body>
</html>

Here is form.html

<!DOCTYPE html>
<html>
    <head>
        <title>Knockout! Parent Child Usage </title>
        <link rel="stylesheet" type="text/css" href="css/style.css" />
        <script type="text/javascript" src="js/jquery.js" ></script>
        <script type="text/javascript" src="js/knockout-2.3.0.js" ></script>
        <script type="text/javascript" src="js/parentModel.js" ></script>
        <script type="text/javascript">
        var page = function(parent) {
            var self = this
 
            self.FirstName  =   ko.observable()
            self.LastName   =   ko.observable()
            self.FullName   =   ko.computed(function(){
                return self.FirstName() +' '+self.LastName()
            })
 
            self.LoadData   =   function(){
                self.FirstName('Muhammad')
                self.LastName('Raheel')
            }
 
            self.FormPageMethod =   function(){
                console.log('I am called from Form page.')
            }
 
            /*  Call LoadData for Initialization    */
            self.LoadData()
        }
 
        $( document ).ready(function() {
            vm.Form(new page(vm))
            ko.applyBindings(vm)
        })
        </script>
    </head>
    <body data-bind='with:Form'>
        <h2 data-bind='text:$root.Title'></h2>
        <input type="text" data-bind='value:FirstName' />
        <br />
        <input type="text" data-bind='value:LastName' />
        <br />
        <h2 data-bind='text:FullName'></h2>
        <p data-bind='text:"ClickMe",click:FormPageMethod'></p>
        <table>
            <tr>
                <td>Go to Home Page </td>
                <td><a data-bind="click:$root.Navigate.bind($data,'home'),text:'Home'"></a></td>
            </tr>
            <tr>
                <td>Go to List Page </td>
                <td><a data-bind="click:$root.Navigate.bind($data,'list'),text:'List'"></a></td>
            </tr>
        </table>
    </body>
</html>

And here is list.html

<!DOCTYPE html>
<html>
    <head>
        <title>Knockout! Parent Child Usage </title>
        <link rel="stylesheet" type="text/css" href="css/style.css" />
        <script type="text/javascript" src="js/jquery.js" ></script>
        <script type="text/javascript" src="js/knockout-2.3.0.js" ></script>
        <script type="text/javascript" src="js/parentModel.js" ></script>
        <script type="text/javascript">
        var page = function(parent) {
            var self = this
 
            self.Users  =   ko.observableArray([
                { Name : 'Raheel',Age:27},
                { Name : 'Saleem',Age:24},
                { Name : 'Aamir',Age:25}
            ])
 
            self.ListPageMethod =   function(){
                console.log('I am called from List page.')
            }
        }
 
        $( document ).ready(function() {
            vm.List(new page(vm))
            ko.applyBindings(vm)
        })
        </script>
    </head>
    <body data-bind='with:List'>
        <h2 data-bind='text:$root.Title'></h2>
 
        <table>
            <thead>
                <tr>
                    <td>Name</td>
                    <td>Age</td>
                </tr>
            </thead>
            <tbody data-bind='foreach:Users'>
                <tr>
                    <td data-bind='text:$data.Name'></td>
                    <td data-bind='text:$data.Age'></td>
                </tr>
            </tbody>
        </table>
        <p data-bind='text:"ClickMe",click:ListPageMethod'></p>
        <table>
            <tr>
                <td>Go to Home Page </td>
                <td><a data-bind="click:$root.Navigate.bind($data,'home'),text:'Home'"></a></td>
            </tr>
            <tr>
                <td>Go to Form Page </td>
                <td><a data-bind="click:$root.Navigate.bind($data,'form'),text:'Form'" /></td>
            </tr>
        </table>
    </body>
</html>

In all the above peges the technique is same. so i am only describing home.html.
Before this we can put a glance on the structure again

/myapp/
    /js/
        /jquery.js
        /knockout-2.3.0.js
        /parentModel.js
    /css/
        /style.css
    /home.html
    /form.html
    /list.html

OK. Here you can see i have added parentModel on every page like this

<script type="text/javascript" src="js/parentModel.js" ></script>

This is because we are going to attach parentModel to every page. As you can see we created a model for home.html and assigned it in parentModel’s Home property which is observable. The other important thing and the magic IS this line

<body data-bind='with:Home'>

With this we are telling home page to use the model we just assigned to Home property. If we skip this step we will be facing ‘Unable to parse binding’. Or we will need to do some workaround. Using with binding will let us use child model’s properties and methods as usual as though we are using a simple model for a page.

Now lets have a look how we can use child methods and properties then we will be seeing how we can use parent properties and methods.

How to Use Child Properties and Methods?

Now as we are using with binding on our body tag it means inside body we can use any property or method of child viewmodel.

Accessing Methods

For accessing child we have 3 choices. Lets say i want to call HomePageMethod which is method of  childmodel.

Use $data keyword

<p data-bind='text:"ClickMe",click:$data.HomePageMethod'></p>

This method of calling childmodel’s methods is not recommended. Because it can create some problem when you are inside a loop.

Start from root

<p data-bind='text:"ClickMe",click:$root.Home().HomePageMethod'></p>

This method is fine but not recommended too. Although it will never produce any problem but to write readable and clean code use $root only for calling parent Model’s method and properties so that you can easily distinguish between parent and child usage.

Or use simple call.

<a data-bind='text:"Console Me too", click:HomePageMethod'></a>

This will give you a neat and clean code to understand and you can easily find that this is a call to child method.

Passing Parameters to Child Method

You can use any method of passing parameters to methods. For this you can see the documentation

Accessing Properties

For accessing properties we have all three choices. I recommend the last one.

<a data-bind='text:FirstName'></a>

How to Use Parent Methods and Properties?

Accessing Parent Methods

Use $root keyword

<a data-bind="click:$root.Navigate.bind($data,'form'),text:'Form'" />

This will let us use parentModel’s Navigate method.

Passing Parameters

Passing parameters method is same just like we did for child methods.

Accessing Properties

To access any property of parent class you can use $root keyword.

<h2 data-bind='text:$root.Title'></h2>

This will display parentModel’s Title property.

Final Thoughts

In the current post we have seen how we can use knockout to define a parent class and all the other classes will be assigned as observable property of parent viewmodel. This structure only shows how you can write code which is common for multiple viewmodels and you don’t want to write it in each viewmodel. Similiarly, Some properties that are common can be defined in parent viewModel and will be available in each viewmodel. So here parent means a class that only contains common code nothing else. Hope this helps. For any question you can give response.