Skip to content

Instantly share code, notes, and snippets.

@angelyordanov
Last active December 12, 2015 06:38
Show Gist options
  • Save angelyordanov/4730076 to your computer and use it in GitHub Desktop.
Save angelyordanov/4730076 to your computer and use it in GitHub Desktop.
The 'datePicker' and 'dateValue' knockout binding handlers.
function BindingHandlerFactory(bindingKey, handlerConstructor) {
this.init = $.proxy(this.init, this);
this.update = $.proxy(this.update, this);
this._jqDataKey = 'bhf.' + bindingKey;
this._handlerConstructor = handlerConstructor;
}
BindingHandlerFactory.prototype.init = function (element, valueAccessor) {
var self = this;
self._untrackedCall(function () {
var handler = $.data(element, self._jqDataKey),
context = valueAccessor();
self._lastContext = context;
if (!handler) {
handler = new self._handlerConstructor(self._handlerOptions);
handler.initialize(element, context);
$.data(element, self._jqDataKey, handler);
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
handler.dispose();
$.removeData(element, self._jqDataKey);
});
}
});
return { controlsDescendantBindings: false };
};
BindingHandlerFactory.prototype.update = function (element, valueAccessor) {
var self = this;
self._untrackedCall(function () {
var handler = $.data(element, self._jqDataKey),
context = valueAccessor();
if (handler && self._lastContext !== context) {
handler.contextChanged(context);
}
});
};
//hides all observables from the automatic subscription of the binding system
//as we want to handle all changes locally
BindingHandlerFactory.prototype._untrackedCall = function (func) {
ko.computed({
read: function () {
func();
}
}).dispose();
};
function DatePickerHandler() {
}
DatePickerHandler.prototype.initialize = function (element, context) {
this._$element = $(element);
this._picker = this._$element.datepicker(context.options).data('datepicker');
//bind the context of the _pickerDateChanged function
//so that we can safely use it in $().on()/off()
this._pickerDateChanged = $.proxy(this._pickerDateChanged, this);
this.contextChanged(context);
this._$element.on('changeDate', undefined, undefined, this._pickerDateChanged);
};
DatePickerHandler.prototype.contextChanged = function (context) {
var self = this;
self._value = context.date;
if (self._setDateComputed) {
self._setDateComputed.dispose();
}
//create a computed to update the datepicker date
self._setDateComputed = ko.computed({
read: function () {
var date = ko.utils.unwrapObservable(self._value);
if (date) {
self._picker.setDate(date);
} else {
//the 'bootstrap-datepicker' does not support deselection
//so set the currentdate instead
self._picker.setDate(new Date());
}
}
});
if (self._setDisabledComputed) {
self._setDisabledComputed.dispose();
}
self._setDisabledComputed = ko.computed({
read: function () {
if (ko.utils.unwrapObservable(context.disabled)) {
self._picker._detachEvents();
} else {
self._picker._attachEvents();
}
}
});
};
DatePickerHandler.prototype.dispose = function () {
this._setDateComputed.dispose();
this._setDisabledComputed.dispose();
this._$element.off('changeDate', undefined, this._pickerDateChanged);
this._picker.remove();
};
DatePickerHandler.prototype._pickerDateChanged = function (ev) {
if (ko.isObservable(this._value)) {
this._value(new Date(ev.date));
}
this._picker.hide();
};
function DateValueHandler() {
}
DateValueHandler.prototype.initialize = function (element, context) {
this._element = element;
this.contextChanged(context);
};
DateValueHandler.prototype.contextChanged = function (context) {
var self = this,
newValueAccessor,
newAllBindingsAccessor;
if (self._converterComputed) {
self._converterComputed.dispose();
}
if (ko.isObservable(context)) {
self._converterComputed = ko.computed({
read: function () {
self._triggeredRead = true;
return self._convert(context());
},
write: function (newValue) {
var newConvertedValue = self._convertBack(newValue);
self._triggeredRead = false;
context(newConvertedValue);
//Notify the subscribers even if the write to the underlying value doesn't
//when the value hasn't changed.
//NOTE: This case happens when two consecutive times invalid date strings are
//written, the second value would not trigger a read and the bound input will
//not be emptied.
if (!self._triggeredRead) {
self._converterComputed.notifySubscribers(self._convert(newConvertedValue));
}
}
});
self._value = self._converterComputed;
} else {
self._value = self._convert(context);
}
newValueAccessor = function () { return self._value; };
newAllBindingsAccessor = function () { return { dateValue: self._value}; };
self._initialized = false;
if (self._setValueComputed) {
self._setValueComputed.dispose();
}
self._setValueComputed = ko.computed({
read: function () {
if (!self._initialized) {
if (ko.bindingHandlers.value.init) {
ko.bindingHandlers.value.init(
self._element,
newValueAccessor,
newAllBindingsAccessor
);
}
self._initialized = true;
}
if (ko.bindingHandlers.value.update) {
ko.bindingHandlers.value.update(
self._element,
newValueAccessor,
newAllBindingsAccessor
);
}
}
});
};
DateValueHandler.prototype.dispose = function () {
if (this._converterComputed) {
this._converterComputed.dispose();
}
this._setValueComputed.dispose();
};
DateValueHandler.prototype._convert = function (date) {
return date ? moment(date).format('DD.MM.YYYY') : undefined;
};
DateValueHandler.prototype._convertBack = function (text) {
var momentDate = moment(text, 'DD.MM.YYYY');
if (momentDate && momentDate.isValid()) {
return momentDate.toDate();
}
return undefined;
};
ko.bindingHandlers.datePicker = new BindingHandlerFactory('datePicker', DatePickerHandler);
ko.bindingHandlers.dateValue = new BindingHandlerFactory('dateValue', DateValueHandler);
//Copyright 2013 Angel Yordanov
//
//Permission is hereby granted, free of charge, to any person obtaining
//a copy of this software and associated documentation files (the
//"Software"), to deal in the Software without restriction, including
//without limitation the rights to use, copy, modify, merge, publish,
//distribute, sublicense, and/or sell copies of the Software, and to
//permit persons to whom the Software is furnished to do so, subject to
//the following conditions:
//
//The above copyright notice and this permission notice shall be
//included in all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
//LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
//OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
//WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment