javascriptknockout.jsdatepickerko-custom-bindingmetro.js

knockout mvvm binding with metro.js datepicker


I was trying to hack my way around with the metro.js datepicker and knockout. So far my datepicker binding code looks like:

ko.bindingHandlers.datepicker = {
  init: function(el, va, ba, model, ctx) {
    var prop = va();
    $(el).datepicker({
      onSelect: function(txt, date) {
        prop(date);
      }
    });
  },
  update: function(el, va, ba, model, ctx) {
    var prop = va();
    var date = ko.unwrap(prop);

    if(date) {
      applyDate(date);
    }

    function applyDate(dt) {
      var j = $(el);
      var dp = j.data('datepicker');
      var inp = j.find('input');
      var fmt = dp.options.format;
      var sDate = format(fmt, dt);
      // dp._calendar.calendar.dayClick(sDate, dt);
      // inp.value = sDate;
      dp._calendar.calendar('setDate', sDate);
      j.find('input').val(dp._calendar.calendar('getDate')).trigger('change', sDate);
    }

    function format(fmt, dt) {
      fmt = fmt.replace('yyyy', dt.getFullYear());
      fmt = fmt.replace('mm', pad(dt.getMonth() + 1));
      fmt = fmt.replace('dd', pad(dt.getDate()));
      return fmt;
    }

    function pad(n) {
      return parseInt(n) < 10 ? '0' + n: '' + n;
    };
  }
}

Issue is that when I issue a model update on the date property its bound to the datepicker doesn't update. I mean, it does it the very first time, but post that, it fails to update the textbox; calendar shows okay however. Ultimately I need to change the logic in the applyDate function...

JSBin: http://jsbin.com/rupaqolexa/1/edit?html,js,output

Update: Another issue just cropped up...it doesn't work in IE 10+. The date appears as NaN in the UI...

Update: Steps for reproduction

  1. type date 2nd text box: 2013/05/13 & click on the Change button. Observe date is updated in the datepicker textbox. This works as expected. (Except in IE).
  2. type another date in the textbox & click the change button. Observe the date is not updated in the datepicker textbox. Expected here that the datepicker textbox updates with latest value.

Solution

  • In the update part of your custom binding you need to make all the changes to the bound elements, which include the calendar widget, and the related input element.

    I've modified the code to do so, so that it now works.

    function ViewModel(date) {
      var model = this;
      model.date = ko.observable(date);
      model.set = function() {
        var val = $('#somedate').val();
        var dt = new Date(val);
        model.date(dt);
      };
    }
    
    ko.bindingHandlers.datepicker = {
      init: function(el, va, ba, model, ctx) {
        var prop = va();
        $(el).datepicker({
          onSelect: function(txt, date) {
            prop(date);
          }
        });
      },
      update: function(el, va, ba, model, ctx) {
        var newDate = ko.unwrap(va());
    
        if(newDate) {
          var $el = $(el);
          var datePicker = $el.data('datepicker');
          var $input = $el.find('input');
          var formattedDate = format(datePicker.options.format, newDate);
          datePicker._calendar.calendar('setDate', formattedDate);
          $input.val(formattedDate);
          //$input.val(dp._calendar.calendar('getDate'))
          //  .trigger('change', sDate);
        }
    
        function format(fmt, dt) {
          fmt = fmt.replace('yyyy', dt.getFullYear());
          fmt = fmt.replace('mm', pad(dt.getMonth() + 1));
          fmt = fmt.replace('dd', pad(dt.getDate()));
          return fmt;
        }
    
        function pad(n) {
          return parseInt(n) < 10 ? '0' + n: '' + n;
        }
      }
    };
    
    var m = new ViewModel();
    
    $(function(){
      ko.applyBindings(m);
    });
    <link href="//metroui.org.ua/css/metro.css" rel="stylesheet">
    <link href="//metroui.org.ua/css/metro-icons.css" rel="stylesheet">
    <link href="//metroui.org.ua/css/metro-responsive.css" rel="stylesheet">
    <link href="http://metroui.org.ua/css/metro-schemes.css" rel="stylesheet">
    
    <script src="http://metroui.org.ua/js/jquery-2.1.3.min.js"></script>
    <script src="http://metroui.org.ua/js/metro.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-debug.js"></script>
    
    <div>
      <div class="input-control text" data-bind="datepicker: date">
        <input type="text">
        <button class="button"><span class="mif-calendar"></span></button>
      </div>
    </div>
    <div>
      <label>Date</label>
      <div class="input-control text">
        <input type="text" id="somedate"/>
      </div>
      <input type="button" class="button" value="Change" data-bind="click: set"/>
    </div>
    
    <div>
      <code data-bind="text: date"></code>
    </div>

    However there is still a little hiccup: the datepiceker's calendar setdate adss new selected date, instead of replacing selected ones. Please, see the API docs to solve this yourself.