5 Useful Knockout JS Binding Handlers

Knockout JS is an awesome MVVM framework and is probably my favorite templating library. That being said, there are a couple helpers I add to every project to make it more useful in real work applications. (Note: all markup examples are using bootstrap

Checkbox

iCheck is an excellent cross browser, mobile friendly plugin for standardizing your check boxes and radio buttons. This binding handler takes care of wiring it's events up, you could extend further to allow passing in the theme.

<div class="form-group">
    <div class="checkbox">
        <label class="no-padding">
            <input type="checkbox" data-bind="iChecked: IsReal"> Is it real?
        </label>
    </div>
</div>
ko.bindingHandlers.iChecked = {
    init: function (element, valueAccessor) {
        $(element).iCheck({
            checkboxClass: "icheckbox_flat-blue",
            radioClass: "iradio_flat-blue",
            increaseArea: "20%"
            });
        $(element).on('ifChanged', function () {
            var observable = valueAccessor();
            observable($(element)[0].checked);
        });
    },
    update: function (element, valueAccessor) {
        var value = ko.unwrap(valueAccessor());
        if (value) {
            $(element).iCheck('check');
        } else {
            $(element).iCheck('uncheck');
        }
    }
};

Multiselect

Multiselect is a must have plugin for bootstrap if you use multi-selects, much better than the browser defaults. I also use knockout-validation in my projects, so I added in the markup I tweaked for this so the error messages show better. This binding also pairs well with the minArrayLength validation rule.

<div class="form-group" data-bind="validationElement: SelectedPeople, validationOptions: { insertMessages: false }">
    <label class="control-label">People</label>
    <div>
        <select multiple="multiple" class="form-control" data-bind="multiselect: { config: {}, options: AvailablePeople, selectedOptions: SelectedPeople }, optionsText: 'Text', optionsValue: 'Value'"></select>
    </div>
    <div data-bind="validationMessage: SelectedPeople" class="text-danger"></div>
</div>
ko.bindingHandlers.multiselect = {
    init: function (element, valueAccessor, allBindingAccessors) {
        var options = valueAccessor(),
        defaults = {};
        $.extend(options.config, defaults);
        ko.bindingHandlers.options.update(element, function() { 
            return options.options;
        }, allBindingAccessors);
        ko.bindingHandlers.selectedOptions.init(element, function() {
            return options.selectedOptions;
        }, allBindingAccessors);
        ko.bindingHandlers.selectedOptions.update(element, function () {
            return options.selectedOptions;
        }, allBindingAccessors);
        $(element).multiselect(options.config);
    },
    update: function (element, valueAccessor, allBindingAccessors) {
        var options = valueAccessor();
        ko.bindingHandlers.selectedOptions.update(element, function() { 
            return options.selectedOptions; 
        }, allBindingAccessors);
        $(element).multiselect("refresh");
    }
};

Currency

I have seen several attempts at currency binding handlers, but all of them fall short in one area or another. This one handles everything: input, display, symbols, and 2-way binding regardless of leftovers! This one makes sure the underlying observable stays a number for easy serialization back to your backend (.NET does not cleanup format currency well back into a decimal).

<input type="text" class="form-control" data-bind="currency: Total" />
function formatCurrency (symbol, value, precision) {
    return (value < 0 ? "-" : "") + symbol + Math.abs(value).toFixed(precision).replace(/(\d)(?=(\d{3})+\.)/g, "$1,");
}

function rawNumber (val) {
    return Number(val.replace(/[^\d\.\-]/g, ""));
}

ko.bindingHandlers.currency = {
    symbol: ko.observable("$"),
    init: function (element, valueAccessor, allBindingsAccessor) {
        //only inputs need this, text values don't write back
        if ($(element).is("input") === true) {
            var underlyingObservable = valueAccessor(),
                interceptor = ko.computed({
                read: underlyingObservable,
                write: function (value) {
                    if (value === "") {
                        underlyingObservable(null);
                    } else {
                        underlyingObservable(rawNumber(value));
                    }
                }
            });
            ko.bindingHandlers.value.init(element, function () {
                return interceptor;
            }, allBindingsAccessor);
        }
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        var symbol = ko.unwrap(allBindingsAccessor().symbol !== undefined ? allBindingsAccessor().symbol : ko.bindingHandlers.currency.symbol),
            value = ko.unwrap(valueAccessor());
        if ($(element).is("input") === true) {
            //leave the boxes empty by default
            value = value !== null && value !== undefined && value !== "" ? formatCurrency(symbol, parseFloat(value), 2) : "";
            $(element).val(value);
        } else {
            //text based bindings its nice to see a 0 in place of nothing
            value = value || 0;
            $(element).text(formatCurrency(symbol, parseFloat(value), 2));
        }
    }
};

Percentages

Percentages are similar to currency in that other examples I have seen fall short somewhere. The code is very similar to the currency binding, so it handles everything equally as painlessly.

<input type="text" class="form-control" data-bind="percentage: Discount" />
function formatPercentage (value, precision) {
    return (value < 0 ? "-" : "") + Math.abs(value).toFixed(precision) + "%";
}

function rawNumber (val) {
    return Number(val.replace(/[^\d\.\-]/g, ""));
}

ko.bindingHandlers.percentage = {
    precision: ko.observable(2),
    init: function (element, valueAccessor, allBindingsAccessor) {
        //only inputs need this, text values don't write back
        if ($(element).is("input") === true) {
            var underlyingObservable = valueAccessor(),
                interceptor = ko.computed({
                read: underlyingObservable,
                write: function (value) {
                    if (value === "") {
                        underlyingObservable(null);
                    } else {
                        underlyingObservable(rawNumber(value));
                    }
                }
            });
            ko.bindingHandlers.value.init(element, function () {
                return interceptor;
            }, allBindingsAccessor);
        }
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        var precision = ko.unwrap(allBindingsAccessor().precision !== undefined ? allBindingsAccessor().precision : ko.bindingHandlers.percentage.precision),
            value = ko.unwrap(valueAccessor());
        if ($(element).is("input") === true) {
            //leave the boxes empty by default
            value = value !== null && value !== undefined && value !== "" ? formatPercentage(parseFloat(value), precision) : "";
            $(element).val(value);
        } else {
            //text based bindings its nice to see a 0 in place of nothing
            value = value || 0;
            $(element).text(formatPercentage(parseFloat(value), precision));
        }
    }
};

Date Picker

Bootstrap has 100 different datepicker plugins, but I like this one, so this handler was designed around it's options.

<input type="text" class="form-control" data-bind="date: Birthday" />
ko.bindingHandlers.date = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        //only inputs need this, text values don't write back
        if ($(element).is("input") === true) {
            var options = {
                format: "MM/DD/YYYY",
                defaultDate: ko.unwrap(valueAccessor())
            };
            ko.utils.extend(options, allBindingsAccessor().dateTimePickerOptions);
            var underlyingObservable = valueAccessor(),
                interceptor = ko.computed({
                read: underlyingObservable,
                write: function (value) {
                    underlyingObservable(value);
                }
            });
            $(element).parent().datetimepicker(options).on("dp.change", function (e) {
                if (e.date !== undefined) {
                    var picker = $(this).data("DateTimePicker"),
                        d = picker.getDate();
                    interceptor(d.format(picker.format));
                }
            });
            ko.bindingHandlers.value.init(element, function () {
                return interceptor;
            }, allBindingsAccessor);
        }
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var value = valueAccessor(),
            allBindings = allBindingsAccessor(),
            valueUnwrapped = ko.unwrap(value),
            pattern = allBindings.dateTimePickerOptions && allBindings.dateTimePickerOptions.format ? allBindings.dateTimePickerOptions.format : "MM/DD/YYYY",
            output = "";
        //Date formats: http://momentjs.com/docs/#/displaying/format/
        if (valueUnwrapped !== null && valueUnwrapped !== undefined && valueUnwrapped.length > 0) {
            output = moment(valueUnwrapped).format(pattern);
        }
        if ($(element).is("input") === true) {
            $(element).val(output);
        } else {
            $(element).text(output);
        }
    }
};

And that's the list! If you want to make these handlers validatable with knockout validation, make sure you register them!

ko.validation.makeBindingHandlerValidatable("multiselect");
ko.validation.makeBindingHandlerValidatable("iChecked");
ko.validation.makeBindingHandlerValidatable("currency");
ko.validation.makeBindingHandlerValidatable("percentage");
ko.validation.makeBindingHandlerValidatable("date");