Reputation: 15973
I took over a project which heavily relies on knockout.js's databinding technique. The app's purpose is basically to display (sport) courses. Required are the name of the course and the date (and time) when it begins. The data is pulled via ajax and then bound to a view via knockout.js
Basically, there is a model which looks like this:
BaseModel: function() {
var self = this;
var mappingOptions = {};
self.setMappingOptions = function (options) {
$.extend(mappingOptions, options);
};
self.map = function (data) {
ko.mapping.fromJS(data, mappingOptions, self);
return self;
};
self.isNew = function() {
return !(typeof(self.id) == 'function' && self.id() != null);
};
}
Course: function() {
BaseModel.call(this);
var self = this;
ko.mapping.fromJS({
name: null,
date: moment().tz('UTC').minute(0).second(0).format('YYYY-MM-DDTHH:mm:ssZ'),
timezone: null,
duration: null
}, {}, self);
self.duration = ko.integerObservable();
self.dateDateTime = new IMWeb.ko.DateTime(self.date);
self.timezone = self.dateDateTime.timezone();
self.dateDateTime.timezone.subscribe(function(timezone) {
self.timezone = timezone;
});
}
The view binds this model like this:
<input type="text" class="form-control" id="nameInput" data-bind="value: name"/>
<input type="text" class="form-control" id="dateInput" data-bind="value: clientDateDateTime.date, event:{focus: $parent.onDateInput, mouseover: $parent.onDateInput}, enable: changeable"/>
<input id="timeInput" type="text" class="form-control" data-bind="value: dateDateTime.time, event:{focus: $parent.onTimeInput, mouseover: $parent.onTimeInput}, enable: changeable">
The date in the json data which is sent from the server is set to UTC, so I created the following function in order to convert it to the client's timezone:
/**
* Converts the given UTC date to local date of the client by subtracting
* the local timezone offset from the given date.
*
* @param {Date} utcDate The date object to convert
* @returns {Date} The converted date object
*/
var UTC2LocalDate = function (utcDate) {
var timestamp = utcDate.getTime(); // Number of miliseconds since Jan 1st 1970, 0:00:00 UTC
var offset = (new Date()).getTimezoneOffset(); // Local client offset in minutes
timestamp -= offset * 60 * 1000; // Fix date with offset by converting offset minutes to miliseconds
return new Date(timestamp);
};
Now, here is the tricky part: How can I run this "conversion code" without interfering to much in the data binding mechanism?
Basically, I want this behavior to happen:
Model changes -> Date from model gets converted to client's local timezone using UTC2LocalDate()
and view gets updated (without changing the model's original UTC value).
User changes value in <input>
-> Date/Time gets converted to UTC and saved to model.
Upvotes: 1
Views: 929
Reputation: 43881
Converting back and forth is best handled by a writable computed. You would have an ordinary observable for the UTC date, and a writable computed based on it. The basic construction looks like this:
vm.utcDate = ko.observable();
vm.localDate = ko.computed({
deferEvaluation: true,
read: function () {
return toLocalDate(vm.utcDate());
},
write: function (newLocalDate) {
vm.utcDate(toUtcDate(newLocalDate));
}
});
(I'm obviously leaving implemention of toLocalDate
andtoUtcDate
up to you.) Use the localDate
variable in any binding you want to display and/or accept local dates. It will automatically change when utcDate
is updated, and will automatically change utcDate
when a new localDate
value is input.
Upvotes: 1
Reputation: 414
My approach would be to use your UTC2LocalDate to convert the value of self.date before being used in the construction of dateDateTime. At this point your user will be interacting with a local date time.
Next you could write a function to convert back, lets call it Local2UtcDate, and update your self.date value by subscribing to self.dateDateTime. It would look something like this:
self.duration = ko.integerObservable();
self.dateDateTime = new IMWeb.ko.DateTime(UTC2LocalDate(self.date));
self.timezone = self.dateDateTime.timezone();
self.dateDateTime.timezone.subscribe(function(timezone) {
self.timezone = timezone;
});
self.dateDateTime.subscribe(function(changedLocalDate){
self.date(Local2UtcDate(changedLocalDate));
});
I'm not sure exactly what the server is expecting to be posted back, but this way self.dateDateTime will be a local time and self.date will be an updated UTC time.
Upvotes: 0