Nathan
Nathan

Reputation: 1705

Knockout change while jQuery Datepicker is open breaks Datepicker

I've set up a jsFiddle to demonstrate my problem.

What I'm doing:

I'm using Knockout with a jQuery Datepicker as shown in this question.

My model contains an observableArray which contains objects with date properties. Knockout renders an <input type="text"> for every object, and binds the value to the date using RP Niemeyer's datepicker binding.

What's going wrong:

If the user has a datepicker open when knockout's model changes (e.g., adding a new item), the datepicker stops working correctly. From what I can tell, the last <input type="text"> created while the datepicker is open becomes the new datepicker target. I need to fix the binding so that this does not happen.

Sample HTML:

<ul data-bind="foreach: dateBoxes">
    <li>
        <span data-bind="text: index"></span>
        <input type="text" data-bind="datepicker: date"/>
    </li>
</ul>

Sample Javascript:

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"));
        });

        //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")));
        }

        var current = $(element).datepicker("getDate");

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

var model;
var id = 0;
function DateBox(d) {
    var self = this;
    self.date = ko.observable(d);
    self.index = ko.observable(id++);
}
function Model() {
    var self = this;
    self.dateBoxes = ko.observableArray([]);

    self.changeCount = function() {
        if (self.dateBoxes().length < 2) {
            self.dateBoxes.push(new DateBox(new Date()));
        } else if (self.dateBoxes().length > 1) {
            self.dateBoxes.splice(0,1);
        }
    }
    self.tick = function() {
        self.changeCount();
        setTimeout(function() {
            self.tick();
        }, 5000);
    }
    self.tick();
}
model = new Model();
ko.applyBindings(model);

Upvotes: 2

Views: 1378

Answers (1)

Jeroen
Jeroen

Reputation: 63729

Note: This answer isn't complete. However, as it's too large for a comment and probably helpful (towards a solution) anyhow I've taken the liberty and posted it as an answer. Anyone that can complete this answer is invited to do so through an edit or by spinning off into another better answer.


The thing that seems to be spoiling the party (taken from the documentation, emphasis mine):

This will be called once when the binding is first applied to an element, and again whenever the associated observable changes value.

If I hackisly prevent the update from doing its datepicker calls in the first update the datepicker won't break anymore (other issues do arise though). For the init I've added this line at the end:

element.isFirstRun = true;

Then the update method will do the following just before the datepicker calls:

if (element.isFirstRun) {
    $(element).val(value);
    element.IsFirstRun = false;
    return;
}    

See this updated fiddle for the results, which are:

  • the mentioned scenario now updates the correct textbox (a good thing);
  • the initial value is now a more verbose DateTime string and stays that way after updates (kinda not nice);

Hopefully this will help towards a more complete solution.

Upvotes: 1

Related Questions