23

This question is similar to knockoutjs databind with jquery-ui datepicker, but instead of the jQueryUI datepicker, I would like to use one of the Bootstrap datepickers.

The API for the Bootstrap datepicker is different from jquery-ui, and I am having some trouble wrapping my head around making it work with knockout.js. I have created a jsFiddle to try it out.

It seems like the Bootstrap datepicker could be much simpler to use because it does not independently store the date. However, I would like to know how whether the jsFiddle is the appropriate way to use the Bootstrap datepicker widget with knockout.js i.e.

ko.bindingHandlers.datepicker = {
    init: function(element, valueAccessor, allBindingsAccessor) {
      //initialize datepicker with some optional options
      var options = allBindingsAccessor().datepickerOptions || {};
      $(element).datepicker(options);

      ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $(element).datepicker("destroy");
        });
    },
    update: function(element, valueAccessor) {
    }
};
Community
  • 1
  • 1
Brian M. Hunt
  • 81,008
  • 74
  • 230
  • 343

5 Answers5

41

Here is a sample of how you could accomplish this with the datepicker that you are using:

ko.bindingHandlers.datepicker = {
    init: function(element, valueAccessor, allBindingsAccessor) {
      //initialize datepicker with some optional options
      var options = allBindingsAccessor().datepickerOptions || {};
      $(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);
             }                
      });
    },
    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) {
                widget.setValue();            
            }
        }
    }
};

It did not look like there was any destroy functionality, so I removed that piece. This handles the widgets changeDate event to update the view model, when a user changes the date. The update function handles when the view model is changed to update the widget.

If you want to bind the value to a non-observable, then it would take a little more code. Let me know if that is something that you need to support.

http://jsfiddle.net/rniemeyer/KLpq7/

Brian M. Hunt
  • 81,008
  • 74
  • 230
  • 343
RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • 2
    Here is a [jsFiddle](http://jsfiddle.net/bmh_ca/uQcCx/2/) that has more amenable string->date conversion when the field is updated. This still has the caveat that only changes through the widget are registered; making changes to the input field does not update the model. – Brian M. Hunt Jun 21 '12 at 01:05
  • 2
    Here is an update to bind against the `change` event to respond to changes to the input field: http://jsfiddle.net/rniemeyer/uQcCx/3/ – RP Niemeyer Jun 21 '12 at 03:07
  • JsFiddles above doesn't seems to work anymore, have you make some changes since ?, tested chrome and firefox, datepicker doesn' t show up anymore. Cheers – dtjmsy Mar 19 '13 at 08:46
  • I figure it out, The link to the external debug knockoutjs doesn't work anymore – dtjmsy Mar 19 '13 at 09:00
  • @RPNiemeyer I ended up combining your answer from http://stackoverflow.com/questions/6612705/knockout-with-jquery-ui-datepicker with the answer here to solve a problem. Really brilliant answers, thank you so much. – Tom Studee Mar 19 '13 at 16:00
  • 2
    I notice that this example no longer works in the sense that the initial date of the date picker is not the date in the text box. This is the very bug that I am trying to fix in my own code. Any ideas? – voam Apr 15 '13 at 21:32
  • This code will not work if you gonna use another date-format than the current locale. – Fredrik C Jul 09 '13 at 14:39
  • about destroying, you simply need at the end of init to add the appropriate callback: ko.utils.domNodeDisposal.addDisposeCallback(element, function() { // This will be called when the element is removed by Knockout or // if some other part of your code calls ko.removeNode(element) $el.myWidget("destroy"); }); – Demetris Leptos Sep 16 '16 at 06:14
  • @DemetrisLeptos - Thanks. The original comment in the answer was because at least at that time the datepicker had no destroy functionality, so I removed the disposal code. Perhaps that has changed in jQuery UI. – RP Niemeyer Sep 17 '16 at 20:01
7

my current version is a mix between the already shown solutions:

ko.bindingHandlers.datepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {

    var unwrap = ko.utils.unwrapObservable;
    var dataSource = valueAccessor();
    var binding = allBindingsAccessor();

    //initialize datepicker with some optional options
    var options = allBindingsAccessor().datepickerOptions || {};
    $(element).datepicker(options);
    $(element).datepicker('update', dataSource());
    //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);
        }
    });
},
update: function (element, valueAccessor) {
    var widget = $(element).data("datepicker");

    var value = ko.utils.unwrapObservable(valueAccessor());

    //when the view model is updated, update the widget
    if (widget) {
        widget.date = value;
        if (widget.date) {
            widget.setValue();
            $(element).datepicker('update', value)
        }
    }
}};
Philipp P
  • 609
  • 1
  • 9
  • 12
6

The accepted answer didn't work for me with the current version of the date picker. The input wasn't being initialized with the value of the observable. I made an updated binding, to which I added this:

$(element).datepicker('update', dataSource());

That seems to do the trick.

Here's an updated fiddle that uses the latest available date picker, Bootstrap, jQuery, and Knockout: http://jsfiddle.net/krainey/nxhqerxg/

Update:

I experienced some difficulty with the date picker not playing nicely with the observable when a user would edit the value in the text field manually. The tool would immediately parse the date, and plug the result into the input field.

If the user tried to edit 10/07/2014, for example, and used the backspace or delete to remove a number (10/0/2014), the resulting value would be parsed immediately and inserted into the text input. If the value was, for a moment, 10/0/2014, the picker would shift the calendar to 09/30/2014, and plug that value into the text field. If I tried to edit the month, and the value was, for a moment, 1/7/2014, the picker would shift to January 7, 2014, and plug that value in to the text field.

You can see that behavior in this fiddle:

http://jsfiddle.net/krainey/nxhqerxg/10/

I had to update my binding with a special handler to detect focus, and bind a one-time blur event to get it to handle manual edits correctly.

$(element).on("changeDate", function (ev) {
    var observable = valueAccessor();
    if ($(element).is(':focus')) {
        // Don't update while the user is in the field...
        // Instead, handle focus loss
        $(element).one('blur', function(ev){
            var dateVal = $(element).datepicker("getDate");
            observable(dateVal);
        });
    }
    else {
        observable(ev.date);
    }
});

The fiddle referenced in the original answer has been updated to reflect this:

http://jsfiddle.net/krainey/nxhqerxg/

kiprainey
  • 3,241
  • 4
  • 29
  • 29
2

Here is what I Ended up

ko.bindingHandlers.datepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
    //initialize datepicker with some optional options

    var options = {
        autoclose: true,
        format: 'yyyy-mm-dd',
    }

    //var options = allBindingsAccessor().datepickerOptions || {};
    $(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)) {
            var myDate = event.date;
            var month = myDate.getMonth() + 1;
            var monthText = month;

            if (month < 10)
                monthText = "0" + month;

            var day1 = parseInt(myDate.getDate());
            var dayText = day1;

            if (day1 < 10)
                dayText = '0' + day1;

            value(myDate.getFullYear() + '-' + monthText + '-' + dayText);
        }
    });
},
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());
        widget.setValue(widget.date);
    }
}};
Muhammad Amin
  • 1,173
  • 8
  • 11
1

There is a much simpler way to get bootstrap-datepicker.js and knockout.js working together. Typically the initialization of the datepicker inputs is invoked during/after the page load. However when knockout.js updates the value binding, the datepicker is not updated correctly with the new value and so when you focus on the datepicker input it defaults to 01-Jan-2001.

All you have to do is to destroy and reinitialise the datepicker inputs after any knockout.js value bindings are updated as per the ViewModel method below, which sets up a mapped object to be edited.

self.showEditOrderForm = function (data) {
    ko.mapping.fromJS(data, self.entity);
    self.mode('edit');
    $('#edit-dateordered').datepicker('destroy');
    $('#edit-dateordered').datepicker({
        format: 'dd-M-yyyy',
        title: 'Select Date',
        weekStart: 1,
        todayHighlight: true,
        autoClose: true,
        endDate: '0d'
    });
};
tdy
  • 36,675
  • 19
  • 86
  • 83