Mark
Mark

Reputation: 595

TempusDominus Knockout Binding Doesn't Fire update Event After Initializing

I'm trying to use knockoutjs with the tempusdominus datetimepicker and am having a problem with the custom binding. The binding will work during initialization, but changes made through the datepicker ui or not resulting in update events being handled by the custom binding. Please ignore the missing icons, I don't think that is what is causing the problem.

ko.bindingHandlers.datepicker = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    //initialize datepicker with some optional options
    var options = allBindings().datepickerOptions || {
      format: 'MM/DD/YYYY HH:mm',
      defaultDate: valueAccessor()()
    };
    $(element).datetimepicker(options);

    //when a user changes the date, update the view model
    ko.utils.registerEventHandler(element, "change.datetimepicker", function(event) {
      var value = valueAccessor();
      if (ko.isObservable(value)) {
        value(event.date);
      }
      console.log("change.datetimepicker"); //, event.date.format());
    });
  },
  update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    var val = ko.utils.unwrapObservable(valueAccessor());
    if ($(element).datetimepicker) {
      $(element).datetimepicker("date", val);
    }
    console.log("update called"); //, $(element).datetimepicker("date").format());
  }
};

var viewModel = {};

function QuoteViewModel(data) {
  var self = this;
  self.dt2 = ko.observable(moment());
};

viewModel = new QuoteViewModel();
ko.applyBindings(viewModel);

/*
$("#datetimepicker1").on("change.datetimepicker", function (e) {
  console.log("on change", e.date.format());
});
*/
<link href="https://cdn.rawgit.com/Eonasdan/bootstrap-datetimepicker/d004434a5ff76e7b97c8b07c01f34ca69e635d97/build/css/bootstrap-datetimepicker.css" rel="stylesheet"/>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/moment-with-locales.js"></script>
<script src="https://cdn.rawgit.com/Eonasdan/bootstrap-datetimepicker/d004434a5ff76e7b97c8b07c01f34ca69e635d97/src/js/bootstrap-datetimepicker.js"></script>

<div class="col-sm-6">
  <div class="form-group">
    <div class="input-group date" id="datetimepicker1" data-target-input="nearest">
      <input type="text" class="form-control datetimepicker-input" data-target="#datetimepicker1" data-bind="datepicker: dt2" />
      <div class="input-group-append" data-target="#datetimepicker1" data-toggle="datetimepicker">
        <div class="input-group-text"><i class="fa fa-calendar"></i></div>
      </div>
    </div>
  </div>
</div>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre> SCRIPT

https://jsfiddle.net/mh_surge/7yrze0Lv/6/

If I uncomment the code at the bottom that adds the non-ko change event handler, it always fires as expected. This leads me to believe that there might be a problem with ko.utils.registerEventHandler, but the source code for that function seems fine.

Why is the update and change event only being fired off only once during init and how do I fix it?

Upvotes: 2

Views: 1553

Answers (3)

m-albert
m-albert

Reputation: 1100

The following edit works for me with both the calendar popup AND manual editing of the text input.

Here is why this works. In the original code init function, I changed the line value(event.date); to value(event.date || event.target.value || null);. This is because the event object could come from either the textbox or the datepicker itself. The coalescent modification allows the value to be set from either scenario. Finally, there is also a fallback that sets the valueAccessor to null if neither reference returns a non-falsey (true-y?) value.

ko.bindingHandlers.dateTimePicker = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        var options = allBindingsAccessor().dateTimePickerOptions || {};
        var initialValue = ko.utils.unwrapObservable(valueAccessor());
        if (initialValue) {
            options.date = initialValue;
        }
        $(element).datetimepicker(options);

        ko.utils.registerEventHandler(element, "change.datetimepicker", function (event) {
            var value = valueAccessor();
            if (ko.isObservable(value)) {
                value(event.date || event.target.value || null);
            }
        });

        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).datetimepicker("destroy");
        });
    },
    update: function (element, valueAccessor) {
        var val = ko.utils.unwrapObservable(valueAccessor());
        console.log(arguments, val);
        if ($(element).datetimepicker) {
            $(element).datetimepicker("date", val);
        }
    }
};

Upvotes: 0

Richard   Housham
Richard Housham

Reputation: 1680

I was getting similar adding valueUpdate:'input' e.g

      <input type="text" autocomplete="off" data-bind="value:addStart,valueUpdate:'input'" class="form-control datetimepicker-input timepicker" id="addStartTime" data-toggle="datetimepicker" data-target="#addStartTime" />
           

Seems to solve the issue.

Upvotes: 0

user3297291
user3297291

Reputation: 23372

The change event that is triggered does not contain an event.date property. The reason your commented out jQuery version does, is that it targets a different element.

In the knockout code, you're using the element argument passed to init and update. This refers to the element that has the datepicker data-bind.

In the jQuery code, you're targeting $("#datetimepicker1"), which is a <div> that has a custom change element implemented by the plugin.

To fix this, you'll need to:

  • Put the datepicker binding on the element with id datetimepicker1 (i.e. not on the input element)
  • Use the $(element).on("change.datepicker", ...) syntax in the init function
  • Add an element disposal callback to clean up the widget instance in case knockout removes the element from the DOM

Upvotes: 3

Related Questions