Remake of cascading dropdowns with KnockoutJS: Part 2 – cascading behavior

This post is second part of series started here.

Starting point is this changeset on GitHub.

If you don’t want to go through all these steps manually, you can download finished code sample produced in this post from this changeset on GitHub and fiddle with it while reading this post.

In previous post, I created custom Knockout binding, named remoteOptions that accepts url of http service as parameter and loads options from that service instead from view model. That can help to reduce amount of code responsible to build reference data that needs to be available to user to choose from, as with this binding you have one piece of ~10 lines of code for all dropdowns that always have same content. This does not get me very far, as, at least on projects where I worked on, most of the dropdowns were dependent on something, i.e. some attribute of currently viewed set of information, or some other information that user already entered in current form (i.e. when user chooses Female gender, you might want to remove Mr. from title dropdown etc).

Just for reference, I’m adding current state of custom binding, where I left it on GitHub:

Screenshot 2014-05-13-20-26-59-4598210

As there are two dropdowns (category and subcategory), I want to make second one depend on first (load it when user chooses value in first based on that value). For that, I will change binding so that it tells what parameters I want to pass to my service on configured url:

Screenshot 2014-05-13-22-30-59-3084533

I want to pass id of selected category to my service as parameter named ‘category’ and I want to tell that to my binding with parameters:{ category: categoryId }. Now, I have to make sure that my view model has this categoryId defined and pointing to Id of category:

Screenshot 2014-05-13-23-43-46-7844669

Note that this view model is just an JavaScript object made on the fly for this demo, if you are working on a big application that may have large (or even composite) view models, maybe with inheritance, please read this great article about prototypes in JavaScript which will help you better understand how to use new keyword in JavaScript and properly build your models.

Back to cascading dropdowns, now I have Id value of currently selected category in categoryId property (computed, so that it will automatically update after every change in first dropdown). If I go now to my binding and inspect its value when it is processing this dropdown by setting breakpoint after binding is assigned from valueAccessor() (if you set breakpoint, let it execute first time when is processing first dropdown), I see that I have category here as observable:

Screenshot 2014-05-13-22-38-09-0020760

dependentObservable is function name that is remnant of pre-2.0 version of Knockout, and now is same as computedObservable.

To make my subcategory options dependent on category, I will create subscription that is refreshed when any of parameters is refreshed, and then use it to reload data from server when that happens by subscribing refreshOptions function to it in init binding handler:

Screenshot 2014-05-14-23-25-59-1980099

The catch here is in when any of parameters is refreshed, as parameters are passed from binding as names of properties on view model, and binding contains references to their observables. That means that parametersChangeListener computed observable will automatically get new value and notify its subscribers whenever user causes change to any of subscribed observables (in this case categoryId which is indirectly updated by changing selection in category dropdown).

Return value of parametersChangeListener will always be plain JSON object, because every of observables is read with ko.unwrap, which will also support any non-observable variables without problems. That makes it suitable for direct pass to getJSON (you might want to use post here instead, for example to prevent caching response, or to have your http endpoint better secured).

If I try to run it now, I can see that subcategories are filtered based on selected category:

Screenshot 2014-05-14 23.37.33 Screenshot 2014-05-14 23.37.40

Best of all is that this subscription is generated automatically, from whatever is configured in binding, and you can have as many linked dropdowns as you want, they refresh automatically and track changes on their parents.

Every time when I change category, binding makes new ajax request and loads corresponding items for dependent dropdown:

Screenshot 2014-05-14-23-35-38-0394038

Screenshot 2014-05-14-23-35-47-3420418

So, there is cascading part of behavior. Source code is here.

I’m a little busy right now with search for new apartment and relocation, but I will try to continue this series and further improve what we have here.

Till next time!



Leave a Reply