Skip to content

Instantly share code, notes, and snippets.

@Manuel-S
Created July 27, 2016 11:37
Show Gist options
  • Select an option

  • Save Manuel-S/bb4eb7336ac2e4ad88f6a3de193540d5 to your computer and use it in GitHub Desktop.

Select an option

Save Manuel-S/bb4eb7336ac2e4ad88f6a3de193540d5 to your computer and use it in GitHub Desktop.
extended binding syntax and a few custom bindings
// adds a jquery plugin function to apply ko bindings to a jquery DOM set
$.fn.applyBindings = function (viewModel) {
return this.each(function () {
ko.applyBindings(viewModel, this);
});
}
$.fn.koClean = function () {
return this.each(function () {
ko.cleanNode(this);
});
};
ko.subscribable.fn.subscribeOnce = function (callback, target, event) {
var subscription;
subscription = this.subscribe(function (value) {
if (subscription && subscription.dispose) {
subscription.dispose();
}
callback.call(target, value);
}, null, event);
};
/* --- Binding Handlers ---
*/
// hides children while a boolean is set to true and displays a loading icon instead
ko.bindingHandlers['loadingWhen'] = {
init: function (element) {
var
$element = $(element),
currentPosition = $element.css("position"),
$loader = $("<div><span class=\"ui-button-text\"><img src=\""+g_strAppPrefix+"/Images/gearloader.gif\" style=\"height:18px;width:18px;\"/></span></div>")
.addClass('loader').hide();
//add the loader
$element.append($loader);
//make sure that we can absolutely position the loader against the original element
if (currentPosition == "auto" || currentPosition == "static")
$element.css("position", "relative");
//center the loader
$loader.css({
position: "absolute",
top: "50%",
left: "50%",
"margin-left": -($loader.width() / 2) + "px",
"margin-top": -($loader.height() / 2) + "px"
});
},
update: function (element, valueAccessor) {
var isLoading = ko.utils.unwrapObservable(valueAccessor()),
$element = $(element),
$childrenToHide = $element.children(":not(div.loader)"),
$loader = $element.find("div.loader");
if (isLoading) {
$childrenToHide.css("visibility", "hidden").attr("disabled", "disabled");
$loader.show();
}
else {
$loader.hide();
$childrenToHide.css("visibility", "visible").removeAttr("disabled");
}
}
};
// binding handler that iterates over all properties of an object like a dictionary and provides key/value variables for the binding engine
ko.bindingHandlers['keyvalue'] = {
makeTemplateValueAccessor: function (valueAccessor, sortCallback) {
return function () {
var values = ko.unwrap(valueAccessor());
var array = [];
for (var key in values)
if (values.hasOwnProperty(key))
array.push({ key: key, value: values[key] });
if (typeof sortCallback === 'function') {
array.sort(sortCallback);
}
return array;
};
},
'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
return ko.bindingHandlers['foreach']['init'](element, ko.bindingHandlers['keyvalue'].makeTemplateValueAccessor(valueAccessor, allBindings().sortCallback));
},
'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
return ko.bindingHandlers['foreach']['update'](element,
ko.bindingHandlers['keyvalue'].makeTemplateValueAccessor(valueAccessor, allBindings().sortCallback), allBindings, viewModel, bindingContext);
}
};
/* Function.name polyfill */
(function () {
var fnNamePrefixRegex = /^[\S\s]*?function\s*/;
var fnNameSuffixRegex = /[\s\(\/][\S\s]+$/;
function _name() {
var name = "";
if (this === Function || this === Function.prototype.constructor) {
name = "Function";
}
else if (this !== Function.prototype) {
name = ("" + this).replace(fnNamePrefixRegex, "").replace(fnNameSuffixRegex, "");
}
return name;
}
// Inspect the polyfill-ability of this browser
var needsPolyfill = !("name" in Function.prototype && "name" in (function x() { }));
var canDefineProp = typeof Object.defineProperty === "function" &&
(function () {
var result;
try {
Object.defineProperty(Function.prototype, "_xyz", {
get: function () {
return "blah";
},
configurable: true
});
result = Function.prototype._xyz === "blah";
delete Function.prototype._xyz;
}
catch (e) {
result = false;
}
return result;
})();
// Add the "private" property for testing, even if the real property can be polyfilled
//Function.prototype._name = _name;
// Polyfill it!
if (canDefineProp && needsPolyfill) {
Object.defineProperty(Function.prototype, "name", {
get: _name
});
}
})();
String.prototype.matchAll = String.prototype.matchAll || function (regexp) {
var matches = [];
this.replace(regexp, function () {
var arr = ([]).slice.call(arguments, 0);
var extras = arr.splice(-2);
arr.index = extras[0];
arr.input = extras[1];
matches.push(arr);
});
return matches.length ? matches : null;
};
/* --- String interpolating binding provider ---
Allows {{ expr }} and ${ expr } syntax in DOM.
Also allows attribute.bind="property" attribute bindings.
*/
var StringInterpolatingBindingProvider = function () {
function serialize(obj) {
var str = "";
var names = Object.getOwnPropertyNames(obj);
var length = names.length;
for (let i = 0; i < length; i++) {
var prop = names[i];
var value = obj[prop];
str += "'" + prop + "': " + value;
if (i < (length - 1)) {
str += ", ";
}
}
return "{" + str + "}";
}
this.constructor = StringInterpolatingBindingProvider;
var expressionRegex = /(?:{{([\s\S]+?)}}|\${([^}]*)})/g;
this.preprocessNode = function (node) {
if (node.nodeType === node.TEXT_NODE && node.nodeValue) {
// Preprocess by replacing {{ expr }} with <!-- ko text: expr --><!-- /ko --> nodes
var newNodes = replaceExpressionsInText(node.nodeValue, expressionRegex, function (expressionText) {
return [
document.createComment("ko text:" + expressionText),
document.createComment("/ko")
];
});
// Insert the resulting nodes into the DOM and remove the original unpreprocessed node
if (newNodes) {
for (var i = 0; i < newNodes.length; i++) {
node.parentNode.insertBefore(newNodes[i], node);
}
node.parentNode.removeChild(node);
return newNodes;
}
}
if (node.nodeType === node.ELEMENT_NODE) {
var attrs = {};
var attributes = Array.prototype.slice.call(node.attributes);
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i];
var localName = attribute.localName;
var value = attribute.value;
if (!localName.endsWith(".bind") || localName === "data-bind")
continue;
node.removeAttribute(localName);
localName = localName.substr(0, localName.length - 5);
attrs[localName] = value;
}
if (Object.getOwnPropertyNames(attrs).length > 0) {
var previousBindings = node.getAttribute("data-bind");
node.setAttribute("data-bind", (previousBindings ? previousBindings + ', ' : '') + 'attr:' + serialize(attrs));
return [node];
}
}
};
function replaceExpressionsInText(text, expressionRegex, callback) {
var prevIndex = expressionRegex.lastIndex = 0,
resultNodes = null,
match;
// Find each expression marker, and for each one, invoke the callback
// to get an array of nodes that should replace that part of the text
while (match = expressionRegex.exec(text)) {
var leadingText = text.substring(prevIndex, match.index);
prevIndex = expressionRegex.lastIndex;
resultNodes = resultNodes || [];
// Preserve leading text
if (leadingText) {
resultNodes.push(document.createTextNode(leadingText));
}
resultNodes.push.apply(resultNodes, callback(match[2] || match[1]));
}
// Preserve trailing text
var trailingText = text.substring(prevIndex);
if (resultNodes && trailingText) {
resultNodes.push(document.createTextNode(trailingText));
}
return resultNodes;
};
};
StringInterpolatingBindingProvider.prototype = ko.bindingProvider.instance;
// set as default binding provider
ko.bindingProvider.instance = new StringInterpolatingBindingProvider();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment