-// Knockout JavaScript library v1.3.0rc\r
+// Knockout JavaScript library v2.0.0rc3\r
// (c) Steven Sanderson - http://knockoutjs.com/\r
// License: MIT (http://www.opensource.org/licenses/mit-license.php)\r
\r
},\r
\r
outerHTML: function(node) {\r
- // For IE and Chrome\r
- var nativeOuterHtml = node.outerHTML;\r
- if (typeof nativeOuterHtml == "string")\r
- return nativeOuterHtml;\r
- \r
+ // For Chrome on non-text nodes\r
+ // (Although IE supports outerHTML, the way it formats HTML is inconsistent - sometimes closing </li> tags are omitted, sometimes not. That caused https://github.com/SteveSanderson/knockout/issues/212.)\r
+ if (ieVersion === undefined) {\r
+ var nativeOuterHtml = node.outerHTML;\r
+ if (typeof nativeOuterHtml == "string")\r
+ return nativeOuterHtml;\r
+ }\r
+\r
// Other browsers\r
var dummyContainer = window.document.createElement("div");\r
dummyContainer.appendChild(node.cloneNode(true));\r
return dummyContainer.innerHTML;\r
},\r
\r
+ setTextContent: function(element, textContent) {\r
+ var value = ko.utils.unwrapObservable(textContent);\r
+ if ((value === null) || (value === undefined))\r
+ value = "";\r
+\r
+ 'innerText' in element ? element.innerText = value\r
+ : element.textContent = value;\r
+ \r
+ if (ieVersion) {\r
+ // Believe it or not, this actually fixes an IE9 rendering bug. Insane. https://github.com/SteveSanderson/knockout/issues/209\r
+ element.innerHTML = element.innerHTML;\r
+ }\r
+ },\r
+\r
range: function (min, max) {\r
min = ko.utils.unwrapObservable(min);\r
max = ko.utils.unwrapObservable(max);\r
})();\r
\r
ko.exportSymbol('ko.utils', ko.utils);\r
-ko.exportSymbol('ko.utils.arrayForEach', ko.utils.arrayForEach);\r
-ko.exportSymbol('ko.utils.arrayFirst', ko.utils.arrayFirst);\r
-ko.exportSymbol('ko.utils.arrayFilter', ko.utils.arrayFilter);\r
-ko.exportSymbol('ko.utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues);\r
-ko.exportSymbol('ko.utils.arrayIndexOf', ko.utils.arrayIndexOf);\r
-ko.exportSymbol('ko.utils.arrayMap', ko.utils.arrayMap);\r
-ko.exportSymbol('ko.utils.arrayPushAll', ko.utils.arrayPushAll);\r
-ko.exportSymbol('ko.utils.arrayRemoveItem', ko.utils.arrayRemoveItem);\r
-ko.exportSymbol('ko.utils.extend', ko.utils.extend);\r
-ko.exportSymbol('ko.utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);\r
-ko.exportSymbol('ko.utils.getFormFields', ko.utils.getFormFields);\r
-ko.exportSymbol('ko.utils.postJson', ko.utils.postJson);\r
-ko.exportSymbol('ko.utils.parseJson', ko.utils.parseJson);\r
-ko.exportSymbol('ko.utils.registerEventHandler', ko.utils.registerEventHandler);\r
-ko.exportSymbol('ko.utils.stringifyJson', ko.utils.stringifyJson);\r
-ko.exportSymbol('ko.utils.range', ko.utils.range);\r
-ko.exportSymbol('ko.utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);\r
-ko.exportSymbol('ko.utils.triggerEvent', ko.utils.triggerEvent);\r
-ko.exportSymbol('ko.utils.unwrapObservable', ko.utils.unwrapObservable);\r
+ko.utils.arrayForEach([\r
+ ['arrayForEach', ko.utils.arrayForEach],\r
+ ['arrayFirst', ko.utils.arrayFirst],\r
+ ['arrayFilter', ko.utils.arrayFilter],\r
+ ['arrayGetDistinctValues', ko.utils.arrayGetDistinctValues],\r
+ ['arrayIndexOf', ko.utils.arrayIndexOf],\r
+ ['arrayMap', ko.utils.arrayMap],\r
+ ['arrayPushAll', ko.utils.arrayPushAll],\r
+ ['arrayRemoveItem', ko.utils.arrayRemoveItem],\r
+ ['extend', ko.utils.extend],\r
+ ['fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost],\r
+ ['getFormFields', ko.utils.getFormFields],\r
+ ['postJson', ko.utils.postJson],\r
+ ['parseJson', ko.utils.parseJson],\r
+ ['registerEventHandler', ko.utils.registerEventHandler],\r
+ ['stringifyJson', ko.utils.stringifyJson],\r
+ ['range', ko.utils.range],\r
+ ['toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass],\r
+ ['triggerEvent', ko.utils.triggerEvent],\r
+ ['unwrapObservable', ko.utils.unwrapObservable]\r
+], function(item) {\r
+ ko.exportSymbol('ko.utils.' + item[0], item[1]);\r
+});\r
\r
if (!Function.prototype['bind']) {\r
// 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)\r
\r
return ko.utils.makeArray(div.lastChild.childNodes);\r
}\r
+\r
+ function jQueryHtmlParse(html) {\r
+ var elems = jQuery['clean']([html]);\r
+\r
+ // 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.\r
+ // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.\r
+ // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.\r
+ if (elems && elems[0]) {\r
+ // Find the top-most parent element that's a direct child of a document fragment\r
+ var elem = elems[0];\r
+ while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)\r
+ elem = elem.parentNode;\r
+ // ... then detach it\r
+ if (elem.parentNode)\r
+ elem.parentNode.removeChild(elem);\r
+ }\r
+ \r
+ return elems;\r
+ }\r
\r
ko.utils.parseHtmlFragment = function(html) {\r
- return typeof jQuery != 'undefined' ? jQuery['clean']([html]) // As below, benefit from jQuery's optimisations where possible\r
+ return typeof jQuery != 'undefined' ? jQueryHtmlParse(html) // As below, benefit from jQuery's optimisations where possible\r
: simpleHtmlParse(html); // ... otherwise, this simple logic will do in most common cases.\r
};\r
\r
}, timeout); \r
}\r
});\r
+ },\r
+\r
+ 'notify': function(target, notifyWhen) {\r
+ target["equalityComparer"] = notifyWhen == "always" \r
+ ? function() { return false } // Treat all values as not equal\r
+ : ko.observable["fn"]["equalityComparer"];\r
+ return target;\r
}\r
};\r
\r
ko.utils.extend(this, ko.subscribable['fn']);\r
ko.exportProperty(this, 'subscribe', this.subscribe);\r
ko.exportProperty(this, 'extend', this.extend);\r
- ko.exportProperty(this, 'notifySubscribers', this.notifySubscribers);\r
ko.exportProperty(this, 'getSubscriptionsCount', this.getSubscriptionsCount);\r
}\r
\r
return subscription;\r
},\r
\r
- notifySubscribers: function (valueToNotify, event) {\r
+ "notifySubscribers": function (valueToNotify, event) {\r
event = event || defaultEvent;\r
if (this._subscriptions[event]) {\r
ko.utils.arrayForEach(this._subscriptions[event].slice(0), function (subscription) {\r
\r
\r
ko.isSubscribable = function (instance) {\r
- return typeof instance.subscribe == "function" && typeof instance.notifySubscribers == "function";\r
+ return typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";\r
};\r
\r
ko.exportSymbol('ko.subscribable', ko.subscribable);\r
}\r
}\r
ko.subscribable.call(observable);\r
- observable.valueHasMutated = function () { observable.notifySubscribers(_latestValue); }\r
- observable.valueWillMutate = function () { observable.notifySubscribers(_latestValue, "beforeChange"); }\r
+ observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }\r
+ observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }\r
ko.utils.extend(observable, ko.observable['fn']);\r
\r
ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);\r
});\r
var valueForThis = options["owner"] || evaluatorFunctionTarget; // If undefined, it will default to "window" by convention. This might change in the future.\r
var newValue = options["read"].call(valueForThis);\r
- dependentObservable.notifySubscribers(_latestValue, "beforeChange");\r
+ dependentObservable["notifySubscribers"](_latestValue, "beforeChange");\r
_latestValue = newValue;\r
} finally {\r
ko.dependencyDetection.end();\r
}\r
\r
- dependentObservable.notifySubscribers(_latestValue);\r
+ dependentObservable["notifySubscribers"](_latestValue);\r
_hasBeenEvaluated = true;\r
}\r
\r
\r
ko.exportSymbol('ko.toJS', ko.toJS);\r
ko.exportSymbol('ko.toJSON', ko.toJSON);(function () {\r
+ var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';\r
+\r
// Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values\r
// are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values\r
// that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.\r
ko.selectExtensions = {\r
readValue : function(element) {\r
if (element.tagName == 'OPTION') {\r
- if (element['__ko__hasDomDataOptionValue__'] === true)\r
+ if (element[hasDomDataExpandoProperty] === true)\r
return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);\r
return element.getAttribute("value");\r
} else if (element.tagName == 'SELECT')\r
if (element.tagName == 'OPTION') {\r
switch(typeof value) {\r
case "string":\r
- case "number":\r
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);\r
- if ('__ko__hasDomDataOptionValue__' in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node\r
- delete element['__ko__hasDomDataOptionValue__'];\r
+ if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node\r
+ delete element[hasDomDataExpandoProperty];\r
}\r
element.value = value; \r
break;\r
default:\r
// Store arbitrary object using DomData\r
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);\r
- element['__ko__hasDomDataOptionValue__'] = true;\r
- element.value = "";\r
+ element[hasDomDataExpandoProperty] = true;\r
+\r
+ // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.\r
+ element.value = typeof value === "number" ? value : "";\r
break;\r
} \r
} else if (element.tagName == 'SELECT') {\r
var allBindings = allBindingsAccessor();\r
\r
try { \r
- handlerReturnValue = handlerFunction.apply(viewModel, arguments); \r
+ // Take all the event args, and prefix with the viewmodel\r
+ var argsForHandler = ko.utils.makeArray(arguments);\r
+ argsForHandler.unshift(viewModel);\r
+ handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);\r
} finally {\r
if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.\r
if (event.preventDefault)\r
ko.bindingHandlers['submit'] = {\r
'init': function (element, valueAccessor, allBindingsAccessor, viewModel) {\r
if (typeof valueAccessor() != "function")\r
- throw new Error("The value for a submit binding must be a function to invoke on submit");\r
+ throw new Error("The value for a submit binding must be a function");\r
ko.utils.registerEventHandler(element, "submit", function (event) {\r
var handlerReturnValue;\r
var value = valueAccessor();\r
return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;\r
});\r
var previousScrollTop = element.scrollTop;\r
+ element.scrollTop = 0; // Workaround for a Chrome rendering bug. Note that we restore the scroll position later. (https://github.com/SteveSanderson/knockout/issues/215)\r
\r
var value = ko.utils.unwrapObservable(valueAccessor());\r
var selectedValue = element.value;\r
optionText = optionValue; // Given no optionsText arg; use the data value itself\r
if ((optionText === null) || (optionText === undefined))\r
optionText = ""; \r
- optionText = ko.utils.unwrapObservable(optionText).toString();\r
- typeof option.innerText == "string" ? option.innerText = optionText\r
- : option.textContent = optionText;\r
+\r
+ ko.utils.setTextContent(option, optionText);\r
\r
element.appendChild(option);\r
}\r
}\r
}\r
};\r
-ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.bindingHandlers.options.optionValueDomData__';\r
+ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.optionValueDomData__';\r
\r
ko.bindingHandlers['selectedOptions'] = {\r
getSelectedValuesFromSelectNode: function (selectNode) {\r
\r
ko.bindingHandlers['text'] = {\r
'update': function (element, valueAccessor) {\r
- var value = ko.utils.unwrapObservable(valueAccessor());\r
- if ((value === null) || (value === undefined))\r
- value = "";\r
- typeof element.innerText == "string" ? element.innerText = value\r
- : element.textContent = value;\r
+ ko.utils.setTextContent(element, valueAccessor());\r
}\r
};\r
\r
ko.templateEngine = function () { };\r
\r
ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {\r
- throw "Override renderTemplateSource in your ko.templateEngine subclass";\r
+ throw "Override renderTemplateSource";\r
};\r
\r
ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {\r
- throw "Override createJavaScriptEvaluatorBlock in your ko.templateEngine subclass"; \r
+ throw "Override createJavaScriptEvaluatorBlock";\r
};\r
\r
ko.templateEngine.prototype['makeTemplateSource'] = function(template) {\r
// Anonymous template\r
return new ko.templateSources.anonymousTemplate(template);\r
} else\r
- throw new Error("Unrecognised template type: " + template);\r
+ throw new Error("Unknown template type: " + template);\r
};\r
\r
ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options) {\r
\r
var templateSubscription = null;\r
\r
- if (typeof bindingValue['foreach'] != "undefined") {\r
+ if ((typeof bindingValue === 'object') && ('foreach' in bindingValue)) { // Note: can't use 'in' operator on strings\r
// Render once for each data point (treating data set as empty if shouldDisplay==false)\r
var dataArray = (shouldDisplay && bindingValue['foreach']) || [];\r
templateSubscription = ko.renderTemplateForEach(templateName || element, dataArray, /* options: */ bindingValue, element, bindingContext);\r
- }\r
- else {\r
+ } else {\r
if (shouldDisplay) {\r
// Render once for this single data point (or use the viewModel if no data was provided)\r
var innerBindingContext = (typeof bindingValue == 'object') && ('data' in bindingValue)\r