In a project I am working I rolled my own knockoutjs binding for the twitter bootstrap datepicker by @eternicode. I’ve had some “fun” getting the binding to play nice with Chrome and dates in Australia.
My datePicker binding is used to configure a text box for use of eternicode’s datepicker, without having to call it using jquery. It’s also used to synchronise the UI with the knockoutjs ViewModel.
To hook it up in I call the following:
<input name="dateofbirth" type="text" data-bind="datePicker: program.dateOfBirth" placeholder = "dd/mm/yyyy" class="span2" />
which says to bind the html element to the dateOfBirth property on my ViewModel, and configure the element to use eternicode’s datepicker. My custom binding is as follows:
ko.bindingHandlers.datePicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || { format: 'dd/mm/yyyy', autoclose: true };
$(element).datepicker(options);
//when a user changes the date, update the view model
ko.utils.registerEventHandler(element, "changeDate", function (event) {
var value = valueAccessor();
if (ko.isObservable(value)) {
value(event.date);
}
});
ko.utils.registerEventHandler(element, "change", function () {
var widget = $(element).data("datepicker");
var value = valueAccessor();
if (ko.isObservable(value)) {
if (element.value) {
var date = widget.getUTCDate();
value(date);
} else {
value(null);
}
}
});
},
update: function (element, valueAccessor) {
var widget = $(element).data("datepicker");
//when the view model is updated, update the widget
if (widget) {
widget.date = ko.utils.unwrapObservable(valueAccessor());
if (!widget.date) {
return;
}
if (_.isString(widget.date)) {
widget.setDate(moment(widget.date).toDate());
return;
}
widget.setValue();
}
}
};
Now the background is out of the way, the problem…
I am using the knockout validation library by ericmbarnard, which works great, except that when the value of my dateOfBirth property (or any property that is data bound via datePicker), I do not ever get the UI notification that is presented with the other controls. I wanted a span element to be present after the datePicker control when its value is invalid.
After some digging I came to the following lines in the validation library source:
// override for KO's default 'value' binding
(function () {
var init = ko.bindingHandlers['value'].init;
ko.bindingHandlers['value'].init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
init(element, valueAccessor, allBindingsAccessor);
return ko.bindingHandlers['validationCore'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
};
}());
What the above is doing is taking the default init method that ships with knockout’s value binding and adding a call to the validationcore at the end. I checked my own code and when I bind to value, rather than datePicker, I get the desired message. It seemed to me that I needed to hook into the validation library from the init method of my custom binding and after looking at knockouts value binding implementation and the above code from the validation library, it seems using a modification of the above snippet would do this trick.
With this in mind, here is my code which now gives me the functionality I desired:
(function() {
var init = ko.bindingHandlers['datePicker'].init;
ko.bindingHandlers['datePicker'].init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
init(element, valueAccessor, allBindingsAccessor);
return ko.bindingHandlers['validationCore'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
};
}());
Pretty simple. All I needed to do was copy the existing code and replace ‘value’ with ‘datePicker’ (my custom binding). Oh yes, please remember to put any code NOT in the knockout.validation.debug.js source, or it’ll get clobbered when you update to the latest version – and it won’t exist if you use the minified version in your release build.