KnockoutJS Enable binding in a Grid - CodeProject

:

Introduction

This post talks about how to enable/disable controls within a column in a grid when a dropdown value changes in another column using KnockoutJs library. The assumption is the reader is aware of the basics of KnockoutJS.

The snapshot above shows what we are trying to achieve. There is a column called "IsTaxApplicable", which when selected to "Yes", will make the Tax Amount enabled, so that the user can go ahead and fill in some information. Similarly, when the user selects "No" in the "IsTaxApplicable" column, the Tax Amount becomes disabled. Also, the Total Amount would be a computed column, which is nothing but the Amount Column + Tax Amount.

Background

Basic Introduction about KnockoutJS:

KnockoutJS is a client side JavaScript library which implements the MVVM pattern. There is an excellent article written on MVVM pattern - http://www.codeproject.com/Articles/100175/Model-View-ViewModel-MVVM-Explained

KnockoutJS is built on three core features:

  1. Observables and Dependency Tracking
  2. Declarative Bindings
  3. Templating

Observables are special JavaScript that notify subscribers about changes and can automatically detect dependencies. Declarative Bindings are to link View Model to the View.

For more information regarding KnockOutJs, check out http://knockoutjs.com/.

There is an excellent Plural Sight Course as well at http://www.pluralsight.com/courses/knockout-mvvm.

Let’s Begin

This sample is created using ASP.NET MVC application, you can create this in any web application.

Open Visual Studio and create an ASP.NET MVC Empty Application. Create a Home Controller within the Controller Folder. Create a folder inside "View" folder named "Home". Right Click and create a view "index.cshtml"

Open Package Manager Console and run the following command to install the KnockoutJS library.

PM> Install-Package knockoutjs

This will bring the KnockoutJS library inside the Scripts folder.

Open the Layout.cshtml and remove all the references for the @scripts and place the following code:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>KnockoutJs Sandbox</title>
    <script src="~/Scripts/jquery-1.7.1.min.js"></script>
    <script src="~/Scripts/knockout-3.3.0.js"></script>
</head>
<body>
    @RenderBody()    
</body>
</html>

Let us a create a simple table inside the index.cshtml, which has 4 columns Amount, IsTaxAppicable which will have the options (Yes, No), TaxAmount (which needs to be disabled, when the TaxApplicable is chosen as No), Total Amount (which needs to be computed -> which is Amount + TaxAmount).

<table style="width: 50%">
    <thead>
        <tr>
            <th>Amount</th>
            <th>IsTaxApplicable</th>
            <th>Tax Amount</th>
            <th>Total Amount</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <input type="text" /></td>
            <td>
                <select></select></td>
            <td>
                <input type="text" /></td>
            <td>
                <input type="text"/></td>
        </tr>
    </tbody>
</table>

Once this is done, we need to create a View Model. The following are the variables (observables) that need to be created.

  • Amount Observable
  • Tax Amount Observable
  • Tax Options List (Yes, No) Observable
  • Event to handle the Dropdown Change Observable
  • Enable / Disable Observable
<script type="text/javascript">
    $(function () {
        var TaxOptionsList = [{ "Code": "Y", "Value": "Yes" }, 
        { "Code": "N", "Value": "No" }];
 
        var Sale = function () {
 
            var self = this;
 
            self.Amount = ko.observable(0);
 
            self.TaxAmount = ko.observable(0);
        
        //set the isTaxEnabled to false, so that the control is disabled to begin with
            self.isTaxEnabled = ko.observable(false);
 
            self.TaxOptions = ko.observableArray(TaxOptionsList);
 
            self.OptionSelected = ko.observable();
 
            self.TaxOptionSelect = ko.computed({
                read: function () { return self.OptionSelected },
                write: function (OptionSelected) {
                    if (OptionSelected == "Y") {
                        self.isTaxEnabled(true);
                    }
                    else if (OptionSelected == "N") {
                        self.isTaxEnabled(false);
                    }
                }
            });
 
            self.TotalAmount = ko.computed(function () {
                return Number(self.Amount()) + Number(self.TaxAmount());
            }); 
        };
 
        var viewModel = function () {
            var self = this;
            self.SaleCollection = ko.observableArray([new Sale()]);
 
            self.Addclick = function () {
                self.SaleCollection.push(new Sale());
            }; 
        };
 
        ko.applyBindings(new viewModel());
    });
 
</script>

TaxOptionSelect is the event which handles the Dropdown Change event, on write event, the OptionSelected is checked to see if the code is "Y", then the isTaxEnabled -> observable is set to true, else it is set to false. In order to hold the selected values, a observable OptionSelected is created.

Total Amount is the computed observable, which is simply just add Amount and Tax Amount. The other code is self-explanatory.

Now let us wire the View Model to the view.

<!-- Add the click event and invoke the Addclick Event added to the view Model -->
<button data-bind="click:Addclick">Add</button>
 
    <table style="width: 50%">
        <thead>
            <tr>
                <th>Amount</th>
                <th>IsTaxApplicable</th>
                <th>Tax Amount</th>
                <th>Total Amount</th>
            </tr>
        </thead>
        <!—Iterate through the saleCollection so that whenever a record gets added 

        it gets shown on to the table -->
        <tbody data-bind="foreach:SaleCollection">
            <tr>
                <td>
                    <input type="text" data-bind="value:Amount" /></td>
                <td>
                    <!-- The options is set to the List of Options which is Tax Options, the OptionsValue (Value Member) set to return the Code, and OptionsText(Display Member) which is set to return the Code, and on the Value Event is set to the TaxOptionSelect, which is the Event. -->
                    <select data-bind="options: TaxOptions, optionsCaption: 'Is Tax Applicable..', 
                    optionsValue: function(item) { return item.Code; }, optionsText: function(item) 
                    { return item.Value; }, value:TaxOptionSelect, 
                    valueUpdate:'change'"></select></td>
                <td>
               <!-- isTaxEnabled is bound to the enable binding, which means, when set to false, would be disabled and when set to true, which will enable the control -->
                    <input type="text" data-bind="enable:isTaxEnabled, value:TaxAmount" /></td>
                <td>
                    <input type="text" data-bind="value:TotalAmount" /></td>
            </tr>
        </tbody>
    </table>

This should do and we would get the result as shown in the figure.