One of all-time most popular articles on this blog is AJAX Cascading Dropdown (actually whole series), which still receives lot of attention even after 3 years.
Not surprising, as this is universal problem that is always present, and any business has some kinds of categories and groups of its own, with more or less levels deep.
Small aside to prevent wasting your time: if you are advanced KnockoutJS user, then you might want to wait for next article of this series, where I will be improving binding from this one to add cascading interactions. This post is more detailed and suited for developers who had only used default bindings that come with KnockoutJS, without many customization.
As browsers advanced, and got closer and closer to a common standard, jQuery became less and less necessary, and other frameworks emerged to address problems of a mess that very often gets produced when many developers try to produce “rich client” UI with jQuery. Hard-coded ids, storing variables in hidden fields to communicate between client and server (and even to communicate between different JavaScript “modules” when there is no real modularity and dependency injection), global functions and variables that are shared between in-line JavaScript and referenced files… you name it. Don’t get me wrong, I am aware that it IS possible to produce something clean and maintainable using jQuery and its plugins, and if you happen to be one of these people who knows how to do it, just think about how easy is to find team of developers who know the same? And even if only benefit of MVVM and data-binding frameworks would be avoiding of copy-pasting selectors all over the place and removing hidden fields, it would be worth learning them, so it is not surprise that they are now so popular for client-side development in JavaScript.
So, these two facts, popularity of my first post about jQuery version of cascading dropdowns, and popularity of Knockout are obviously good clue for me that people are solving same problem today with Knockout, and this is why I decided to create new, updated version of my initial blog post, this time with KnockoutJS. For any of you asking why KnockoutJS and not some other library, my answer is that is first data-binding library I learned couple years back, and even if it is not my favorite anymore, I still like it, it is being used on most of projects I work on, and I believe it won’t go away just yet, so with many people using it and continuing to use it in future, I believe that this article will provide quality content for my readers. And, besides that, I’ll make AngularJS version next :).
So, goal of this series of articles is to implement support for series of dropdowns that are depending on each other (choices avalable in some of them depend in chosen values in others), with items being loaded from remote service, and dependencies between them being managed automatically. All of that should be simple to use and reusable, meaning that no new code needs to be written when you use it 5, 10 or 50 times. To make that work, I will create custom binding. If you never created custom binding, I hope that this article will give you all necessary guidance to be able to create other bindings, depending on your needs.
In this, first article, I’m going to make step-by-step guide on how to implement part of this feature as custom knockout binding, which loads items from server, but without cascading behavior (so multiple dropdowns, no interactions between them), and at the end you have link to source code if you just want to quickly add this binding to your project and (hopefully) solve at least one of your problems. By reading it, you will also find out about my approach to creation of custom bindings. I derived it after years of experience in creating them, and I believe it is good, as it allows you to add one small tweak at a time, and you always know if a line you added breaks something. Obviously, there are always areas to improve, and if you have some tips, feel free to share them in comments. Custom bindings tend to be pieces of code you don’t write every day, so you usually forget how they work, and what are arguments for handlers. Therefore, taking this iterative approach is what I usually do when I see that I need new binding, if I haven’t created one yesterday (so that I remember all this stuff and can skip baby steps).
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.
To reduce overhead for getting some base to work on, I will use ASP.NET MVC5 with Bootstrap 3.1.1 LESS template for Visual Studio. This template gives you already working application with decent css where I can start implementing this (almost) immediately. Only missing prerequisites that I need to add are knockout and ko lite tools:
PM> Install-Package knockoutjs
PM> Install-Package KoLite
To start with, I’m cleaning up index page, adding reference to knockout and activity plug-in from ko lite tools (that we don’t need yet, but when options are loaded with ajax, some form of feedback for user will be necessary), and creating one select to bind with some options:
If you didn’t know, you can hardcode your data when using knockoutjs bindings, and model is here just because I plan to use it, but this already produces dropdown with two options, second being selected:
Now, on to create custom binding. As I want to display some options, that will be loaded using ajax, good place to start is default ‘options’ binding that comes with knockout. For start, you can create your alias for it (pass through call to options binding without changing/adding anything), and then run application to make sure all still works as before. So, I created ‘remoteOptions’ binding and used it instead of ‘options’ to check if all works as expected:
If you don’t know how bindings in Knockout work, here is very short explanation for arguments that are passed to your handler:
- element is DOM element of page that contains data-bind attribute for your binding,
- valueAccessor is function that returns object assigned to your binding in data-bind attribute,
- allBindings is an object that contains all parsed bindings from data-bind attribute.
- bindingContext an object that contains and $data property that represents your current model (i.e. model that you used in applyBindings, or current item in foreach binding, or property that was used in with binding), but also $parent, $parents and $root properties where you can access other parts of your model (traverse up from current node)
For more details, read documentation.
So, my goal is to tell my binding where to load options from and avoid having either options or remote address in my view model. That reduces number of properties and lists in view model, and in many cases has effect of making view model much more like (data) model reducing mapping complexity, or removing mapping completely. So, having that in mind, first step will be to change binding syntax (I also changed html and added some css but that is just markup, important part is in red):
This now passes an object instead of array of options, and to avoid ko options binding being broken with it, I’m changing my custom binding, just a little bit:
As you can see, I’m reading new binding by calling valueAccessor() and at the moment doing nothing except logging its content to console to show you what is in:
{“url”:”categories/get”}
At same time, I created function optionsValueAccessor (actually just moved hard-coded options here) to provide some options that default binding will read and use to display options in select element:
I added another option now, and it can be seen in page:
Ok, that part seems to be simple enough, now to get some real options using ajax. For that, I’m exposing very simple http service in web app to return contents of an enum (in real app you would probably read that from some kind of persistence service):
So, I want to load list from this http service automatically as options in Category dropdown when page is loaded (and binding initialized), and to accomplish that, I want to have minimal amount of coding configuration, and that would be address of endpoint that contains options for dropdown:
remoteOptions:{ url:’/categories/get’ }
That is rather easy to accomplish with knockout, as we can pass reference to an observable array to underlying default options binding which will show options from that observable array, and set its items once ajax request has been completed. Single problem is that we have to make that ajax call in init method of bindingHandler, as they need to be loaded only once, update is triggered every time user selects item from dropdown, and every time update is triggered, we need to pass same observable array to underlying options binding (no need to refresh all items when selecting one of them). I noticed that passing reference from init to update handlers is not simple, as you can’t write to allBindings (well, you can, but it doesn’t get persisted anywhere, next time when update is called, you get new object without your changes), and you cannot write to value of your property in model, as it is supposed to hold value user has selected (having something else defeats purpose of this blog post). Fortunately, value binds to observable, which is a function, and in JavaScript everything is object (including function), so we can simply add a property to that object (observable, function) that will hold reference to observable array with options:
To reduce probability of interference with something else that may be in observable function/object, I tried to give unique name to my property: ‘__remoteDropdown_options’ (if I used something like ‘options’ here, somebody else might used same property name for something else and then if both bindings are used at same time they would read/write different thing in/from same place, and there might be very weird and hard to track down bugs). As you can see, I’m setting it to observable array in init, and firing off an ajax request to load options from url passed in binding. Reference to that same options observable array is returned as result of optionsValueAccessor in update function that initializes dropdown. When ajax request completes, options is filled with result from server, and voila, my dropdown contains these values:
That is kinda cool (especially because with these 10 lines of code you can fill in any dropdown with ajax), but, there is still no trace of “cascading” from title of this post. And, not to mention that in most cases description shown in dropdown will differ from value (i.e. you will have item’s primary key as value, and description as text shown to user). So, let’s add that before trying to add cascading behavior (because that is short and easy, and this post already becomes very long, too long for binding of 10 lines).
First, instead of list of strings, return list of objects that have Id and Text:
That makes controller now return array like this:
But dropdown now shows three [object Object] items instead of text. Fortunately, we are passing through allBindings untouched to knockout’s default options binding, which already has support for this (read documentation for details). To use this feature, I just need to tell knockout what is name of my text property: optionsText:’Text’. Now is right time to add category as property on my view model to be able to show what is selected:
And yes, that is view model. I know that some of you are going to have hard time to accept that I did not define view model in C#, but it is intentional, not only for this sample, it can be avoided completely. You have view model here, and I believe one is enough. Keep it simple. You don’t need it even when you are accepting your domain entity in controller (when you are saving it), you can define model binder to initialize everything that you need, effectively avoiding one layer of view model classes and mappers (mapper does the same your model binder would do, but you need to add code to call it into your controller, and why do that when MVC already has extensibility point for that?) This is a topic for separate post (still in drafts folder), but feel free to comment about it here.
So, with this here, I’m changing markup to reflect latest change in options that are returned from server, and with having observable to store category in view model, I’m going to show it on screen:
As you can see, when using remoteOptions, you can use optionsText as with default options binding to choose property to display in dropdown, and value (selected item from dropdown) is bound to category.
As I did not set optionsValue, knockout will set whole object (with both Id and Text) into category observable, and you can see that from with binding and results below:
These are 3 screenshots of same dropdown with different option selected, as you see, it automatically shows chosen category below.
So, to conclude this first part, I’m going to add another dropdown, with subcategories (that should be filtered by category selected in first dropdown, but not yet). For that I need another http service to get data from, and another html markup for dropdown:
So, for this dropdown, different label, different url, and different property in vew model (I have added subcategory: ko.observable()). That produces:
With this, I’m ending this post, and source code changeset on GitHub is exactly the place where I’m starting next post, in which I will add dependencies between these dropdowns and filter items in “children” based on selected items in “parents”.
P.S. One of additional purposes of this article is also to document/explain feature that I implemented on my latest project, binding which works on same principles as this, and anyone having trouble understanding it can come here for help. Even if I’m sad that my code requires additional explanation, it is still a good way to celebrate successful migration to new domain for my blog.