Search code examples
javascriptknockout.jsbootstrap-datetimepicker

How can I access the observable in my custom binding?


I am using Bootstrap 3 Datepicker to edit the date and time of one of the properties of my observable. It works fine when I am creating a new record but not to edit one that is already created.

My custom binding is as follows

ko.bindingHandlers.datetimepicker = {
    init: function (element, valueAccessor, allBindings) {
        var options = {
            format: 'DD/MM/YYYY hh:mm A',
            locale: 'en-AU',
            sideBySide: true,
            defaultDate: ko.unwrap(valueAccessor())
        };

        ko.utils.extend(options, allBindings.dateTimePickerOptions);

        $(element).datetimepicker(options).on("dp.change", function (evntObj) {
            var observable = valueAccessor();
            if (evntObj.timeStamp !== undefined) {
                var picker = $(this).data("DateTimePicker");
                var d = picker.date();
                if (ko.isObservable(observable)) {
                    observable(d.format(options.format));
                } else {
                    valueAccessor(d.format(options.format));
                    console.log("Not observable");
                }
                console.log(observable, "\n", $(element).val());
            }
        });
    },
    update: function (element, valueAccessor) {
        var value = ko.unwrap(valueAccessor());
        $(element).datetimepicker('date', value || '');
    }
};

And here is the relevant part of my ViewModel

var PicksViewModel = function () {
    var self = this;
    self.pick = ko.observable();
    self.newPick = {
        Title: ko.observable(),
        Sport: ko.observable(),
        MatchTime: ko.observable(),
        PublishTime: ko.observable(),
        HomeTeam: ko.observable(),
        AwayTeam: ko.observable(),
        Analysis: ko.observable(),
        PickSummary: ko.observable()
    };
}

newPick and pick are used for 2 separate forms. One to create a new one and one to edit an existing pick. When I use the datepicker for a new pick it works as intended and I don't get a console log that says Not observable but when I edit an existing pick the observable isn't passed to the binding and therefore doesn't get updated and I get the console log.

How can I get the observable passed to my custom binding?

I am using the following html for my binding

<form data-bind="with:pick">
    <!-- Rest of form omitted -->
    <div class="input-group date" id="editMatchTimePicker">
        <input type="text" data-bind="datetimepicker:MatchTime">
    </div>
</form>

Update
I fixed it by binding to a separate observable and using a computed observable to handle the conversion between UTC and local times.

self.editMatchTime = ko.observable();
self.localMatchTime = ko.pureComputed({
    read: function () {
        self.editMatchTime(self.pick().MatchTime);
        var local = moment.utc(self.pick().MatchTime).local().format("YYYY-MM-DD[T]HH:mm:ss");
        return local;
    },
    write: function(value) {
        var utcTime = moment(value).utc();
        self.editMatchTime(utcTime);
    },
    owner: self
});

And then my binding changed to

<input type="text" data-bind="datetimepicker:$root.localMatchTime">

Solution

  • You're not binding an observable to your datetimepicker, you're binding a value which is converted from the observable MatchTime. You could bind MatchTime itself and do the conversion inside the handler, or you can use the allBindings parameter to get at the value-bound observable. If you bind the observable itself, you can roll the value-binding functionality into your custom binding handlers so that you only have to make one binding. (See the "Simple Wrapper Binding" section here.)