Skip to content

Instantly share code, notes, and snippets.

@agarciadelrio
Last active August 8, 2022 07:28
Show Gist options
  • Save agarciadelrio/4a41a1460e9b946839d02e3d29a67454 to your computer and use it in GitHub Desktop.
Save agarciadelrio/4a41a1460e9b946839d02e3d29a67454 to your computer and use it in GitHub Desktop.
KnockoutJS Extenders Collection
/* <div data-bind="blockUI: IsLoading">...</div> */
ko.bindingHandlers.blockUI = {
update: function (element, valueAccessor, allBindingsAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value)
$(element).block();
else
$(element).unblock();
}
};
ko.bindingHandlers.sortable = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var config = valueAccessor();
if (!config) {
return;
}
var allBindings = allBindingsAccessor();
var array = allBindings.foreach || allBindings.template.foreach;
var $list = jQuery(element);
$list
.data('ko-sort-array', array)
.on('sortstart', function (event, ui) {
ui.item.data('ko-sort-array', array);
ui.item.data('ko-sort-index', ui.item.index());
})
.on('sortupdate', function (event, ui) {
//this.object_has_changed(true)
//console.log('sortable this', this)
//console.log('sortable viewModel', viewModel )
//console.log('sortable allBindingsAccessor', allBindingsAccessor )
if (viewModel.object_has_changed) {
viewModel.object_has_changed(true);
}
var $newList = ui.item.parent();
if ($newList[0] != $list[0]) {
return;
}
var oldArray = ui.item.data('ko-sort-array');
var oldIndex = ui.item.data('ko-sort-index');
var newArray = $newList.data('ko-sort-array');
var newIndex = ui.item.index();
var item = oldArray.splice(oldIndex, 1)[0];
newArray.splice(newIndex, 0, item);
})
.sortable(config);
}
};
ko.bindingHandlers.dump = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var context = valueAccessor();
var allBindings = allBindingsAccessor();
var pre = document.createElement('pre');
element.appendChild(pre);
var dumpJSON = ko.computed({
read: function () {
var en = allBindings.enabled === undefined || allBindings.enabled;
return en ? ko.toJSON(context, null, 2) : '';
},
disposeWhenNodeIsRemoved: element
});
ko.applyBindingsToNode(pre, {
text: dumpJSON,
visible: dumpJSON
});
return { controlsDescendentBindings: true };
}
};
ko.bindingHandlers.summernote = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor());
var $element = $(element);
$element.html(value).summernote({
lang: 'es-ES',
callbacks: {
onChange: function (contents) {
valueAccessor()(contents);
}
}
});
}
};
ko.bindingHandlers.fadeVisible = {
init: function (element, valueAccessor) {
// Initially set the element to be instantly visible/hidden depending on the value
var value = valueAccessor();
$(element).toggle(ko.unwrap(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
},
update: function (element, valueAccessor) {
// Whenever the value subsequently changes, slowly fade the element in or out
var value = valueAccessor();
ko.unwrap(value) ? $(element).fadeIn() : $(element).fadeOut();
}
};
ko.bindingHandlers.let = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
// Make a modified binding context, with extra properties, and apply it to descendant elements
var innerContext = bindingContext.extend(valueAccessor);
ko.applyBindingsToDescendants(innerContext, element);
return { controlsDescendantBindings: true };
}
};
ko.virtualElements.allowedBindings.let = true;
//example <div data-bind="let: {url : file_name().replace('.json','')} ">
ko.bindingHandlers.initialValue = {
init: function(element, valueAccessor, allBindingsAccessor) {
//console.log('INIT initialValue');
//console.log('element',element);
//console.log('valueAccessor',valueAccessor);
var $e = $(element);
var val = $e.data('value');
$e.val(val).trigger('change');
}
};
ko.bindingHandlers.currency = {
update: function(element, valueAccessor){
//console.log('CURRENCY');
var value = ko.utils.unwrapObservable(valueAccessor()) || 0;
value = + value;
//var formattedText = "$" + value.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,");
//$(element).text(formattedText);
$(element).text( parseFloat(value).toFixed(2).replace('.',',') + '€' );
}
};
ko.bindingHandlers.timepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
console.log('timepicker.');
var $e = $(element);
var options = allBindingsAccessor().timepickerOptions || {};
$e.timepicker(options);
$e.timepicker().update(function() {
if($e.hasClass('recogida')) {
$('.recogida-h').val($e.val()).trigger('change');
}
if($e.hasClass('devolucion')) {
$('.devolucion-h').val($e.val()).trigger('change');
}
});
}
};
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {
minDate: 0,
showOtherMonths: true,
selectOtherMonths: true,
dateFormat: ((window.aviacar && window.aviacar.dateFormat) ? window.aviacar.dateFormat : 'dd/mm/yy'),
};
//console.log('dateFormat', window.aviacar.dateFormat);
$(element).datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", function () {
var observable = valueAccessor();
//console.log('elemt', $(element).val());
if($(element).hasClass('from')) {
if($('.form-control-date.to').datepicker('getDate') < $(element).datepicker('getDate')) {
$('.form-control-date.to').val($(element).val()).datepicker( "option", "minDate", $(element).datepicker("getDate") );
$('.form-control-date.to').trigger('change');
}
}
//observable($(element).datepicker("getDate"));
observable($(element).val());
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).datepicker("destroy");
});
},
//update the control when the view model changes
update: function(element, valueAccessor) {
//console.log('DATEPICKER UPDATE');
var value = ko.utils.unwrapObservable(valueAccessor());
//current = $(element).datepicker("getDate");
var current = $(element).val();
if (value - current !== 0) {
$(element).datepicker("setDate", value);
}
}
};
ko.bindingHandlers.popUp = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var attribute = ko.utils.unwrapObservable(valueAccessor());
var templateContent = attribute.content;
var popOverTemplate = "<div class='popOverClass' id='" + attribute.id + "-popover'>" + $(templateContent).html() + "</div>";
$(element).popover({
placement: 'bottom',
content: popOverTemplate,
html: true,
trigger: 'manual'
});
$(element).attr('id', "popover" + attribute.id + "_click");
$(element).on('click', function () {
$(".popOverClass").popover("hide");
$(this).popover('toggle');
var thePopover = document.getElementById(attribute.id + "-popover");
childBindingContext = bindingContext.createChildContext(viewModel);
ko.cleanNode(thePopover);
ko.applyBindingsToDescendants(childBindingContext, thePopover);
});
}
};
ko.bindingHandlers.readonly = {
update: function (element, valueAccessor) {
if (valueAccessor()) {
$(element).attr("readonly", "readonly");
$(element).addClass("disabled");
} else {
$(element).removeAttr("readonly");
$(element).removeClass("disabled");
}
}
};
ko.bindingHandlers.fadeVisible = {
init: function(element, valueAccessor) {
// Initially set the element to be instantly visible/hidden depending on the value
var value = valueAccessor();
$(element).toggle(ko.unwrap(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
},
update: function(element, valueAccessor) {
// Whenever the value subsequently changes, slowly fade the element in or out
var value = valueAccessor();
return ko.unwrap(value) ? $(element).fadeIn() : $(element).fadeOut();
}
};
ko.bindingHandlers.slideVisible = {
init: function(element, valueAccessor) {
// Initially set the element to be instantly visible/hidden depending on the value
var value = valueAccessor();
$(element).toggle(ko.unwrap(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
},
update: function(element, valueAccessor) {
// Whenever the value subsequently changes, slowly fade the element in or out
var value = valueAccessor();
return ko.unwrap(value) ? $(element).slideDown() : $(element).slideUp();
}
};
ko.bindingHandlers.option = {
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
//console.log('value', element, valueAccessor);
ko.selectExtensions.writeValue(element, value);
}
};
// VERSION 0.1
ko.bindingHandlers.contentEditable = {
update: function (element, valueAccessor) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
element.innerHTML = valueUnwrapped;
element.addEventListener('blur', function() {
var observable = valueAccessor();
observable(element.innerHTML);
})
}
};
// VERSION 0.2
ko.bindingHandlers.contentEditable = {
update: function(element, valueAccessor) {
var value = valueAccessor();
function onBlur(){
if (ko.isWriteableObservable(value)) {
value(this.innerHTML);
}
};
element.innerHTML = value(); //set initial value
element.contentEditable = true; //mark contentEditable true
element.addEventListener('blur', onBlur); //add blur listener
}
};
// VERSION OK
ko.bindingHandlers.contentEditable = {
init: function(element, valueAccessor) {
var value = valueAccessor();
function onBlur(){
if (ko.isWriteableObservable(value)) {
value(this.innerHTML);
}
}
element.contentEditable = 'true'; //mark contentEditable true
element.addEventListener('blur', onBlur); //add blur listener
},
update: function(element, valueAccessor) {
element.innerHTML = ko.unwrap(valueAccessor()); //set initial value
}
};
// https://riptutorial.com/knockout-js/example/22504/custom-binding-handler
ko.bindingHandlers.href = {
update: function(element, valueAccessor) {
element.href = ko.utils.unwrapObservable(valueAccessor());
}
};
ko.components.register('like-widget', {
viewModel: function (params) {
// Data: value is either null, 'like', or 'dislike'
this.chosenValue = params.value;
// Behaviors
this.like = function () { this.chosenValue('like'); }.bind(this);
this.dislike = function () { this.chosenValue('dislike'); }.bind(this);
},
template:
`<div class="like-or-dislike" data-bind="visible: !chosenValue()">
<button data-bind="click: like">Like it</button>
<button data-bind="click: dislike">Dislike it</button>
</div>
<div class="result" data-bind="visible: chosenValue">
You <strong data-bind="text: chosenValue"></strong> it
</div>`
});
// Persist data on Local Storage
ko.extenders.persist = function (target, key) {
var initialValue = target();
// Load existing value from localStorage if set
if (key && localStorage.getItem(key) !== null) {
try {
initialValue = JSON.parse(localStorage.getItem(key));
}
catch (e) { }
}
target(initialValue);
// Subscribe to new values and add them to localStorage
target.subscribe(function (newValue) { localStorage.setItem(key, ko.toJSON(newValue)); });
return target;
// ejemplo de uso: o = ko.observable('').extend({ persist: key });
};
ko.extenders.notnull = function(target, key) {
target.subscribe(function(newValue) {
if(newValue=='') newValue=key
target(newValue)
})
return target
}
// Track if observable is changed
ko.extenders.trackChange = function (target, track) {
if (track) {
target.isDirty = ko.observable(false);
target.originalValue = target();
target.setOriginalValue = function(startingValue) {
target.originalValue = startingValue;
};
target.subscribe(function (newValue) {
// use != not !== so numbers will equate naturally
target.isDirty(newValue != target.originalValue);
});
}
return target;
};
ko.extenders.parentSaveChanges = function(target, parent) {
if(parent) {
target.subscribe(function(new_value) {
parent.saveChanges();
});
}
return target;
};
// Autocast to Float Number
ko.extenders.autoCast = function (target, activated) {
if (activated) {
var result = ko.pureComputed({
read: target,
write: function (newValue) {
var current = target();
var valueToWrite;
if (newValue === undefined) {
valueToWrite = null;
}
else if (isNaN(newValue)) {
if (newValue == NaN) {
valueToWrite = null;
}
else if (newValue === 'true' || newValue === 'false') {
valueToWrite = newValue === 'true' ? true : false;
}
else {
valueToWrite = newValue;
}
}
else {
if (newValue === true || newValue === false) {
valueToWrite = newValue;
}
else {
valueToWrite = parseFloat(newValue);
}
if (isNaN(valueToWrite)) {
valueToWrite = null;
}
}
if (valueToWrite !== current) {
target(valueToWrite);
}
else {
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({ notify: 'always' });
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result;
}
};
// Cast to Boolean
ko.extenders.toBoolean = function (target, activated) {
//create a writable computed observable to intercept writes to our observable
if (activated) {
var result = ko.pureComputed({
read: target,
write: function (newValue) {
//console.log('cambios en input')
var current = target();
var valueToWrite = newValue == '1' ? true : false;
//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
}
else {
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({ notify: 'always' });
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result;
}
};
// Cast to Integer Number
ko.extenders.toNumber = function(target, number) {
//create a writable computed observable to intercept writes to our observable
if(number) {
var result = ko.pureComputed({
read: target, //always return the original observables value
write: function(newValue) {
//console.log('cambios en input')
var current = target();
var valueToWrite = parseInt(newValue,10);
//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
} else {
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({ notify: 'always' });
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result ? result : 0;
}
};
ko.extenders.currency = function(target) {
var result = ko.pureComputed({
read : target,
write : function(newValue) {
var current = target(),
roundingMultipler = Math.pow(10, 2),
newValueAsNum = isNaN(+newValue) ? 0 : parseFloat(newValue.toString()),
valueToWrite = Math.round(newValueAsNum * roundingMultipler) / roundingMultipler;
if (valueToWrite !== current) {
target(valueToWrite);
}
else {
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({ notify: 'always' });
result(target());
return result;
}
ko.extenders.camelCase = function (target, activated) {
//Require lodash
//create a writable computed observable to intercept writes to our observable
if (activated) {
var result = ko.pureComputed({
read: target,
write: function (newValue) {
//console.log('cambios en input')
var current = target();
var valueToWrite = _.upperFirst(_.camelCase(newValue));
//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
}
else {
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({ notify: 'always' });
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result;
}
};
ko.extenders.snakeCase = function (target, activated) {
//Require lodash
//create a writable computed observable to intercept writes to our observable
if (activated) {
var result = ko.pureComputed({
read: target,
write: function (newValue) {
//console.log('cambios en input')
var current = target();
var valueToWrite = _.snakeCase(newValue);
//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
}
else {
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({ notify: 'always' });
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result;
}
};
ko.extenders.upperSnakeCase = function (target, activated) {
//Require lodash
if (activated) {
var result = ko.pureComputed({
read: target,
write: function (newValue) {
//console.log('cambios en input')
var current = target();
var valueToWrite = _.snakeCase(newValue).toUpperCase();
//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
}
else {
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({ notify: 'always' });
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result;
}
};
// Validate email
ko.extenders.email = function(target, overrideMessage) {
target.hasError = ko.observable();
target.validationMessage = ko.observable();
function validate(newValue) {
newValue = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(newValue);
target.hasError(newValue ? false : true);
target.validationMessage(newValue ? "" : overrideMessage || "Email incorrecto");
}
validate(target());
target.subscribe(validate);
return target;
};
// Check for Required value
ko.extenders.required = function(target, overrideMessage) {
//add some sub-observables to our observable
target.hasError = ko.observable();
target.validationMessage = ko.observable();
//define a function to do validation
function validate(newValue) {
target.hasError(newValue ? false : true);
target.validationMessage(newValue ? "" : overrideMessage || "Campo requerido");
}
//initial validation
validate(target());
//validate whenever the value changes
target.subscribe(validate);
//return the original observable
return target;
};
ko.computed(function() {
return ko.toJSON(complexObject);
}).subscribe(function() {
// called whenever any of the properties of complexObject changes
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment