knockoutjs databind with jquery-ui datepicker

I'm using a jQuery UI datepicker. The HTML input field behind it is currently hooked up to KnockoutJS as a dependentObservable, but when its value is set in the viewmodel, the datepicker loses its format.

How should I do this and not lose the format? I would like the viewModel not to know that it is a jQuery datepicker.


You could write a custom binding that sets the date in the field using the datepicker APIs and also sets the value of your observable by reading the date properly.

The custom binding might look like:

ko.bindingHandlers.datepicker = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var options = allBindingsAccessor().datepickerOptions || {},
            $el = $(element);

        //initialize datepicker with some optional options
        $el.datepicker(options);

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function() {
            var observable = valueAccessor();
            observable($el.datepicker("getDate"));
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $el.datepicker("destroy");
        });

    },
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            $el = $(element),
            current = $el.datepicker("getDate");

        if (value - current !== 0) {
            $el.datepicker("setDate", value);   
        }
    }
};

You would use it like:

<input data-bind="datepicker: myDate, datepickerOptions: { minDate: new Date() }" />

The datepickeroptions would be optional and could include anything that you want to pass into the datepicker() call.

Also, this assumes that you are using an observable for the date. The binding has to do a little more work if you want to do a one-way binding with a non-observable, but that is unlikely.

Sample here: http://jsfiddle.net/rniemeyer/NAgNV/


I had to make a slight edit to RP Niemeyer's code to work in my code using the dateFormat option, replacing

$(element).datepicker("getDate")

With

$(element).val()

So the formatted version of the date was passed around correctly under the hood.


I've been using RP Niemeyer's code marked as the answer above, but since I've been using it I've made a few small changes to it. I thought I would post here. Maybe it will help others. It's pretty much the same, the only difference is that if the element has a value when the page loads then it will retain its value. Also, I made $elem a variable so that there will be less processing of $(element) that jQuery will have to do.

ko.bindingHandlers['jqDatePicker'] = {
    'init': function(element, valueAccessor, allBindingsAccessor) {
        /* Initialize datepicker with some optional options */
        var options = allBindingsAccessor().jqDatePickerOptions || {},
            prop = valueAccessor(),
            $elem = $(element);

        prop($elem.val());

        $elem.datepicker(options);

        /* Handle the field changing */
        ko.utils.registerEventHandler(element, "change", function () {
            prop($elem.datepicker("getDate"));
        });

        /* Handle disposal (if KO removes by the template binding) */
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $elem.datepicker("destroy");
        });
    },
    'update': function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            $elem = $(element),
            current = $elem.datepicker("getDate");

        if (value - current !== 0) {
            $elem.datepicker("setDate", value);
        }
    }
};

Here is what worked for my particular set of circumstances. I'm running a new enough version of MVC, that the default datetime serializer renders at ISO 8601 (see On the nightmare that is JSON Dates. Plus, JSON.NET and ASP.NET Web API). My bindings do not write directly to the date picker, but instead to an input tag's "value" attribute.

Also, of note, I'm using date.js

ko.bindingHandlers.dateValue = {
    update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        var value = valueAccessor(),
            allBindings = allBindingsAccessor();
        var valueUnwrapped = ko.utils.unwrapObservable(value);
        var pattern = allBindings.datePattern || 'MM/dd/yyyy';
        var date = Date.parse(valueUnwrapped)
        $(element).val(date.toString(pattern));
    },

    init: function(element, valueAccessor, allBindingsAccessor) {
        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            var date = Date.parse($(element).val());
            observable(date.toString("yyyy-MM-ddThh:mm:ss"));
        });
    }
}

Binding looks like this:

<input class="date" type="text" data-bind="dateValue: SomeViewModelDate" />

And the JavaScript code to turn on the datepicker:

$(document).ready(function () {
    $('.date').datepicker({ dateFormat: "mm/dd/yy" });
});

The datepicker samples above change the format of the date in the viewmodel from WCF format to the JavaScript date format when the user selects a new date from the datepicker control.

In my case, I was passing the date back to a WCF service, and it would not accept a deserialized JavaScript date, it needed the date in the WCF format. I modified the above script so that it stores the date in the viewmodel in WCF format so that it can be sent back to the server in that format.

ko.bindingHandlers.datepicker = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        //Initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {};
        $(element).datepicker(options);
        //Handle the field changing
        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            // observable($(element).datepicker("getDate"));
            // store the date in 'WCF String format"
            var tempdate=$(element).datepicker("getDate");
            var tempdatestr="/Date("+tempdate.getTime()+")/";
            observable(tempdatestr);
        });
        //Handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).datepicker("destroy");
        });
    },
    update: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        //Handle date data coming via JSON from Microsoft
        if (String(value).indexOf('/Date(') == 0) {
            value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
        }
        current = $(element).datepicker("getDate");
        if (value - current !== 0) {
            $(element).datepicker("setDate", value);
        }
    }
};