From 26d7faa0aaaeb531ac9d8c0678e840ffdd1762e6 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Mon, 26 Mar 2012 11:09:19 +0000 Subject: [PATCH] js versionned named resource gloups missed to add the file git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@1305310 13f79535-47bb-0310-9956-ffa450edef68 --- .../main/webapp/js/knockout-2.0.0.debug.js | 3223 +++++++++++++++++ 1 file changed, 3223 insertions(+) create mode 100644 archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/knockout-2.0.0.debug.js diff --git a/archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/knockout-2.0.0.debug.js b/archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/knockout-2.0.0.debug.js new file mode 100644 index 000000000..56ce31566 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/knockout-2.0.0.debug.js @@ -0,0 +1,3223 @@ +// Knockout JavaScript library v2.0.0 +// (c) Steven Sanderson - http://knockoutjs.com/ +// License: MIT (http://www.opensource.org/licenses/mit-license.php) + +(function(window,undefined){ +var ko = window["ko"] = {}; +// Google Closure Compiler helpers (used only to make the minified file smaller) +ko.exportSymbol = function(publicPath, object) { + var tokens = publicPath.split("."); + var target = window; + for (var i = 0; i < tokens.length - 1; i++) + target = target[tokens[i]]; + target[tokens[tokens.length - 1]] = object; +}; +ko.exportProperty = function(owner, publicName, object) { + owner[publicName] = object; +}; +ko.utils = new (function () { + var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g; + + // Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup) + var knownEvents = {}, knownEventTypesByEventName = {}; + var keyEventTypeName = /Firefox\/2/i.test(navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents'; + knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress']; + knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave']; + for (var eventType in knownEvents) { + var knownEventsForType = knownEvents[eventType]; + if (knownEventsForType.length) { + for (var i = 0, j = knownEventsForType.length; i < j; i++) + knownEventTypesByEventName[knownEventsForType[i]] = eventType; + } + } + + // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness) + var ieVersion = (function() { + var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i'); + + // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment + while ( + div.innerHTML = '', + iElems[0] + ); + return version > 4 ? version : undefined; + }()); + var isIe6 = ieVersion === 6, + isIe7 = ieVersion === 7; + + function isClickOnCheckableElement(element, eventType) { + if ((element.tagName != "INPUT") || !element.type) return false; + if (eventType.toLowerCase() != "click") return false; + var inputType = element.type.toLowerCase(); + return (inputType == "checkbox") || (inputType == "radio"); + } + + return { + fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/], + + arrayForEach: function (array, action) { + for (var i = 0, j = array.length; i < j; i++) + action(array[i]); + }, + + arrayIndexOf: function (array, item) { + if (typeof Array.prototype.indexOf == "function") + return Array.prototype.indexOf.call(array, item); + for (var i = 0, j = array.length; i < j; i++) + if (array[i] === item) + return i; + return -1; + }, + + arrayFirst: function (array, predicate, predicateOwner) { + for (var i = 0, j = array.length; i < j; i++) + if (predicate.call(predicateOwner, array[i])) + return array[i]; + return null; + }, + + arrayRemoveItem: function (array, itemToRemove) { + var index = ko.utils.arrayIndexOf(array, itemToRemove); + if (index >= 0) + array.splice(index, 1); + }, + + arrayGetDistinctValues: function (array) { + array = array || []; + var result = []; + for (var i = 0, j = array.length; i < j; i++) { + if (ko.utils.arrayIndexOf(result, array[i]) < 0) + result.push(array[i]); + } + return result; + }, + + arrayMap: function (array, mapping) { + array = array || []; + var result = []; + for (var i = 0, j = array.length; i < j; i++) + result.push(mapping(array[i])); + return result; + }, + + arrayFilter: function (array, predicate) { + array = array || []; + var result = []; + for (var i = 0, j = array.length; i < j; i++) + if (predicate(array[i])) + result.push(array[i]); + return result; + }, + + arrayPushAll: function (array, valuesToPush) { + for (var i = 0, j = valuesToPush.length; i < j; i++) + array.push(valuesToPush[i]); + return array; + }, + + extend: function (target, source) { + for(var prop in source) { + if(source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + return target; + }, + + emptyDomNode: function (domNode) { + while (domNode.firstChild) { + ko.removeNode(domNode.firstChild); + } + }, + + setDomNodeChildren: function (domNode, childNodes) { + ko.utils.emptyDomNode(domNode); + if (childNodes) { + ko.utils.arrayForEach(childNodes, function (childNode) { + domNode.appendChild(childNode); + }); + } + }, + + replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) { + var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray; + if (nodesToReplaceArray.length > 0) { + var insertionPoint = nodesToReplaceArray[0]; + var parent = insertionPoint.parentNode; + for (var i = 0, j = newNodesArray.length; i < j; i++) + parent.insertBefore(newNodesArray[i], insertionPoint); + for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) { + ko.removeNode(nodesToReplaceArray[i]); + } + } + }, + + setOptionNodeSelectionState: function (optionNode, isSelected) { + // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser. + if (navigator.userAgent.indexOf("MSIE 6") >= 0) + optionNode.setAttribute("selected", isSelected); + else + optionNode.selected = isSelected; + }, + + stringTrim: function (string) { + return (string || "").replace(stringTrimRegex, ""); + }, + + stringTokenize: function (string, delimiter) { + var result = []; + var tokens = (string || "").split(delimiter); + for (var i = 0, j = tokens.length; i < j; i++) { + var trimmed = ko.utils.stringTrim(tokens[i]); + if (trimmed !== "") + result.push(trimmed); + } + return result; + }, + + stringStartsWith: function (string, startsWith) { + string = string || ""; + if (startsWith.length > string.length) + return false; + return string.substring(0, startsWith.length) === startsWith; + }, + + evalWithinScope: function (expression /*, scope1, scope2, scope3... */) { + // Build the source for a function that evaluates "expression" + // For each scope variable, add an extra level of "with" nesting + // Example result: with(sc[1]) { with(sc[0]) { return (expression) } } + var scopes = Array.prototype.slice.call(arguments, 1); + var functionBody = "return (" + expression + ")"; + for (var i = 0; i < scopes.length; i++) { + if (scopes[i] && typeof scopes[i] == "object") + functionBody = "with(sc[" + i + "]) { " + functionBody + " } "; + } + return (new Function("sc", functionBody))(scopes); + }, + + domNodeIsContainedBy: function (node, containedByNode) { + if (containedByNode.compareDocumentPosition) + return (containedByNode.compareDocumentPosition(node) & 16) == 16; + while (node != null) { + if (node == containedByNode) + return true; + node = node.parentNode; + } + return false; + }, + + domNodeIsAttachedToDocument: function (node) { + return ko.utils.domNodeIsContainedBy(node, document); + }, + + registerEventHandler: function (element, eventType, handler) { + if (typeof jQuery != "undefined") { + if (isClickOnCheckableElement(element, eventType)) { + // For click events on checkboxes, jQuery interferes with the event handling in an awkward way: + // it toggles the element checked state *after* the click event handlers run, whereas native + // click events toggle the checked state *before* the event handler. + // Fix this by intecepting the handler and applying the correct checkedness before it runs. + var originalHandler = handler; + handler = function(event, eventData) { + var jQuerySuppliedCheckedState = this.checked; + if (eventData) + this.checked = eventData.checkedStateBeforeEvent !== true; + originalHandler.call(this, event); + this.checked = jQuerySuppliedCheckedState; // Restore the state jQuery applied + }; + } + jQuery(element)['bind'](eventType, handler); + } else if (typeof element.addEventListener == "function") + element.addEventListener(eventType, handler, false); + else if (typeof element.attachEvent != "undefined") + element.attachEvent("on" + eventType, function (event) { + handler.call(element, event); + }); + else + throw new Error("Browser doesn't support addEventListener or attachEvent"); + }, + + triggerEvent: function (element, eventType) { + if (!(element && element.nodeType)) + throw new Error("element must be a DOM node when calling triggerEvent"); + + if (typeof jQuery != "undefined") { + var eventData = []; + if (isClickOnCheckableElement(element, eventType)) { + // Work around the jQuery "click events on checkboxes" issue described above by storing the original checked state before triggering the handler + eventData.push({ checkedStateBeforeEvent: element.checked }); + } + jQuery(element)['trigger'](eventType, eventData); + } else if (typeof document.createEvent == "function") { + if (typeof element.dispatchEvent == "function") { + var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents"; + var event = document.createEvent(eventCategory); + event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element); + element.dispatchEvent(event); + } + else + throw new Error("The supplied element doesn't support dispatchEvent"); + } else if (typeof element.fireEvent != "undefined") { + // Unlike other browsers, IE doesn't change the checked state of checkboxes/radiobuttons when you trigger their "click" event + // so to make it consistent, we'll do it manually here + if (eventType == "click") { + if ((element.tagName == "INPUT") && ((element.type.toLowerCase() == "checkbox") || (element.type.toLowerCase() == "radio"))) + element.checked = element.checked !== true; + } + element.fireEvent("on" + eventType); + } + else + throw new Error("Browser doesn't support triggering events"); + }, + + unwrapObservable: function (value) { + return ko.isObservable(value) ? value() : value; + }, + + domNodeHasCssClass: function (node, className) { + var currentClassNames = (node.className || "").split(/\s+/); + return ko.utils.arrayIndexOf(currentClassNames, className) >= 0; + }, + + toggleDomNodeCssClass: function (node, className, shouldHaveClass) { + var hasClass = ko.utils.domNodeHasCssClass(node, className); + if (shouldHaveClass && !hasClass) { + node.className = (node.className || "") + " " + className; + } else if (hasClass && !shouldHaveClass) { + var currentClassNames = (node.className || "").split(/\s+/); + var newClassName = ""; + for (var i = 0; i < currentClassNames.length; i++) + if (currentClassNames[i] != className) + newClassName += currentClassNames[i] + " "; + node.className = ko.utils.stringTrim(newClassName); + } + }, + + outerHTML: function(node) { + // For Chrome on non-text nodes + // (Although IE supports outerHTML, the way it formats HTML is inconsistent - sometimes closing tags are omitted, sometimes not. That caused https://github.com/SteveSanderson/knockout/issues/212.) + if (ieVersion === undefined) { + var nativeOuterHtml = node.outerHTML; + if (typeof nativeOuterHtml == "string") + return nativeOuterHtml; + } + + // Other browsers + var dummyContainer = window.document.createElement("div"); + dummyContainer.appendChild(node.cloneNode(true)); + return dummyContainer.innerHTML; + }, + + setTextContent: function(element, textContent) { + var value = ko.utils.unwrapObservable(textContent); + if ((value === null) || (value === undefined)) + value = ""; + + 'innerText' in element ? element.innerText = value + : element.textContent = value; + + if (ieVersion >= 9) { + // Believe it or not, this actually fixes an IE9 rendering bug. Insane. https://github.com/SteveSanderson/knockout/issues/209 + element.innerHTML = element.innerHTML; + } + }, + + range: function (min, max) { + min = ko.utils.unwrapObservable(min); + max = ko.utils.unwrapObservable(max); + var result = []; + for (var i = min; i <= max; i++) + result.push(i); + return result; + }, + + makeArray: function(arrayLikeObject) { + var result = []; + for (var i = 0, j = arrayLikeObject.length; i < j; i++) { + result.push(arrayLikeObject[i]); + }; + return result; + }, + + isIe6 : isIe6, + isIe7 : isIe7, + + getFormFields: function(form, fieldName) { + var fields = ko.utils.makeArray(form.getElementsByTagName("INPUT")).concat(ko.utils.makeArray(form.getElementsByTagName("TEXTAREA"))); + var isMatchingField = (typeof fieldName == 'string') + ? function(field) { return field.name === fieldName } + : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate + var matches = []; + for (var i = fields.length - 1; i >= 0; i--) { + if (isMatchingField(fields[i])) + matches.push(fields[i]); + }; + return matches; + }, + + parseJson: function (jsonString) { + if (typeof jsonString == "string") { + jsonString = ko.utils.stringTrim(jsonString); + if (jsonString) { + if (window.JSON && window.JSON.parse) // Use native parsing where available + return window.JSON.parse(jsonString); + return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers + } + } + return null; + }, + + stringifyJson: function (data) { + if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined")) + throw new Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js"); + return JSON.stringify(ko.utils.unwrapObservable(data)); + }, + + postJson: function (urlOrForm, data, options) { + options = options || {}; + var params = options['params'] || {}; + var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost; + var url = urlOrForm; + + // If we were given a form, use its 'action' URL and pick out any requested field values + if((typeof urlOrForm == 'object') && (urlOrForm.tagName == "FORM")) { + var originalForm = urlOrForm; + url = originalForm.action; + for (var i = includeFields.length - 1; i >= 0; i--) { + var fields = ko.utils.getFormFields(originalForm, includeFields[i]); + for (var j = fields.length - 1; j >= 0; j--) + params[fields[j].name] = fields[j].value; + } + } + + data = ko.utils.unwrapObservable(data); + var form = document.createElement("FORM"); + form.style.display = "none"; + form.action = url; + form.method = "post"; + for (var key in data) { + var input = document.createElement("INPUT"); + input.name = key; + input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key])); + form.appendChild(input); + } + for (var key in params) { + var input = document.createElement("INPUT"); + input.name = key; + input.value = params[key]; + form.appendChild(input); + } + document.body.appendChild(form); + options['submitter'] ? options['submitter'](form) : form.submit(); + setTimeout(function () { form.parentNode.removeChild(form); }, 0); + } + } +})(); + +ko.exportSymbol('ko.utils', ko.utils); +ko.utils.arrayForEach([ + ['arrayForEach', ko.utils.arrayForEach], + ['arrayFirst', ko.utils.arrayFirst], + ['arrayFilter', ko.utils.arrayFilter], + ['arrayGetDistinctValues', ko.utils.arrayGetDistinctValues], + ['arrayIndexOf', ko.utils.arrayIndexOf], + ['arrayMap', ko.utils.arrayMap], + ['arrayPushAll', ko.utils.arrayPushAll], + ['arrayRemoveItem', ko.utils.arrayRemoveItem], + ['extend', ko.utils.extend], + ['fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost], + ['getFormFields', ko.utils.getFormFields], + ['postJson', ko.utils.postJson], + ['parseJson', ko.utils.parseJson], + ['registerEventHandler', ko.utils.registerEventHandler], + ['stringifyJson', ko.utils.stringifyJson], + ['range', ko.utils.range], + ['toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass], + ['triggerEvent', ko.utils.triggerEvent], + ['unwrapObservable', ko.utils.unwrapObservable] +], function(item) { + ko.exportSymbol('ko.utils.' + item[0], item[1]); +}); + +if (!Function.prototype['bind']) { + // Function.prototype.bind is a standard part of ECMAScript 5th Edition (December 2009, http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf) + // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js + Function.prototype['bind'] = function (object) { + var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift(); + return function () { + return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments))); + }; + }; +} +ko.utils.domData = new (function () { + var uniqueId = 0; + var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime(); + var dataStore = {}; + return { + get: function (node, key) { + var allDataForNode = ko.utils.domData.getAll(node, false); + return allDataForNode === undefined ? undefined : allDataForNode[key]; + }, + set: function (node, key, value) { + if (value === undefined) { + // Make sure we don't actually create a new domData key if we are actually deleting a value + if (ko.utils.domData.getAll(node, false) === undefined) + return; + } + var allDataForNode = ko.utils.domData.getAll(node, true); + allDataForNode[key] = value; + }, + getAll: function (node, createIfNotFound) { + var dataStoreKey = node[dataStoreKeyExpandoPropertyName]; + var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null"); + if (!hasExistingDataStore) { + if (!createIfNotFound) + return undefined; + dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++; + dataStore[dataStoreKey] = {}; + } + return dataStore[dataStoreKey]; + }, + clear: function (node) { + var dataStoreKey = node[dataStoreKeyExpandoPropertyName]; + if (dataStoreKey) { + delete dataStore[dataStoreKey]; + node[dataStoreKeyExpandoPropertyName] = null; + } + } + } +})(); + +ko.exportSymbol('ko.utils.domData', ko.utils.domData); +ko.exportSymbol('ko.utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully +ko.utils.domNodeDisposal = new (function () { + var domDataKey = "__ko_domNodeDisposal__" + (new Date).getTime(); + + function getDisposeCallbacksCollection(node, createIfNotFound) { + var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey); + if ((allDisposeCallbacks === undefined) && createIfNotFound) { + allDisposeCallbacks = []; + ko.utils.domData.set(node, domDataKey, allDisposeCallbacks); + } + return allDisposeCallbacks; + } + function destroyCallbacksCollection(node) { + ko.utils.domData.set(node, domDataKey, undefined); + } + + function cleanSingleNode(node) { + // Run all the dispose callbacks + var callbacks = getDisposeCallbacksCollection(node, false); + if (callbacks) { + callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves) + for (var i = 0; i < callbacks.length; i++) + callbacks[i](node); + } + + // Also erase the DOM data + ko.utils.domData.clear(node); + + // Special support for jQuery here because it's so commonly used. + // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData + // so notify it to tear down any resources associated with the node & descendants here. + if ((typeof jQuery == "function") && (typeof jQuery['cleanData'] == "function")) + jQuery['cleanData']([node]); + } + + return { + addDisposeCallback : function(node, callback) { + if (typeof callback != "function") + throw new Error("Callback must be a function"); + getDisposeCallbacksCollection(node, true).push(callback); + }, + + removeDisposeCallback : function(node, callback) { + var callbacksCollection = getDisposeCallbacksCollection(node, false); + if (callbacksCollection) { + ko.utils.arrayRemoveItem(callbacksCollection, callback); + if (callbacksCollection.length == 0) + destroyCallbacksCollection(node); + } + }, + + cleanNode : function(node) { + if ((node.nodeType != 1) && (node.nodeType != 9)) + return; + cleanSingleNode(node); + + // Clone the descendants list in case it changes during iteration + var descendants = []; + ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*")); + for (var i = 0, j = descendants.length; i < j; i++) + cleanSingleNode(descendants[i]); + }, + + removeNode : function(node) { + ko.cleanNode(node); + if (node.parentNode) + node.parentNode.removeChild(node); + } + } +})(); +ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience +ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience +ko.exportSymbol('ko.cleanNode', ko.cleanNode); +ko.exportSymbol('ko.removeNode', ko.removeNode); +ko.exportSymbol('ko.utils.domNodeDisposal', ko.utils.domNodeDisposal); +ko.exportSymbol('ko.utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback); +ko.exportSymbol('ko.utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);(function () { + var leadingCommentRegex = /^(\s*)/; + + function simpleHtmlParse(html) { + // Based on jQuery's "clean" function, but only accounting for table-related elements. + // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly + + // Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of + // a descendant node. For example: "
abc
" will get parsed as "
abc
" + // This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node + // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present. + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div"); + + // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column + var wrap = tags.match(/^<(thead|tbody|tfoot)/) && [1, "", "
"] || + !tags.indexOf("", ""] || + (!tags.indexOf("", ""] || + /* anything else */ [0, "", ""]; + + // Go to html and back, then peel off extra wrappers + // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness. + var markup = "ignored
" + wrap[1] + html + wrap[2] + "
"; + if (typeof window['innerShiv'] == "function") { + div.appendChild(window['innerShiv'](markup)); + } else { + div.innerHTML = markup; + } + + // Move to the right depth + while (wrap[0]--) + div = div.lastChild; + + return ko.utils.makeArray(div.lastChild.childNodes); + } + + function jQueryHtmlParse(html) { + var elems = jQuery['clean']([html]); + + // As of jQuery 1.7.1, jQuery parses the HTML by appending it to some dummy parent nodes held in an in-memory document fragment. + // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time. + // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment. + if (elems && elems[0]) { + // Find the top-most parent element that's a direct child of a document fragment + var elem = elems[0]; + while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */) + elem = elem.parentNode; + // ... then detach it + if (elem.parentNode) + elem.parentNode.removeChild(elem); + } + + return elems; + } + + ko.utils.parseHtmlFragment = function(html) { + return typeof jQuery != 'undefined' ? jQueryHtmlParse(html) // As below, benefit from jQuery's optimisations where possible + : simpleHtmlParse(html); // ... otherwise, this simple logic will do in most common cases. + }; + + ko.utils.setHtml = function(node, html) { + ko.utils.emptyDomNode(node); + + if ((html !== null) && (html !== undefined)) { + if (typeof html != 'string') + html = html.toString(); + + // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments, + // for example elements which are not normally allowed to exist on their own. + // If you've referenced jQuery we'll use that rather than duplicating its code. + if (typeof jQuery != 'undefined') { + jQuery(node)['html'](html); + } else { + // ... otherwise, use KO's own parsing logic. + var parsedNodes = ko.utils.parseHtmlFragment(html); + for (var i = 0; i < parsedNodes.length; i++) + node.appendChild(parsedNodes[i]); + } + } + }; +})(); + +ko.exportSymbol('ko.utils.parseHtmlFragment', ko.utils.parseHtmlFragment); +ko.exportSymbol('ko.utils.setHtml', ko.utils.setHtml); +ko.memoization = (function () { + var memos = {}; + + function randomMax8HexChars() { + return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); + } + function generateRandomId() { + return randomMax8HexChars() + randomMax8HexChars(); + } + function findMemoNodes(rootNode, appendToArray) { + if (!rootNode) + return; + if (rootNode.nodeType == 8) { + var memoId = ko.memoization.parseMemoText(rootNode.nodeValue); + if (memoId != null) + appendToArray.push({ domNode: rootNode, memoId: memoId }); + } else if (rootNode.nodeType == 1) { + for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++) + findMemoNodes(childNodes[i], appendToArray); + } + } + + return { + memoize: function (callback) { + if (typeof callback != "function") + throw new Error("You can only pass a function to ko.memoization.memoize()"); + var memoId = generateRandomId(); + memos[memoId] = callback; + return ""; + }, + + unmemoize: function (memoId, callbackParams) { + var callback = memos[memoId]; + if (callback === undefined) + throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized."); + try { + callback.apply(null, callbackParams || []); + return true; + } + finally { delete memos[memoId]; } + }, + + unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) { + var memos = []; + findMemoNodes(domNode, memos); + for (var i = 0, j = memos.length; i < j; i++) { + var node = memos[i].domNode; + var combinedParams = [node]; + if (extraCallbackParamsArray) + ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray); + ko.memoization.unmemoize(memos[i].memoId, combinedParams); + node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again + if (node.parentNode) + node.parentNode.removeChild(node); // If possible, erase it totally (not always possible - someone else might just hold a reference to it then call unmemoizeDomNodeAndDescendants again) + } + }, + + parseMemoText: function (memoText) { + var match = memoText.match(/^\[ko_memo\:(.*?)\]$/); + return match ? match[1] : null; + } + }; +})(); + +ko.exportSymbol('ko.memoization', ko.memoization); +ko.exportSymbol('ko.memoization.memoize', ko.memoization.memoize); +ko.exportSymbol('ko.memoization.unmemoize', ko.memoization.unmemoize); +ko.exportSymbol('ko.memoization.parseMemoText', ko.memoization.parseMemoText); +ko.exportSymbol('ko.memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants); +ko.extenders = { + 'throttle': function(target, timeout) { + // Throttling means two things: + + // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies + // notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate + target['throttleEvaluation'] = timeout; + + // (2) For writable targets (observables, or writable dependent observables), we throttle *writes* + // so the target cannot change value synchronously or faster than a certain rate + var writeTimeoutInstance = null; + return ko.dependentObservable({ + 'read': target, + 'write': function(value) { + clearTimeout(writeTimeoutInstance); + writeTimeoutInstance = setTimeout(function() { + target(value); + }, timeout); + } + }); + }, + + 'notify': function(target, notifyWhen) { + target["equalityComparer"] = notifyWhen == "always" + ? function() { return false } // Treat all values as not equal + : ko.observable["fn"]["equalityComparer"]; + return target; + } +}; + +function applyExtenders(requestedExtenders) { + var target = this; + if (requestedExtenders) { + for (var key in requestedExtenders) { + var extenderHandler = ko.extenders[key]; + if (typeof extenderHandler == 'function') { + target = extenderHandler(target, requestedExtenders[key]); + } + } + } + return target; +} + +ko.exportSymbol('ko.extenders', ko.extenders); +ko.subscription = function (callback, disposeCallback) { + this.callback = callback; + this.disposeCallback = disposeCallback; + ko.exportProperty(this, 'dispose', this.dispose); +}; +ko.subscription.prototype.dispose = function () { + this.isDisposed = true; + this.disposeCallback(); +}; + +ko.subscribable = function () { + this._subscriptions = {}; + + ko.utils.extend(this, ko.subscribable['fn']); + ko.exportProperty(this, 'subscribe', this.subscribe); + ko.exportProperty(this, 'extend', this.extend); + ko.exportProperty(this, 'getSubscriptionsCount', this.getSubscriptionsCount); +} + +var defaultEvent = "change"; + +ko.subscribable['fn'] = { + subscribe: function (callback, callbackTarget, event) { + event = event || defaultEvent; + var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback; + + var subscription = new ko.subscription(boundCallback, function () { + ko.utils.arrayRemoveItem(this._subscriptions[event], subscription); + }.bind(this)); + + if (!this._subscriptions[event]) + this._subscriptions[event] = []; + this._subscriptions[event].push(subscription); + return subscription; + }, + + "notifySubscribers": function (valueToNotify, event) { + event = event || defaultEvent; + if (this._subscriptions[event]) { + ko.utils.arrayForEach(this._subscriptions[event].slice(0), function (subscription) { + // In case a subscription was disposed during the arrayForEach cycle, check + // for isDisposed on each subscription before invoking its callback + if (subscription && (subscription.isDisposed !== true)) + subscription.callback(valueToNotify); + }); + } + }, + + getSubscriptionsCount: function () { + var total = 0; + for (var eventName in this._subscriptions) { + if (this._subscriptions.hasOwnProperty(eventName)) + total += this._subscriptions[eventName].length; + } + return total; + }, + + extend: applyExtenders +}; + + +ko.isSubscribable = function (instance) { + return typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function"; +}; + +ko.exportSymbol('ko.subscribable', ko.subscribable); +ko.exportSymbol('ko.isSubscribable', ko.isSubscribable); + +ko.dependencyDetection = (function () { + var _frames = []; + + return { + begin: function (callback) { + _frames.push({ callback: callback, distinctDependencies:[] }); + }, + + end: function () { + _frames.pop(); + }, + + registerDependency: function (subscribable) { + if (!ko.isSubscribable(subscribable)) + throw "Only subscribable things can act as dependencies"; + if (_frames.length > 0) { + var topFrame = _frames[_frames.length - 1]; + if (ko.utils.arrayIndexOf(topFrame.distinctDependencies, subscribable) >= 0) + return; + topFrame.distinctDependencies.push(subscribable); + topFrame.callback(subscribable); + } + } + }; +})();var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true }; + +ko.observable = function (initialValue) { + var _latestValue = initialValue; + + function observable() { + if (arguments.length > 0) { + // Write + + // Ignore writes if the value hasn't changed + if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) { + observable.valueWillMutate(); + _latestValue = arguments[0]; + observable.valueHasMutated(); + } + return this; // Permits chained assignments + } + else { + // Read + ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation + return _latestValue; + } + } + ko.subscribable.call(observable); + observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); } + observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); } + ko.utils.extend(observable, ko.observable['fn']); + + ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated); + ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate); + + return observable; +} + +ko.observable['fn'] = { + __ko_proto__: ko.observable, + + "equalityComparer": function valuesArePrimitiveAndEqual(a, b) { + var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes); + return oldValueIsPrimitive ? (a === b) : false; + } +}; + +ko.isObservable = function (instance) { + if ((instance === null) || (instance === undefined) || (instance.__ko_proto__ === undefined)) return false; + if (instance.__ko_proto__ === ko.observable) return true; + return ko.isObservable(instance.__ko_proto__); // Walk the prototype chain +} +ko.isWriteableObservable = function (instance) { + // Observable + if ((typeof instance == "function") && instance.__ko_proto__ === ko.observable) + return true; + // Writeable dependent observable + if ((typeof instance == "function") && (instance.__ko_proto__ === ko.dependentObservable) && (instance.hasWriteFunction)) + return true; + // Anything else + return false; +} + + +ko.exportSymbol('ko.observable', ko.observable); +ko.exportSymbol('ko.isObservable', ko.isObservable); +ko.exportSymbol('ko.isWriteableObservable', ko.isWriteableObservable); +ko.observableArray = function (initialValues) { + if (arguments.length == 0) { + // Zero-parameter constructor initializes to empty array + initialValues = []; + } + if ((initialValues !== null) && (initialValues !== undefined) && !('length' in initialValues)) + throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined."); + + var result = new ko.observable(initialValues); + ko.utils.extend(result, ko.observableArray['fn']); + + ko.exportProperty(result, "remove", result.remove); + ko.exportProperty(result, "removeAll", result.removeAll); + ko.exportProperty(result, "destroy", result.destroy); + ko.exportProperty(result, "destroyAll", result.destroyAll); + ko.exportProperty(result, "indexOf", result.indexOf); + ko.exportProperty(result, "replace", result.replace); + + return result; +} + +ko.observableArray['fn'] = { + remove: function (valueOrPredicate) { + var underlyingArray = this(); + var removedValues = []; + var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; }; + for (var i = 0; i < underlyingArray.length; i++) { + var value = underlyingArray[i]; + if (predicate(value)) { + if (removedValues.length === 0) { + this.valueWillMutate(); + } + removedValues.push(value); + underlyingArray.splice(i, 1); + i--; + } + } + if (removedValues.length) { + this.valueHasMutated(); + } + return removedValues; + }, + + removeAll: function (arrayOfValues) { + // If you passed zero args, we remove everything + if (arrayOfValues === undefined) { + var underlyingArray = this(); + var allValues = underlyingArray.slice(0); + this.valueWillMutate(); + underlyingArray.splice(0, underlyingArray.length); + this.valueHasMutated(); + return allValues; + } + // If you passed an arg, we interpret it as an array of entries to remove + if (!arrayOfValues) + return []; + return this.remove(function (value) { + return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0; + }); + }, + + destroy: function (valueOrPredicate) { + var underlyingArray = this(); + var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; }; + this.valueWillMutate(); + for (var i = underlyingArray.length - 1; i >= 0; i--) { + var value = underlyingArray[i]; + if (predicate(value)) + underlyingArray[i]["_destroy"] = true; + } + this.valueHasMutated(); + }, + + destroyAll: function (arrayOfValues) { + // If you passed zero args, we destroy everything + if (arrayOfValues === undefined) + return this.destroy(function() { return true }); + + // If you passed an arg, we interpret it as an array of entries to destroy + if (!arrayOfValues) + return []; + return this.destroy(function (value) { + return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0; + }); + }, + + indexOf: function (item) { + var underlyingArray = this(); + return ko.utils.arrayIndexOf(underlyingArray, item); + }, + + replace: function(oldItem, newItem) { + var index = this.indexOf(oldItem); + if (index >= 0) { + this.valueWillMutate(); + this()[index] = newItem; + this.valueHasMutated(); + } + } +} + +// Populate ko.observableArray.fn with read/write functions from native arrays +ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) { + ko.observableArray['fn'][methodName] = function () { + var underlyingArray = this(); + this.valueWillMutate(); + var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments); + this.valueHasMutated(); + return methodCallResult; + }; +}); + +// Populate ko.observableArray.fn with read-only functions from native arrays +ko.utils.arrayForEach(["slice"], function (methodName) { + ko.observableArray['fn'][methodName] = function () { + var underlyingArray = this(); + return underlyingArray[methodName].apply(underlyingArray, arguments); + }; +}); + +ko.exportSymbol('ko.observableArray', ko.observableArray); +function prepareOptions(evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) { + if (evaluatorFunctionOrOptions && typeof evaluatorFunctionOrOptions == "object") { + // Single-parameter syntax - everything is on this "options" param + options = evaluatorFunctionOrOptions; + } else { + // Multi-parameter syntax - construct the options according to the params passed + options = options || {}; + options["read"] = evaluatorFunctionOrOptions || options["read"]; + } + // By here, "options" is always non-null + + if (typeof options["read"] != "function") + throw "Pass a function that returns the value of the dependentObservable"; + + return options; +} + +ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) { + var _latestValue, + _hasBeenEvaluated = false, + options = prepareOptions(evaluatorFunctionOrOptions, evaluatorFunctionTarget, options); + + // Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values + // (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(), + // plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.) + var disposeWhenNodeIsRemoved = (typeof options["disposeWhenNodeIsRemoved"] == "object") ? options["disposeWhenNodeIsRemoved"] : null; + var disposeWhenNodeIsRemovedCallback = null; + if (disposeWhenNodeIsRemoved) { + disposeWhenNodeIsRemovedCallback = function() { dependentObservable.dispose() }; + ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, disposeWhenNodeIsRemovedCallback); + var existingDisposeWhenFunction = options["disposeWhen"]; + options["disposeWhen"] = function () { + return (!ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved)) + || ((typeof existingDisposeWhenFunction == "function") && existingDisposeWhenFunction()); + } + } + + var _subscriptionsToDependencies = []; + function disposeAllSubscriptionsToDependencies() { + ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) { + subscription.dispose(); + }); + _subscriptionsToDependencies = []; + } + + var evaluationTimeoutInstance = null; + function evaluatePossiblyAsync() { + var throttleEvaluationTimeout = dependentObservable['throttleEvaluation']; + if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) { + clearTimeout(evaluationTimeoutInstance); + evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout); + } else + evaluateImmediate(); + } + + function evaluateImmediate() { + // Don't dispose on first evaluation, because the "disposeWhen" callback might + // e.g., dispose when the associated DOM element isn't in the doc, and it's not + // going to be in the doc until *after* the first evaluation + if ((_hasBeenEvaluated) && typeof options["disposeWhen"] == "function") { + if (options["disposeWhen"]()) { + dependentObservable.dispose(); + return; + } + } + + try { + disposeAllSubscriptionsToDependencies(); + ko.dependencyDetection.begin(function(subscribable) { + _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync)); + }); + var valueForThis = options["owner"] || evaluatorFunctionTarget; // If undefined, it will default to "window" by convention. This might change in the future. + var newValue = options["read"].call(valueForThis); + dependentObservable["notifySubscribers"](_latestValue, "beforeChange"); + _latestValue = newValue; + } finally { + ko.dependencyDetection.end(); + } + + dependentObservable["notifySubscribers"](_latestValue); + _hasBeenEvaluated = true; + } + + function dependentObservable() { + if (arguments.length > 0) { + if (typeof options["write"] === "function") { + // Writing a value + var valueForThis = options["owner"] || evaluatorFunctionTarget; // If undefined, it will default to "window" by convention. This might change in the future. + options["write"].apply(valueForThis, arguments); + } else { + throw "Cannot write a value to a dependentObservable unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters."; + } + } else { + // Reading the value + if (!_hasBeenEvaluated) + evaluateImmediate(); + ko.dependencyDetection.registerDependency(dependentObservable); + return _latestValue; + } + } + dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; } + dependentObservable.hasWriteFunction = typeof options["write"] === "function"; + dependentObservable.dispose = function () { + if (disposeWhenNodeIsRemoved) + ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, disposeWhenNodeIsRemovedCallback); + disposeAllSubscriptionsToDependencies(); + }; + + ko.subscribable.call(dependentObservable); + ko.utils.extend(dependentObservable, ko.dependentObservable['fn']); + + if (options['deferEvaluation'] !== true) + evaluateImmediate(); + + ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose); + ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount); + + return dependentObservable; +}; + +ko.dependentObservable['fn'] = { + __ko_proto__: ko.dependentObservable +}; + +ko.dependentObservable.__ko_proto__ = ko.observable; + +ko.exportSymbol('ko.dependentObservable', ko.dependentObservable); +ko.exportSymbol('ko.computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable" +(function() { + var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle) + + ko.toJS = function(rootObject) { + if (arguments.length == 0) + throw new Error("When calling ko.toJS, pass the object you want to convert."); + + // We just unwrap everything at every level in the object graph + return mapJsObjectGraph(rootObject, function(valueToMap) { + // Loop because an observable's value might in turn be another observable wrapper + for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++) + valueToMap = valueToMap(); + return valueToMap; + }); + }; + + ko.toJSON = function(rootObject) { + var plainJavaScriptObject = ko.toJS(rootObject); + return ko.utils.stringifyJson(plainJavaScriptObject); + }; + + function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) { + visitedObjects = visitedObjects || new objectLookup(); + + rootObject = mapInputCallback(rootObject); + var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date)); + if (!canHaveProperties) + return rootObject; + + var outputProperties = rootObject instanceof Array ? [] : {}; + visitedObjects.save(rootObject, outputProperties); + + visitPropertiesOrArrayEntries(rootObject, function(indexer) { + var propertyValue = mapInputCallback(rootObject[indexer]); + + switch (typeof propertyValue) { + case "boolean": + case "number": + case "string": + case "function": + outputProperties[indexer] = propertyValue; + break; + case "object": + case "undefined": + var previouslyMappedValue = visitedObjects.get(propertyValue); + outputProperties[indexer] = (previouslyMappedValue !== undefined) + ? previouslyMappedValue + : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects); + break; + } + }); + + return outputProperties; + } + + function visitPropertiesOrArrayEntries(rootObject, visitorCallback) { + if (rootObject instanceof Array) { + for (var i = 0; i < rootObject.length; i++) + visitorCallback(i); + } else { + for (var propertyName in rootObject) + visitorCallback(propertyName); + } + }; + + function objectLookup() { + var keys = []; + var values = []; + this.save = function(key, value) { + var existingIndex = ko.utils.arrayIndexOf(keys, key); + if (existingIndex >= 0) + values[existingIndex] = value; + else { + keys.push(key); + values.push(value); + } + }; + this.get = function(key) { + var existingIndex = ko.utils.arrayIndexOf(keys, key); + return (existingIndex >= 0) ? values[existingIndex] : undefined; + }; + }; +})(); + +ko.exportSymbol('ko.toJS', ko.toJS); +ko.exportSymbol('ko.toJSON', ko.toJSON);(function () { + var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__'; + + // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values + // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values + // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns. + ko.selectExtensions = { + readValue : function(element) { + if (element.tagName == 'OPTION') { + if (element[hasDomDataExpandoProperty] === true) + return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey); + return element.getAttribute("value"); + } else if (element.tagName == 'SELECT') + return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined; + else + return element.value; + }, + + writeValue: function(element, value) { + if (element.tagName == 'OPTION') { + switch(typeof value) { + case "string": + ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined); + if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node + delete element[hasDomDataExpandoProperty]; + } + element.value = value; + break; + default: + // Store arbitrary object using DomData + ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value); + element[hasDomDataExpandoProperty] = true; + + // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value. + element.value = typeof value === "number" ? value : ""; + break; + } + } else if (element.tagName == 'SELECT') { + for (var i = element.options.length - 1; i >= 0; i--) { + if (ko.selectExtensions.readValue(element.options[i]) == value) { + element.selectedIndex = i; + break; + } + } + } else { + if ((value === null) || (value === undefined)) + value = ""; + element.value = value; + } + } + }; +})(); + +ko.exportSymbol('ko.selectExtensions', ko.selectExtensions); +ko.exportSymbol('ko.selectExtensions.readValue', ko.selectExtensions.readValue); +ko.exportSymbol('ko.selectExtensions.writeValue', ko.selectExtensions.writeValue); + +ko.jsonExpressionRewriting = (function () { + var restoreCapturedTokensRegex = /\@ko_token_(\d+)\@/g; + var javaScriptAssignmentTarget = /^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i; + var javaScriptReservedWords = ["true", "false"]; + + function restoreTokens(string, tokens) { + var prevValue = null; + while (string != prevValue) { // Keep restoring tokens until it no longer makes a difference (they may be nested) + prevValue = string; + string = string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) { + return tokens[tokenIndex]; + }); + } + return string; + } + + function isWriteableValue(expression) { + if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0) + return false; + return expression.match(javaScriptAssignmentTarget) !== null; + } + + function ensureQuoted(key) { + var trimmedKey = ko.utils.stringTrim(key); + switch (trimmedKey.length && trimmedKey.charAt(0)) { + case "'": + case '"': + return key; + default: + return "'" + trimmedKey + "'"; + } + } + + return { + bindingRewriteValidators: [], + + parseObjectLiteral: function(objectLiteralString) { + // A full tokeniser+lexer would add too much weight to this library, so here's a simple parser + // that is sufficient just to split an object literal string into a set of top-level key-value pairs + + var str = ko.utils.stringTrim(objectLiteralString); + if (str.length < 3) + return []; + if (str.charAt(0) === "{")// Ignore any braces surrounding the whole object literal + str = str.substring(1, str.length - 1); + + // Pull out any string literals and regex literals + var tokens = []; + var tokenStart = null, tokenEndChar; + for (var position = 0; position < str.length; position++) { + var c = str.charAt(position); + if (tokenStart === null) { + switch (c) { + case '"': + case "'": + case "/": + tokenStart = position; + tokenEndChar = c; + break; + } + } else if ((c == tokenEndChar) && (str.charAt(position - 1) !== "\\")) { + var token = str.substring(tokenStart, position + 1); + tokens.push(token); + var replacement = "@ko_token_" + (tokens.length - 1) + "@"; + str = str.substring(0, tokenStart) + replacement + str.substring(position + 1); + position -= (token.length - replacement.length); + tokenStart = null; + } + } + + // Next pull out balanced paren, brace, and bracket blocks + tokenStart = null; + tokenEndChar = null; + var tokenDepth = 0, tokenStartChar = null; + for (var position = 0; position < str.length; position++) { + var c = str.charAt(position); + if (tokenStart === null) { + switch (c) { + case "{": tokenStart = position; tokenStartChar = c; + tokenEndChar = "}"; + break; + case "(": tokenStart = position; tokenStartChar = c; + tokenEndChar = ")"; + break; + case "[": tokenStart = position; tokenStartChar = c; + tokenEndChar = "]"; + break; + } + } + + if (c === tokenStartChar) + tokenDepth++; + else if (c === tokenEndChar) { + tokenDepth--; + if (tokenDepth === 0) { + var token = str.substring(tokenStart, position + 1); + tokens.push(token); + var replacement = "@ko_token_" + (tokens.length - 1) + "@"; + str = str.substring(0, tokenStart) + replacement + str.substring(position + 1); + position -= (token.length - replacement.length); + tokenStart = null; + } + } + } + + // Now we can safely split on commas to get the key/value pairs + var result = []; + var keyValuePairs = str.split(","); + for (var i = 0, j = keyValuePairs.length; i < j; i++) { + var pair = keyValuePairs[i]; + var colonPos = pair.indexOf(":"); + if ((colonPos > 0) && (colonPos < pair.length - 1)) { + var key = pair.substring(0, colonPos); + var value = pair.substring(colonPos + 1); + result.push({ 'key': restoreTokens(key, tokens), 'value': restoreTokens(value, tokens) }); + } else { + result.push({ 'unknown': restoreTokens(pair, tokens) }); + } + } + return result; + }, + + insertPropertyAccessorsIntoJson: function (objectLiteralStringOrKeyValueArray) { + var keyValueArray = typeof objectLiteralStringOrKeyValueArray === "string" + ? ko.jsonExpressionRewriting.parseObjectLiteral(objectLiteralStringOrKeyValueArray) + : objectLiteralStringOrKeyValueArray; + var resultStrings = [], propertyAccessorResultStrings = []; + + var keyValueEntry; + for (var i = 0; keyValueEntry = keyValueArray[i]; i++) { + if (resultStrings.length > 0) + resultStrings.push(","); + + if (keyValueEntry['key']) { + var quotedKey = ensureQuoted(keyValueEntry['key']), val = keyValueEntry['value']; + resultStrings.push(quotedKey); + resultStrings.push(":"); + resultStrings.push(val); + + if (isWriteableValue(ko.utils.stringTrim(val))) { + if (propertyAccessorResultStrings.length > 0) + propertyAccessorResultStrings.push(", "); + propertyAccessorResultStrings.push(quotedKey + " : function(__ko_value) { " + val + " = __ko_value; }"); + } + } else if (keyValueEntry['unknown']) { + resultStrings.push(keyValueEntry['unknown']); + } + } + + var combinedResult = resultStrings.join(""); + if (propertyAccessorResultStrings.length > 0) { + var allPropertyAccessors = propertyAccessorResultStrings.join(""); + combinedResult = combinedResult + ", '_ko_property_writers' : { " + allPropertyAccessors + " } "; + } + + return combinedResult; + }, + + keyValueArrayContainsKey: function(keyValueArray, key) { + for (var i = 0; i < keyValueArray.length; i++) + if (ko.utils.stringTrim(keyValueArray[i]['key']) == key) + return true; + return false; + } + }; +})(); + +ko.exportSymbol('ko.jsonExpressionRewriting', ko.jsonExpressionRewriting); +ko.exportSymbol('ko.jsonExpressionRewriting.bindingRewriteValidators', ko.jsonExpressionRewriting.bindingRewriteValidators); +ko.exportSymbol('ko.jsonExpressionRewriting.parseObjectLiteral', ko.jsonExpressionRewriting.parseObjectLiteral); +ko.exportSymbol('ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson); +(function() { + // "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes + // may be used to represent hierarchy (in addition to the DOM's natural hierarchy). + // If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state + // of that virtual hierarchy + // + // The point of all this is to support containerless templates (e.g., blah) + // without having to scatter special cases all over the binding and templating code. + + // IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186) + // but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property. + // So, use node.text where available, and node.nodeValue elsewhere + var commentNodesHaveTextProperty = document.createComment("test").text === ""; + + var startCommentRegex = commentNodesHaveTextProperty ? /^$/ : /^\s*ko\s+(.*\:.*)\s*$/; + var endCommentRegex = commentNodesHaveTextProperty ? /^$/ : /^\s*\/ko\s*$/; + var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true }; + + function isStartComment(node) { + return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex); + } + + function isEndComment(node) { + return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(endCommentRegex); + } + + function getVirtualChildren(startComment, allowUnbalanced) { + var currentNode = startComment; + var depth = 1; + var children = []; + while (currentNode = currentNode.nextSibling) { + if (isEndComment(currentNode)) { + depth--; + if (depth === 0) + return children; + } + + children.push(currentNode); + + if (isStartComment(currentNode)) + depth++; + } + if (!allowUnbalanced) + throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue); + return null; + } + + function getMatchingEndComment(startComment, allowUnbalanced) { + var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced); + if (allVirtualChildren) { + if (allVirtualChildren.length > 0) + return allVirtualChildren[allVirtualChildren.length - 1].nextSibling; + return startComment.nextSibling; + } else + return null; // Must have no matching end comment, and allowUnbalanced is true + } + + function nodeArrayToText(nodeArray, cleanNodes) { + var texts = []; + for (var i = 0, j = nodeArray.length; i < j; i++) { + if (cleanNodes) + ko.utils.domNodeDisposal.cleanNode(nodeArray[i]); + texts.push(ko.utils.outerHTML(nodeArray[i])); + } + return String.prototype.concat.apply("", texts); + } + + function getUnbalancedChildTags(node) { + // e.g., from
OK
Another, returns: Another + // from
OK
, returns: + var childNode = node.firstChild, captureRemaining = null; + if (childNode) { + do { + if (captureRemaining) // We already hit an unbalanced node and are now just scooping up all subsequent nodes + captureRemaining.push(childNode); + else if (isStartComment(childNode)) { + var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true); + if (matchingEndComment) // It's a balanced tag, so skip immediately to the end of this virtual set + childNode = matchingEndComment; + else + captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point + } else if (isEndComment(childNode)) { + captureRemaining = [childNode]; // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing + } + } while (childNode = childNode.nextSibling); + } + return captureRemaining; + } + + ko.virtualElements = { + allowedBindings: {}, + + childNodes: function(node) { + return isStartComment(node) ? getVirtualChildren(node) : node.childNodes; + }, + + emptyNode: function(node) { + if (!isStartComment(node)) + ko.utils.emptyDomNode(node); + else { + var virtualChildren = ko.virtualElements.childNodes(node); + for (var i = 0, j = virtualChildren.length; i < j; i++) + ko.removeNode(virtualChildren[i]); + } + }, + + setDomNodeChildren: function(node, childNodes) { + if (!isStartComment(node)) + ko.utils.setDomNodeChildren(node, childNodes); + else { + ko.virtualElements.emptyNode(node); + var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children + for (var i = 0, j = childNodes.length; i < j; i++) + endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode); + } + }, + + prepend: function(containerNode, nodeToPrepend) { + if (!isStartComment(containerNode)) { + if (containerNode.firstChild) + containerNode.insertBefore(nodeToPrepend, containerNode.firstChild); + else + containerNode.appendChild(nodeToPrepend); + } else { + // Start comments must always have a parent and at least one following sibling (the end comment) + containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling); + } + }, + + insertAfter: function(containerNode, nodeToInsert, insertAfterNode) { + if (!isStartComment(containerNode)) { + // Insert after insertion point + if (insertAfterNode.nextSibling) + containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling); + else + containerNode.appendChild(nodeToInsert); + } else { + // Children of start comments must always have a parent and at least one following sibling (the end comment) + containerNode.parentNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling); + } + }, + + nextSibling: function(node) { + if (!isStartComment(node)) { + if (node.nextSibling && isEndComment(node.nextSibling)) + return undefined; + return node.nextSibling; + } else { + return getMatchingEndComment(node).nextSibling; + } + }, + + virtualNodeBindingValue: function(node) { + var regexMatch = isStartComment(node); + return regexMatch ? regexMatch[1] : null; + }, + + extractAnonymousTemplateIfVirtualElement: function(node) { + if (ko.virtualElements.virtualNodeBindingValue(node)) { + // Empty out the virtual children, and associate "node" with an anonymous template matching its previous virtual children + var virtualChildren = ko.virtualElements.childNodes(node); + var anonymousTemplateText = nodeArrayToText(virtualChildren, true); + ko.virtualElements.emptyNode(node); + new ko.templateSources.anonymousTemplate(node).text(anonymousTemplateText); + } + }, + + normaliseVirtualElementDomStructure: function(elementVerified) { + // Workaround for https://github.com/SteveSanderson/knockout/issues/155 + // (IE <= 8 or IE 9 quirks mode parses your HTML weirdly, treating closing tags as if they don't exist, thereby moving comment nodes + // that are direct descendants of
    into the preceding
  • ) + if (!htmlTagsWithOptionallyClosingChildren[elementVerified.tagName.toLowerCase()]) + return; + + // Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags + // must be intended to appear *after* that child, so move them there. + var childNode = elementVerified.firstChild; + if (childNode) { + do { + if (childNode.nodeType === 1) { + var unbalancedTags = getUnbalancedChildTags(childNode); + if (unbalancedTags) { + // Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child + var nodeToInsertBefore = childNode.nextSibling; + for (var i = 0; i < unbalancedTags.length; i++) { + if (nodeToInsertBefore) + elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore); + else + elementVerified.appendChild(unbalancedTags[i]); + } + } + } + } while (childNode = childNode.nextSibling); + } + } + }; +})(); +(function() { + var defaultBindingAttributeName = "data-bind"; + + ko.bindingProvider = function() { }; + + ko.utils.extend(ko.bindingProvider.prototype, { + 'nodeHasBindings': function(node) { + switch (node.nodeType) { + case 1: return node.getAttribute(defaultBindingAttributeName) != null; // Element + case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node + default: return false; + } + }, + + 'getBindings': function(node, bindingContext) { + var bindingsString = this['getBindingsString'](node, bindingContext); + return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext) : null; + }, + + // The following function is only used internally by this default provider. + // It's not part of the interface definition for a general binding provider. + 'getBindingsString': function(node, bindingContext) { + switch (node.nodeType) { + case 1: return node.getAttribute(defaultBindingAttributeName); // Element + case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node + default: return null; + } + }, + + // The following function is only used internally by this default provider. + // It's not part of the interface definition for a general binding provider. + 'parseBindingsString': function(bindingsString, bindingContext) { + try { + var viewModel = bindingContext['$data']; + var rewrittenBindings = " { " + ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(bindingsString) + " } "; + return ko.utils.evalWithinScope(rewrittenBindings, viewModel === null ? window : viewModel, bindingContext); + } catch (ex) { + throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString); + } + } + }); + + ko.bindingProvider['instance'] = new ko.bindingProvider(); +})(); + +ko.exportSymbol('ko.bindingProvider', ko.bindingProvider);(function () { + ko.bindingHandlers = {}; + + ko.bindingContext = function(dataItem, parentBindingContext) { + this['$data'] = dataItem; + if (parentBindingContext) { + this['$parent'] = parentBindingContext['$data']; + this['$parents'] = (parentBindingContext['$parents'] || []).slice(0); + this['$parents'].unshift(this['$parent']); + this['$root'] = parentBindingContext['$root']; + } else { + this['$parents'] = []; + this['$root'] = dataItem; + } + } + ko.bindingContext.prototype['createChildContext'] = function (dataItem) { + return new ko.bindingContext(dataItem, this); + }; + + function validateThatBindingIsAllowedForVirtualElements(bindingName) { + var validator = ko.virtualElements.allowedBindings[bindingName]; + if (!validator) + throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements") + } + + function applyBindingsToDescendantsInternal (viewModel, elementVerified) { + var currentChild, nextInQueue = elementVerified.childNodes[0]; + while (currentChild = nextInQueue) { + // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position + nextInQueue = ko.virtualElements.nextSibling(currentChild); + applyBindingsToNodeAndDescendantsInternal(viewModel, currentChild, false); + } + } + + function applyBindingsToNodeAndDescendantsInternal (viewModel, nodeVerified, isRootNodeForBindingContext) { + var shouldBindDescendants = true; + + // Perf optimisation: Apply bindings only if... + // (1) It's a root element for this binding context, as we will need to store the binding context on this node + // Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those + // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template) + var isElement = (nodeVerified.nodeType == 1); + if (isElement) // Workaround IE <= 8 HTML parsing weirdness + ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified); + + var shouldApplyBindings = (isElement && isRootNodeForBindingContext) // Case (1) + || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified); // Case (2) + if (shouldApplyBindings) + shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, viewModel, isRootNodeForBindingContext).shouldBindDescendants; + + if (isElement && shouldBindDescendants) + applyBindingsToDescendantsInternal(viewModel, nodeVerified); + } + + function applyBindingsToNodeInternal (node, bindings, viewModelOrBindingContext, isRootNodeForBindingContext) { + // Need to be sure that inits are only run once, and updates never run until all the inits have been run + var initPhase = 0; // 0 = before all inits, 1 = during inits, 2 = after all inits + + // Pre-process any anonymous template bounded by comment nodes + ko.virtualElements.extractAnonymousTemplateIfVirtualElement(node); + + // Each time the dependentObservable is evaluated (after data changes), + // the binding attribute is reparsed so that it can pick out the correct + // model properties in the context of the changed data. + // DOM event callbacks need to be able to access this changed data, + // so we need a single parsedBindings variable (shared by all callbacks + // associated with this node's bindings) that all the closures can access. + var parsedBindings; + function makeValueAccessor(bindingKey) { + return function () { return parsedBindings[bindingKey] } + } + function parsedBindingsAccessor() { + return parsedBindings; + } + + var bindingHandlerThatControlsDescendantBindings; + new ko.dependentObservable( + function () { + // Ensure we have a nonnull binding context to work with + var bindingContextInstance = viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext) + ? viewModelOrBindingContext + : new ko.bindingContext(ko.utils.unwrapObservable(viewModelOrBindingContext)); + var viewModel = bindingContextInstance['$data']; + + // We only need to store the bindingContext at the root of the subtree where it applies + // as all descendants will be able to find it by scanning up their ancestry + if (isRootNodeForBindingContext) + ko.storedBindingContextForNode(node, bindingContextInstance); + + // Use evaluatedBindings if given, otherwise fall back on asking the bindings provider to give us some bindings + var evaluatedBindings = (typeof bindings == "function") ? bindings() : bindings; + parsedBindings = evaluatedBindings || ko.bindingProvider['instance']['getBindings'](node, bindingContextInstance); + + if (parsedBindings) { + // First run all the inits, so bindings can register for notification on changes + if (initPhase === 0) { + initPhase = 1; + for (var bindingKey in parsedBindings) { + var binding = ko.bindingHandlers[bindingKey]; + if (binding && node.nodeType === 8) + validateThatBindingIsAllowedForVirtualElements(bindingKey); + + if (binding && typeof binding["init"] == "function") { + var handlerInitFn = binding["init"]; + var initResult = handlerInitFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance); + + // If this binding handler claims to control descendant bindings, make a note of this + if (initResult && initResult['controlsDescendantBindings']) { + if (bindingHandlerThatControlsDescendantBindings !== undefined) + throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element."); + bindingHandlerThatControlsDescendantBindings = bindingKey; + } + } + } + initPhase = 2; + } + + // ... then run all the updates, which might trigger changes even on the first evaluation + if (initPhase === 2) { + for (var bindingKey in parsedBindings) { + var binding = ko.bindingHandlers[bindingKey]; + if (binding && typeof binding["update"] == "function") { + var handlerUpdateFn = binding["update"]; + handlerUpdateFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance); + } + } + } + } + }, + null, + { 'disposeWhenNodeIsRemoved' : node } + ); + + return { + shouldBindDescendants: bindingHandlerThatControlsDescendantBindings === undefined + }; + }; + + var storedBindingContextDomDataKey = "__ko_bindingContext__"; + ko.storedBindingContextForNode = function (node, bindingContext) { + if (arguments.length == 2) + ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext); + else + return ko.utils.domData.get(node, storedBindingContextDomDataKey); + } + + ko.applyBindingsToNode = function (node, bindings, viewModel) { + if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness + ko.virtualElements.normaliseVirtualElementDomStructure(node); + return applyBindingsToNodeInternal(node, bindings, viewModel, true); + }; + + ko.applyBindingsToDescendants = function(viewModel, rootNode) { + if (rootNode.nodeType === 1) + applyBindingsToDescendantsInternal(viewModel, rootNode); + }; + + ko.applyBindings = function (viewModel, rootNode) { + if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8)) + throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node"); + rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional + + applyBindingsToNodeAndDescendantsInternal(viewModel, rootNode, true); + }; + + // Retrieving binding context from arbitrary nodes + ko.contextFor = function(node) { + // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them) + switch (node.nodeType) { + case 1: + case 8: + var context = ko.storedBindingContextForNode(node); + if (context) return context; + if (node.parentNode) return ko.contextFor(node.parentNode); + break; + } + return undefined; + }; + ko.dataFor = function(node) { + var context = ko.contextFor(node); + return context ? context['$data'] : undefined; + }; + + ko.exportSymbol('ko.bindingHandlers', ko.bindingHandlers); + ko.exportSymbol('ko.applyBindings', ko.applyBindings); + ko.exportSymbol('ko.applyBindingsToDescendants', ko.applyBindingsToDescendants); + ko.exportSymbol('ko.applyBindingsToNode', ko.applyBindingsToNode); + ko.exportSymbol('ko.contextFor', ko.contextFor); + ko.exportSymbol('ko.dataFor', ko.dataFor); +})();// For certain common events (currently just 'click'), allow a simplified data-binding syntax +// e.g. click:handler instead of the usual full-length event:{click:handler} +var eventHandlersWithShortcuts = ['click']; +ko.utils.arrayForEach(eventHandlersWithShortcuts, function(eventName) { + ko.bindingHandlers[eventName] = { + 'init': function(element, valueAccessor, allBindingsAccessor, viewModel) { + var newValueAccessor = function () { + var result = {}; + result[eventName] = valueAccessor(); + return result; + }; + return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindingsAccessor, viewModel); + } + } +}); + + +ko.bindingHandlers['event'] = { + 'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) { + var eventsToHandle = valueAccessor() || {}; + for(var eventNameOutsideClosure in eventsToHandle) { + (function() { + var eventName = eventNameOutsideClosure; // Separate variable to be captured by event handler closure + if (typeof eventName == "string") { + ko.utils.registerEventHandler(element, eventName, function (event) { + var handlerReturnValue; + var handlerFunction = valueAccessor()[eventName]; + if (!handlerFunction) + return; + var allBindings = allBindingsAccessor(); + + try { + // Take all the event args, and prefix with the viewmodel + var argsForHandler = ko.utils.makeArray(arguments); + argsForHandler.unshift(viewModel); + handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler); + } finally { + if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. + if (event.preventDefault) + event.preventDefault(); + else + event.returnValue = false; + } + } + + var bubble = allBindings[eventName + 'Bubble'] !== false; + if (!bubble) { + event.cancelBubble = true; + if (event.stopPropagation) + event.stopPropagation(); + } + }); + } + })(); + } + } +}; + +ko.bindingHandlers['submit'] = { + 'init': function (element, valueAccessor, allBindingsAccessor, viewModel) { + if (typeof valueAccessor() != "function") + throw new Error("The value for a submit binding must be a function"); + ko.utils.registerEventHandler(element, "submit", function (event) { + var handlerReturnValue; + var value = valueAccessor(); + try { handlerReturnValue = value.call(viewModel, element); } + finally { + if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. + if (event.preventDefault) + event.preventDefault(); + else + event.returnValue = false; + } + } + }); + } +}; + +ko.bindingHandlers['visible'] = { + 'update': function (element, valueAccessor) { + var value = ko.utils.unwrapObservable(valueAccessor()); + var isCurrentlyVisible = !(element.style.display == "none"); + if (value && !isCurrentlyVisible) + element.style.display = ""; + else if ((!value) && isCurrentlyVisible) + element.style.display = "none"; + } +} + +ko.bindingHandlers['enable'] = { + 'update': function (element, valueAccessor) { + var value = ko.utils.unwrapObservable(valueAccessor()); + if (value && element.disabled) + element.removeAttribute("disabled"); + else if ((!value) && (!element.disabled)) + element.disabled = true; + } +}; + +ko.bindingHandlers['disable'] = { + 'update': function (element, valueAccessor) { + ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) }); + } +}; + +function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) { + if (preferModelValue) { + if (modelValue !== ko.selectExtensions.readValue(element)) + ko.selectExtensions.writeValue(element, modelValue); + } + + // No matter which direction we're syncing in, we want the end result to be equality between dropdown value and model value. + // If they aren't equal, either we prefer the dropdown value, or the model value couldn't be represented, so either way, + // change the model value to match the dropdown. + if (modelValue !== ko.selectExtensions.readValue(element)) + ko.utils.triggerEvent(element, "change"); +}; + +ko.bindingHandlers['value'] = { + 'init': function (element, valueAccessor, allBindingsAccessor) { + // Always catch "change" event; possibly other events too if asked + var eventsToCatch = ["change"]; + var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"]; + if (requestedEventsToCatch) { + if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names + requestedEventsToCatch = [requestedEventsToCatch]; + ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch); + eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch); + } + + ko.utils.arrayForEach(eventsToCatch, function(eventName) { + // The syntax "after" means "run the handler asynchronously after the event" + // This is useful, for example, to catch "keydown" events after the browser has updated the control + // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event) + var handleEventAsynchronously = false; + if (ko.utils.stringStartsWith(eventName, "after")) { + handleEventAsynchronously = true; + eventName = eventName.substring("after".length); + } + var runEventHandler = handleEventAsynchronously ? function(handler) { setTimeout(handler, 0) } + : function(handler) { handler() }; + + ko.utils.registerEventHandler(element, eventName, function () { + runEventHandler(function() { + var modelValue = valueAccessor(); + var elementValue = ko.selectExtensions.readValue(element); + if (ko.isWriteableObservable(modelValue)) + modelValue(elementValue); + else { + var allBindings = allBindingsAccessor(); + if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value']) + allBindings['_ko_property_writers']['value'](elementValue); + } + }); + }); + }); + }, + 'update': function (element, valueAccessor) { + var newValue = ko.utils.unwrapObservable(valueAccessor()); + var elementValue = ko.selectExtensions.readValue(element); + var valueHasChanged = (newValue != elementValue); + + // JavaScript's 0 == "" behavious is unfortunate here as it prevents writing 0 to an empty text box (loose equality suggests the values are the same). + // We don't want to do a strict equality comparison as that is more confusing for developers in certain cases, so we specifically special case 0 != "" here. + if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0")) + valueHasChanged = true; + + if (valueHasChanged) { + var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); }; + applyValueAction(); + + // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread + // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread + // to apply the value as well. + var alsoApplyAsynchronously = element.tagName == "SELECT"; + if (alsoApplyAsynchronously) + setTimeout(applyValueAction, 0); + } + + // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change, + // because you're not allowed to have a model value that disagrees with a visible UI selection. + if ((element.tagName == "SELECT") && (element.length > 0)) + ensureDropdownSelectionIsConsistentWithModelValue(element, newValue, /* preferModelValue */ false); + } +}; + +ko.bindingHandlers['options'] = { + 'update': function (element, valueAccessor, allBindingsAccessor) { + if (element.tagName != "SELECT") + throw new Error("options binding applies only to SELECT elements"); + + var selectWasPreviouslyEmpty = element.length == 0; + var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) { + return node.tagName && node.tagName == "OPTION" && node.selected; + }), function (node) { + return ko.selectExtensions.readValue(node) || node.innerText || node.textContent; + }); + var previousScrollTop = element.scrollTop; + element.scrollTop = 0; // Workaround for a Chrome rendering bug. Note that we restore the scroll position later. (https://github.com/SteveSanderson/knockout/issues/215) + + var value = ko.utils.unwrapObservable(valueAccessor()); + var selectedValue = element.value; + + // Remove all existing