Search code examples
knockout.jscascadingdropdowndynamic-list

How to create a cascading select dropdown in a dynamic list using Knockout MVC


I followed the Knockout tutorials some time ago of which one of them http://learn.knockoutjs.com/#/?tutorial=collections

details how to creating lists and collections. However I want to create a cascading drop down select within the list.

My question would have been can you create a cascading drop down in a dynamic list like this using knockout?

As it happens I've actually managed to solve the question after a couple of hours of looking into it so will add it here as an answer as I think it could be useful for someone. Maybe there are better ways of doing it?


Solution

  • It would work, but I would just add one thing: small caching. Basically, once you've loaded the meals available for a given meal you could create a property in your meal object to store them. That way, subsequent calls may know that these meals have already been loaded. I've created an observable array for that, like so:

    Given this function which simulates retrieving data from the server:

    var mealTypesByKey = [];
    mealTypesByKey[1] = [{ mealName: "Vegemite Sandwich", price: 4.00, mealType: 1},
                         { mealName: "Cheese Sandwich", price: 34.95,mealType: 2 },
                         { mealName: "Jam Sandwich", price: 290, mealType: 3 } ];
    mealTypesByKey[2] = [{ mealName: "Standard (Ham)", price: 15, mealType: 1},        
                         { mealName: "Chicken Wrap (Possibly rat)", price: 15, mealType: 1} ];
    mealTypesByKey[3] = [{ mealName: "Premium (lobster)", price: 34.95,mealType: 2 },
                         { mealName: "Ultimate (whole zebra)", price: 290, mealType: 3 } ];
    
    function serverGetMealsForType(key) {
        return mealTypesByKey[key];
    }
    

    You can define the following subscribable function:

    self.mealType.subscribe(function(newMealType) {
        if(!newMealType.meals) {
            newMealType.meals = ko.observableArray([]);
            newMealType.meals(serverGetMealsForType(newMealType.key));
            console.log("meals loaded");
        } else {
            console.log("meals already available for meal type " + newMealType.key);
        }
    });
    

    And that way, the dynamic list is recreated properly with the given binding:

    <td><select data-bind="options: mealType().meals, value: meal, optionsText: 'mealName'"></select></td>
    

    This is a common and easy technique to avoid unneeded server calls.

    Edit: forgot to add the fiddle I've forked.