KnockOutJs custom bindings and data-attributes in a dropdown

At the day-job, we (finally!) get to use Knockout in our old-school WebForms application, albeit on one page to begin with (which is fine; one step at a time). If you're not familiar with Knockout, check out their site and great documentation. If you have a Pluralsight account, even better! They have an excellent course that will get you started very quickly.

Anyway, I came across a situation where I needed to bind a property of my viewmodel to the selected option of a dropdownlist. But not to the value of this selected option, but to a data-attribute. A custom binding is the solution, which could give you the following code:

<select data-bind="value: cityId, selectedDataCountry: country">  
    <option data-country="Belgium" value="1">Brussels</option>
    <option data-country="United Kingdom" value="2">London</option>
    <option data-country="France" value="3">Paris</option>
</select>
So, if you selected 'Paris', your viewmodel would be updated by setting its country property to 'France'. However, your custom binding would specifically have to search for the 'data-country' attribute. To do it a bit more generic, I wanted to be able to do something like the [event binding](http://knockoutjs.com/documentation/event-binding.html) in Knockout:
<select data-bind="value: cityId, selectedDataAttribute: { bind: 'data-country', to: 'country' }"></select>
A look into how the event binding works, and a train trip later, I had it working! The custom binding looks like this:
ko.bindingHandlers.selectedDataAttribute = {  
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var dataAttribute = ko.utils.unwrapObservable(valueAccessor().bind);
        var viewModelProperty = valueAccessor().to;
        var dataAttributeValue = $(element).find(':selected').attr(dataAttribute);
        viewModel[viewModelProperty](dataAttributeValue);

        $(element).change(function() {
            var dataAttribute = ko.utils.unwrapObservable(valueAccessor().bind);
            var viewModelProperty = valueAccessor().to;
            var dataAttributeValue = $(element).find(':selected').attr(dataAttribute);
            viewModel[viewModelProperty](dataAttributeValue);
        });
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var dataAttribute = ko.utils.unwrapObservable(valueAccessor().bind);
        var viewModelProperty = valueAccessor().to;
        var value = viewModel[viewModelProperty]();
        $("option", element).filter(function(option) { return $(option).attr(dataAttribute) === value; }).prop("selected", "selected");
    }
}

What it does is:

  • on init, get the name of the attribute, get the name of the observable on your viewmodel, get the value of the attribute, and set the value of the viewmodels property
  • do the same thing when the selected item of the dropdown changes
  • on update (when the viewmodel updates), get the name of the attribute, get the value of the viewmodel's observable, set the selected option in the dropdown.Come to think of it, this would work with any attribute. Also, it relies on jQuery for simplicity, but you could do it in pure javascript.

Now, this custom binding allows us to use this markup:

<select data-bind="value: cityId, selectedDataAttribute: { bind: 'data-country', to: 'country' }">  
    <option data-country="Belgium" value="1">Brussels</option>
    <option data-country="United Kingdom" value="2">London</option>
    <option data-country="France" value="3">Paris</option>
</select>

Now we can reuse the selectedDataAttribute binding with different data-attributes. You can find the code in my GitHub repository.