0

I'm trying to create a knockout binding for Flatpickr but having no luck. I've tried tweaking the code from the accepted answer here to no avail. The Flatpickr is initialized, but it doesn't seem to use the default options I have setup, and the update part of the binding just doesn't work at all.

My code is below. I would create a jsFiddle but it is blocked here at work..

ko.bindingHandlers.datetimepicker = {
  init: function (element, valueAccessor, allBindingsAccessor) {
    var options = allBindingsAccessor().datetimepickerOptions || { dateFormat: 'm-d-Y', enableTime: true };
    var $el = $(element);

    $(element).flatpickr(options);

    //handle the field changing by registering datepicker's changeDate event
    ko.utils.registerEventHandler(element, "onChange", function () {
      var observable = valueAccessor();
      observable($el.val());
    });

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

    $el.val(new Date(ko.utils.unwrapObservable(valueAccessor())));
  },
  update: function (element, valueAccessor, allBindingsAccessor) {
    var value = ko.utils.unwrapObservable(valueAccessor());
    var $el = $(element);

    // handle json date from microsoft
    if (String(value).indexOf('/Date(') == 0) {
      value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
    }

    var current = new Date($el.val());

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

EDIT

A little more info on my situation. I have a page with pager buttons at the top, and these buttons cycle through an array in my viewmodel. Every time I page, a displayedRecord property gets set to the next/prev item in the array, and all the edit fields are bound to the displayedRecord property. This works fine for all my inputs except the ones that use datetimepicker binding that @atitsbest provided to me.

The datetimepicker fields get populated the first time, but once I page past them the viewmodel property for that record somehow gets set to null.

EDIT 2

Got this working finally. Here's the final product. Ended up removing the subscribe in the init function because it caused errors when trying to set it's observable to null. Also added flatpickr wrap functionality for user's to provide their own buttons for clearing, toggling, etc.

ko.bindingHandlers.flatpickr = {
  init: function (element, valueAccessor, allBindingsAccessor) {
    var options = $.extend({
      dateFormat: 'm/d/Y H:i',
      enableTime: true,
      time_24hr: true,
      minuteIncrement: 1
    }, allBindingsAccessor().flatpickrOptions);
    var $el = $(element);
    var picker;

    if (options.wrap) {
      picker = new Flatpickr(element.parentNode, options);
    } else {
      picker = new Flatpickr(element, options);
    }

    // Save instance for update method.
    $el.data('datetimepickr_inst', picker);

    // handle the field changing by registering datepicker's changeDate event
    ko.utils.registerEventHandler(element, "change", function () {
      valueAccessor()(picker.parseDate($el.val()));
    });

    // handle disposal (if KO removes by the template binding)
    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
      $el.flatpickr("destroy");
    });
  },
  update: function (element, valueAccessor, allBindingsAccessor) {
    // Get datepickr instance.
    var picker = $(element).data('datetimepickr_inst');

    picker.setDate(ko.unwrap(valueAccessor()));
  }
};
Community
  • 1
  • 1
JB06
  • 1,881
  • 14
  • 28

1 Answers1

1

I created a jsfiddle and made some changes to your binding:

  • changed event name from "onChange" to "change"; now it fires.
  • created some default options, even if custom options are set; useful for a default dateFormat.
  • replaced the "update" function with a subscribe to update the observable;.
  • the bound value is now always a date.

The new binding:

ko.bindingHandlers.datetimepicker = {
  init: function(element, valueAccessor, allBindingsAccessor) {
    var options = $.extend({
        dateFormat: 'm-d-Y H:i',
        enableTime: true
      }, allBindingsAccessor().datetimepickerOptions),
      $el = $(element),
      picker = new Flatpickr(element, options),
      observable = valueAccessor();

    //handle the field changing by registering datepicker's changeDate event
    ko.utils.registerEventHandler(element, "change", function() {
      observable(picker.parseDate($el.val()));
    });

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

    observable.subscribe(function(newVal) {
      $el.val(picker.formatDate(options.dateFormat, newVal));
    });

    picker.setDate(ko.unwrap(observable));
  }
};

Hope that helps.

UPDATE

To cope with paging I changed the binding. Here is the updated jsfiddle to test the binding with a paging scenario. And here the new binding code:

ko.bindingHandlers.datetimepicker = {
  init: function(element, valueAccessor, allBindingsAccessor) {
    var options = $.extend({
          dateFormat: 'm-d-Y H:i',
          enableTime: true
        },
        allBindingsAccessor().datetimepickerOptions),
      $el = $(element),
      picker = new Flatpickr(element, options);

    // Save instance for update method.
    $el.data('datetimepickr_inst', picker);

    // handle the field changing by registering datepicker's changeDate event
    ko.utils.registerEventHandler(element, "change", function() {
      valueAccessor()(picker.parseDate($el.val()));
    });

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

    // Update datepicker with new value from observable
    valueAccessor().subscribe(newVal => $el.val(picker.formatDate(options.dateFormat, newVal)));
  },
  update: function(element, valueAccessor, allBindingsAccessor) {
    // Get datepickr instance.
    var picker = $(element).data('datetimepickr_inst');

    picker.setDate(ko.unwrap(valueAccessor()));
  }
};

Hope that helps.

atitsbest
  • 41
  • 2
  • 3
  • This doesn't seem to work for me, I thought it did at first. See my edit for why and a little more detail on my problem. – JB06 Jan 12 '17 at 17:23
  • Shouldn't it have an update member? Here is what the docu says: "Note: you don’t actually have to provide both init and update callbacks — you can just provide one or the other if that’s all you need." – atitsbest Jan 13 '17 at 06:02
  • Sorry it's been so long for me to reply, but I wanted to thank you for the update, I finally got it working with some tweaking. I posted it as an update in my answer. Thanks again! – JB06 Mar 06 '17 at 14:39