ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)


Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

For this part we will use Knockout.ks:

Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model. Any time you have sections of UI that update dynamically (e.g., changing depending on the user’s actions or when an external data source changes), KO can help you implement it more simply and maintainably. (see the References section).

Before reading further it is better to read the Knockout.js documentation + demos (2-3 hours read) as the things can get really confusing.

We will reuse the controller from Part 5.1 and add a new action called KnockOutJs:

public virtual ViewResult KnockOutJs( )
{
    return View( MVC.CascadingDropDownLists.DropDownjQueryAjaxPost.Views.KnockoutJs );
}

And of course the KnockoutJs.cshtml:

<fieldset>
    <legend>Continents</legend>
    <select id="continents" 
            data-bind='
                options: continents, 
                optionsValue : "Id", 
                optionsText: "Name", 
                optionsCaption: "[Please select a continent]", 
                value: selectedContinent'>
    </select>
</fieldset>
<fieldset>
    <legend>Countries</legend>
    <div>
        <select id="countries" 
                data-bind='
                    options: selectedContinent() ? countries : null, 
                    optionsValue : "Id", 
                    optionsText: "Name", 
                    optionsCaption: "[Please select a country]", 
                    value: selectedCountry,
                    visible: (countries() && countries().length > 0)'>
        </select>
        <span data-bind='
            template: {name: "noInfoTemplate"},
            visible: !(countries() && countries().length > 0)'>
        </span>
    </div>
</fieldset>
<fieldset>
    <legend>Cities</legend>
    <div>
        <table data-bind='visible: (cities() && cities().length > 0 )'>
            <thead>
                <tr>
                    <th>
                        Name
                    </th>
                    <th>
                        Population
                    </th>
                </tr>
            </thead>
            <tbody data-bind='template: {name: "citiesTemplate", foreach: cities}'>
            </tbody>
        </table>
        <span data-bind='
            template: {name: "noInfoTemplate"},
            visible: !(cities() && cities().length > 0 )'>
        </span>
    </div>
</fieldset>
@DateTime.Now.ToString( "dd/MM/yyyy HH:mm:ss:fff" )
<script type="text/html" id="citiesTemplate">
    <tr>
        <td>${Name}</td>
        <td align='right'>${Population}</td>
    </tr>
</script>
<script type="text/html" id="noInfoTemplate">
    No Information Available
</script>

Compared to Part 5.1 and 5.2 there is a lot of code but the real power of Knockout.js is when we have a very complex view. Knockout.js introduces an attribute called “data-bind” that binds the html tag to a JavaScript view model, for short.

For the continents dropdown list we have the following (in exact order of the properties):

  • Get the options from the model property called continents
  • The option value will be filled from the Id property of each Continent item
  • The option text (what we see) will be filled from the Name property of each Continent item
  • The first item presented to the use, the option caption, is set to “[Please select a continent]” (the value for this option will be undefined or null)
  • The value of the selected Continent will go the selectedContinent property from the view model

For the countries dropdown list we have the following (in exact order of the properties):

  • Get the options from the model property called countries but only if there is a continent selected
  • The option value will be filled from the Id property of each Country item
  • The option text (what we see) will be filled from the Name property of each Country item
  • The first item presented to the use, the option caption, is set to “[Please select a country]” (the value for this option will be undefined or null)
  • The value of the selected Country will go the selectedCountry property from the view model
  • The countries dropdown list will only be visible if the countries property is not null and not empty (visible expects a value that will evaluate to true/false, 1/0, null, undefined)

For the cities we have the following:

  • The cities table is only visible if the cities property is not null and not empty
  • The cities table body gets its data (the rows) from a template called citiesTemplate which is called for each City in the cities property. We could use the {{each}} template tag directly in the template but in this way if a City is added to the cities array it will be automatically displayed.

There are also 2 span tags visible if the countries and cities table are not visible. The span tags get their inner html form the noInfoTemplate.

Now let’s see how the view model looks. Here is the complete script used by this view:

<script type='text/javascript'>
    $(document).ready(function () {
        var atlas = function () {
            this.selectedContinent = ko.observable();
            this.selectedCountry = ko.observable();
            this.continents = ko.observableArray();
            this.countries = ko.observableArray();
            this.cities = ko.observableArray();
            // Whenever the continent changes, reset the country selection
            this.selectedContinent.subscribe(function (continentId) {
                this.selectedCountry(undefined);
                this.countries(undefined);
                if (continentId != null) {
                    $.ajax({
                        url: '@Url.Action( MVC.CascadingDropDownLists.DropDownjQueryAjaxPost.GetCountries( ) )',
                        data: { continentId: continentId },
                        type: 'GET',
                        success: function (data) {
                            atlasViewModel.countries(data);
                        }
                    });
                }
            } .bind(this));
            this.selectedCountry.subscribe(function (countryId) {
                this.cities(undefined);
                if (countryId != null) {
                    $.ajax({
                        url: '@Url.Action( MVC.CascadingDropDownLists.DropDownjQueryAjaxPost.GetCities( ) )',
                        data: { countryId: countryId },
                        type: 'GET',
                        success: function (data) {
                            atlasViewModel.cities(data);
                        }
                    });
                }
            } .bind(this));
        };
        var atlasViewModel = new atlas();
        ko.applyBindings(atlasViewModel);
        //Load the continents
        $.ajax({
            url: '@Url.Action( MVC.CascadingDropDownLists.DropDownjQueryAjaxPost.GetContinents( ) )',
            type: 'GET',
            success: function (data) {
                atlasViewModel.continents(data);
            }
        });
    });
</script>

Another important piece of the Knockout.js library are the observable properties. When the observable properties change the tags with data-bind attribute will be automatically updated . We can also subscribe to the modification of those properties (as we did for the selectedContinent and selectedCountry properties). Knockout.js is activated by calling the applyBindings method with the view model passed as the argument.

The above code does the following:

  • When the document is ready we make an Ajax request for the continents. The continents property of the view model is updated and it triggers the UI update (if there are binding to that property).
  • When we select a continent the selectedContinent property is updated (through the data-bind properties)
  • Because we have a subscription for the changing of the selectedContinent property our function will be called.
  • The function will clear the selectedCountry and the cities array (again triggering the UI update)
  • If the selected continent is not null (in other words if we haven’t selected the [Please select a continent] option) we make an Ajax request for the countries based on the selected continent id and the countries property is updated with the data received from the server (triggering an UI update).

The function for the selectedCountry changed event is similar as the selectedContinent one.

See it in action

Cascading Dropdown Lists - Knockout.js

The code can be improved by adding 2 dependent observables called countriesVisible and citiesVisible that should be used instead of (countries() && countries().length > 0) statement. Dependent observables depend on the other properties (they are basically function that return a result based on the other properties). For those reading the documentation should be easy to implement.

References

Knockout.js – Documentation | Knockout.js - Samples

Download

Download code

 

Published Sunday, April 17, 2011 9:46 PM by Radu Enuca

Comments

# re: ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

Friday, July 01, 2011 5:37 AM by prateeksasanker.net

I have used the same concept as described here. But I am not binding with the Id of the object. Due to this <option value=""> is set in every options. How do I set the value of the option attibutes??? Plz help.

# re: ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

Friday, July 01, 2011 6:12 AM by Radu Enuca

@prateeksasanker.net you can remove the optionsCaption property. That is the one that generates the empty <option value="">. It's purpose is to give the user a visual hint. In my sample everything is reset if you select that option but you can choose to do nothing by moving

this.selectedCountry(undefined);

this.countries(undefined);

in an else brach.

# re: ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

Friday, July 01, 2011 6:51 AM by prateeksasanker.net

Thnx for the reply Radu Enuca. But my problem is not that. Here is the code of my select tags. constituencies is a collection of constituency. I am not passing the id to populate the cascaded drop downs like u have done. I am passing the object constituency which when selected populates the districts and so on

<select  data-bind='options: constituencies,  optionsText: "AcName", optionsCaption: "...Select...", value: constituency, valueUpdate: "change"'  name="D1">

<select   data-bind='enable: constituency , options: constituency() ? constituency().MasterDistricts: null , optionsText: "DistrictName",   value: district, valueUpdate: "change"' />

<select  data-bind='enable: district, options: district() ? district().MasterSubDivisions: null, optionsText: "SubDivisionName",  optionsCaption: "...Select...", value: subdivision, valueUpdate: "change"' />

My problem now is that all the  options of all the dropdowns contains null for the value attribute. E.g.

<option value=" ">--select--</option>

<option value=" ">value1</option>

<option value=" ">value2</option>

<option value=" ">value3</option>

As a result my client side validation throws validation errors even when a something is selected. I want to set the value attributes of options explicitly. Hope you understand what I want to say. Can you help me?

# re: ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

Friday, July 01, 2011 9:05 AM by Radu Enuca

@prateeksasanker.net: In my sample the value is set by using optionsValue : "Id". I see that you have optionsText: "AcName" but no optionsValue.

# re: ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

Saturday, July 02, 2011 2:17 AM by prateeksasanker.net

Yeah I tried using optionsValue: "Id" but if I do so then my cascaded drop downs are not being populated. Thats the problem. Can I set the options value id with any other method ?

# re: ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

Saturday, July 02, 2011 5:29 AM by Radu Enuca

@prateeksasanker.net: Do you have an Id property? Maybe it has another name. optionsValue: "[Property you want to use a value]". Try to see the javascript error (Ctrl+Shift+J in FireFox will open the error console)

# re: ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

Monday, July 04, 2011 2:25 AM by prateeksasanker.net

thanks Radu Enuca. yeah I told wat you mentioned but no javascript error. Can you help me on copying the selected item of dropdownlist to a textbox ?

# re: ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

Friday, February 24, 2012 4:14 PM by Malcolmn Frexner

Nice sample! Worked well for me.

The only part I did not get is why you used

atlasViewModel.countries(data);

and not

this.countries(data);

Using this.countries(data); worked for me. Is there some reason for using atlasViewModel.cities(data)?

# re: ASP.NET MVC–Cascading Dropdown Lists Tutorial–Part 5.3: Cascading using jQuery.ajax() ($.ajax() and Knockout.js)

Monday, February 27, 2012 1:28 PM by Radu Enuca

@Malcolmn Frexner: I used separate function for the callbacks in the beginning

Leave a Comment

(required) 
(required) 
(optional)
(required)