Converted project to VisualStudio 2012.
Adding SoundGraphDisplay and SensorFrontView classes.
They were respectively based on SystemTray and SensorNotifyIcon.
SoundGraphDisplay is now able to load iMONDisplay.dll providing it lives on your PATH.
Adding option to sensor context menu for adding it into FrontView.
1 // Knockout JavaScript library v2.1.0
2 // (c) Steven Sanderson - http://knockoutjs.com/
3 // License: MIT (http://www.opensource.org/licenses/mit-license.php)
5 (function(window,document,navigator,undefined){
8 // Support three module loading scenarios
9 if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
10 // [1] CommonJS/Node.js
11 var target = module['exports'] || exports; // module.exports is for Node.js
13 } else if (typeof define === 'function' && define['amd']) {
14 // [2] AMD anonymous module
15 define(['exports'], factory);
17 // [3] No module loader (plain <script> tag) - put directly in global namespace
18 factory(window['ko'] = {});
20 }(function(koExports){
21 // Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
22 // In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
23 var ko = typeof koExports !== 'undefined' ? koExports : {};
24 // Google Closure Compiler helpers (used only to make the minified file smaller)
25 ko.exportSymbol = function(koPath, object) {
26 var tokens = koPath.split(".");
28 // In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable)
29 // At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko)
32 for (var i = 0; i < tokens.length - 1; i++)
33 target = target[tokens[i]];
34 target[tokens[tokens.length - 1]] = object;
36 ko.exportProperty = function(owner, publicName, object) {
37 owner[publicName] = object;
41 ko.exportSymbol('version', ko.version);
42 ko.utils = new (function () {
43 var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
45 // 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)
46 var knownEvents = {}, knownEventTypesByEventName = {};
47 var keyEventTypeName = /Firefox\/2/i.test(navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents';
48 knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
49 knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
50 for (var eventType in knownEvents) {
51 var knownEventsForType = knownEvents[eventType];
52 if (knownEventsForType.length) {
53 for (var i = 0, j = knownEventsForType.length; i < j; i++)
54 knownEventTypesByEventName[knownEventsForType[i]] = eventType;
57 var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
59 // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
60 var ieVersion = (function() {
61 var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
63 // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
65 div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
68 return version > 4 ? version : undefined;
70 var isIe6 = ieVersion === 6,
71 isIe7 = ieVersion === 7;
73 function isClickOnCheckableElement(element, eventType) {
74 if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false;
75 if (eventType.toLowerCase() != "click") return false;
76 var inputType = element.type;
77 return (inputType == "checkbox") || (inputType == "radio");
81 fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
83 arrayForEach: function (array, action) {
84 for (var i = 0, j = array.length; i < j; i++)
88 arrayIndexOf: function (array, item) {
89 if (typeof Array.prototype.indexOf == "function")
90 return Array.prototype.indexOf.call(array, item);
91 for (var i = 0, j = array.length; i < j; i++)
92 if (array[i] === item)
97 arrayFirst: function (array, predicate, predicateOwner) {
98 for (var i = 0, j = array.length; i < j; i++)
99 if (predicate.call(predicateOwner, array[i]))
104 arrayRemoveItem: function (array, itemToRemove) {
105 var index = ko.utils.arrayIndexOf(array, itemToRemove);
107 array.splice(index, 1);
110 arrayGetDistinctValues: function (array) {
113 for (var i = 0, j = array.length; i < j; i++) {
114 if (ko.utils.arrayIndexOf(result, array[i]) < 0)
115 result.push(array[i]);
120 arrayMap: function (array, mapping) {
123 for (var i = 0, j = array.length; i < j; i++)
124 result.push(mapping(array[i]));
128 arrayFilter: function (array, predicate) {
131 for (var i = 0, j = array.length; i < j; i++)
132 if (predicate(array[i]))
133 result.push(array[i]);
137 arrayPushAll: function (array, valuesToPush) {
138 if (valuesToPush instanceof Array)
139 array.push.apply(array, valuesToPush);
141 for (var i = 0, j = valuesToPush.length; i < j; i++)
142 array.push(valuesToPush[i]);
146 extend: function (target, source) {
148 for(var prop in source) {
149 if(source.hasOwnProperty(prop)) {
150 target[prop] = source[prop];
157 emptyDomNode: function (domNode) {
158 while (domNode.firstChild) {
159 ko.removeNode(domNode.firstChild);
163 moveCleanedNodesToContainerElement: function(nodes) {
164 // Ensure it's a real array, as we're about to reparent the nodes and
165 // we don't want the underlying collection to change while we're doing that.
166 var nodesArray = ko.utils.makeArray(nodes);
168 var container = document.createElement('div');
169 for (var i = 0, j = nodesArray.length; i < j; i++) {
170 ko.cleanNode(nodesArray[i]);
171 container.appendChild(nodesArray[i]);
176 setDomNodeChildren: function (domNode, childNodes) {
177 ko.utils.emptyDomNode(domNode);
179 for (var i = 0, j = childNodes.length; i < j; i++)
180 domNode.appendChild(childNodes[i]);
184 replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
185 var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
186 if (nodesToReplaceArray.length > 0) {
187 var insertionPoint = nodesToReplaceArray[0];
188 var parent = insertionPoint.parentNode;
189 for (var i = 0, j = newNodesArray.length; i < j; i++)
190 parent.insertBefore(newNodesArray[i], insertionPoint);
191 for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
192 ko.removeNode(nodesToReplaceArray[i]);
197 setOptionNodeSelectionState: function (optionNode, isSelected) {
198 // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
199 if (navigator.userAgent.indexOf("MSIE 6") >= 0)
200 optionNode.setAttribute("selected", isSelected);
202 optionNode.selected = isSelected;
205 stringTrim: function (string) {
206 return (string || "").replace(stringTrimRegex, "");
209 stringTokenize: function (string, delimiter) {
211 var tokens = (string || "").split(delimiter);
212 for (var i = 0, j = tokens.length; i < j; i++) {
213 var trimmed = ko.utils.stringTrim(tokens[i]);
215 result.push(trimmed);
220 stringStartsWith: function (string, startsWith) {
221 string = string || "";
222 if (startsWith.length > string.length)
224 return string.substring(0, startsWith.length) === startsWith;
227 buildEvalWithinScopeFunction: function (expression, scopeLevels) {
228 // Build the source for a function that evaluates "expression"
229 // For each scope variable, add an extra level of "with" nesting
230 // Example result: with(sc[1]) { with(sc[0]) { return (expression) } }
231 var functionBody = "return (" + expression + ")";
232 for (var i = 0; i < scopeLevels; i++) {
233 functionBody = "with(sc[" + i + "]) { " + functionBody + " } ";
235 return new Function("sc", functionBody);
238 domNodeIsContainedBy: function (node, containedByNode) {
239 if (containedByNode.compareDocumentPosition)
240 return (containedByNode.compareDocumentPosition(node) & 16) == 16;
241 while (node != null) {
242 if (node == containedByNode)
244 node = node.parentNode;
249 domNodeIsAttachedToDocument: function (node) {
250 return ko.utils.domNodeIsContainedBy(node, node.ownerDocument);
253 tagNameLower: function(element) {
254 // For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case.
255 // Possible future optimization: If we know it's an element from an XHTML document (not HTML),
256 // we don't need to do the .toLowerCase() as it will always be lower case anyway.
257 return element && element.tagName && element.tagName.toLowerCase();
260 registerEventHandler: function (element, eventType, handler) {
261 var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
262 if (!mustUseAttachEvent && typeof jQuery != "undefined") {
263 if (isClickOnCheckableElement(element, eventType)) {
264 // For click events on checkboxes, jQuery interferes with the event handling in an awkward way:
265 // it toggles the element checked state *after* the click event handlers run, whereas native
266 // click events toggle the checked state *before* the event handler.
267 // Fix this by intecepting the handler and applying the correct checkedness before it runs.
268 var originalHandler = handler;
269 handler = function(event, eventData) {
270 var jQuerySuppliedCheckedState = this.checked;
272 this.checked = eventData.checkedStateBeforeEvent !== true;
273 originalHandler.call(this, event);
274 this.checked = jQuerySuppliedCheckedState; // Restore the state jQuery applied
277 jQuery(element)['bind'](eventType, handler);
278 } else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
279 element.addEventListener(eventType, handler, false);
280 else if (typeof element.attachEvent != "undefined")
281 element.attachEvent("on" + eventType, function (event) {
282 handler.call(element, event);
285 throw new Error("Browser doesn't support addEventListener or attachEvent");
288 triggerEvent: function (element, eventType) {
289 if (!(element && element.nodeType))
290 throw new Error("element must be a DOM node when calling triggerEvent");
292 if (typeof jQuery != "undefined") {
294 if (isClickOnCheckableElement(element, eventType)) {
295 // Work around the jQuery "click events on checkboxes" issue described above by storing the original checked state before triggering the handler
296 eventData.push({ checkedStateBeforeEvent: element.checked });
298 jQuery(element)['trigger'](eventType, eventData);
299 } else if (typeof document.createEvent == "function") {
300 if (typeof element.dispatchEvent == "function") {
301 var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
302 var event = document.createEvent(eventCategory);
303 event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
304 element.dispatchEvent(event);
307 throw new Error("The supplied element doesn't support dispatchEvent");
308 } else if (typeof element.fireEvent != "undefined") {
309 // Unlike other browsers, IE doesn't change the checked state of checkboxes/radiobuttons when you trigger their "click" event
310 // so to make it consistent, we'll do it manually here
311 if (isClickOnCheckableElement(element, eventType))
312 element.checked = element.checked !== true;
313 element.fireEvent("on" + eventType);
316 throw new Error("Browser doesn't support triggering events");
319 unwrapObservable: function (value) {
320 return ko.isObservable(value) ? value() : value;
323 toggleDomNodeCssClass: function (node, className, shouldHaveClass) {
324 var currentClassNames = (node.className || "").split(/\s+/);
325 var hasClass = ko.utils.arrayIndexOf(currentClassNames, className) >= 0;
327 if (shouldHaveClass && !hasClass) {
328 node.className += (currentClassNames[0] ? " " : "") + className;
329 } else if (hasClass && !shouldHaveClass) {
330 var newClassName = "";
331 for (var i = 0; i < currentClassNames.length; i++)
332 if (currentClassNames[i] != className)
333 newClassName += currentClassNames[i] + " ";
334 node.className = ko.utils.stringTrim(newClassName);
338 setTextContent: function(element, textContent) {
339 var value = ko.utils.unwrapObservable(textContent);
340 if ((value === null) || (value === undefined))
343 'innerText' in element ? element.innerText = value
344 : element.textContent = value;
346 if (ieVersion >= 9) {
347 // Believe it or not, this actually fixes an IE9 rendering bug
348 // (See https://github.com/SteveSanderson/knockout/issues/209)
349 element.style.display = element.style.display;
353 ensureSelectElementIsRenderedCorrectly: function(selectElement) {
354 // Workaround for IE9 rendering bug - it doesn't reliably display all the text in dynamically-added select boxes unless you force it to re-render by updating the width.
355 // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
356 if (ieVersion >= 9) {
357 var originalWidth = selectElement.style.width;
358 selectElement.style.width = 0;
359 selectElement.style.width = originalWidth;
363 range: function (min, max) {
364 min = ko.utils.unwrapObservable(min);
365 max = ko.utils.unwrapObservable(max);
367 for (var i = min; i <= max; i++)
372 makeArray: function(arrayLikeObject) {
374 for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
375 result.push(arrayLikeObject[i]);
382 ieVersion : ieVersion,
384 getFormFields: function(form, fieldName) {
385 var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
386 var isMatchingField = (typeof fieldName == 'string')
387 ? function(field) { return field.name === fieldName }
388 : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
390 for (var i = fields.length - 1; i >= 0; i--) {
391 if (isMatchingField(fields[i]))
392 matches.push(fields[i]);
397 parseJson: function (jsonString) {
398 if (typeof jsonString == "string") {
399 jsonString = ko.utils.stringTrim(jsonString);
401 if (window.JSON && window.JSON.parse) // Use native parsing where available
402 return window.JSON.parse(jsonString);
403 return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
409 stringifyJson: function (data, replacer, space) { // replacer and space are optional
410 if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined"))
411 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");
412 return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
415 postJson: function (urlOrForm, data, options) {
416 options = options || {};
417 var params = options['params'] || {};
418 var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
421 // If we were given a form, use its 'action' URL and pick out any requested field values
422 if((typeof urlOrForm == 'object') && (ko.utils.tagNameLower(urlOrForm) === "form")) {
423 var originalForm = urlOrForm;
424 url = originalForm.action;
425 for (var i = includeFields.length - 1; i >= 0; i--) {
426 var fields = ko.utils.getFormFields(originalForm, includeFields[i]);
427 for (var j = fields.length - 1; j >= 0; j--)
428 params[fields[j].name] = fields[j].value;
432 data = ko.utils.unwrapObservable(data);
433 var form = document.createElement("form");
434 form.style.display = "none";
436 form.method = "post";
437 for (var key in data) {
438 var input = document.createElement("input");
440 input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
441 form.appendChild(input);
443 for (var key in params) {
444 var input = document.createElement("input");
446 input.value = params[key];
447 form.appendChild(input);
449 document.body.appendChild(form);
450 options['submitter'] ? options['submitter'](form) : form.submit();
451 setTimeout(function () { form.parentNode.removeChild(form); }, 0);
456 ko.exportSymbol('utils', ko.utils);
457 ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach);
458 ko.exportSymbol('utils.arrayFirst', ko.utils.arrayFirst);
459 ko.exportSymbol('utils.arrayFilter', ko.utils.arrayFilter);
460 ko.exportSymbol('utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues);
461 ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
462 ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
463 ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
464 ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
465 ko.exportSymbol('utils.extend', ko.utils.extend);
466 ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
467 ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
468 ko.exportSymbol('utils.postJson', ko.utils.postJson);
469 ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
470 ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler);
471 ko.exportSymbol('utils.stringifyJson', ko.utils.stringifyJson);
472 ko.exportSymbol('utils.range', ko.utils.range);
473 ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);
474 ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
475 ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
477 if (!Function.prototype['bind']) {
478 // 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)
479 // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
480 Function.prototype['bind'] = function (object) {
481 var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift();
483 return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
488 ko.utils.domData = new (function () {
490 var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
493 get: function (node, key) {
494 var allDataForNode = ko.utils.domData.getAll(node, false);
495 return allDataForNode === undefined ? undefined : allDataForNode[key];
497 set: function (node, key, value) {
498 if (value === undefined) {
499 // Make sure we don't actually create a new domData key if we are actually deleting a value
500 if (ko.utils.domData.getAll(node, false) === undefined)
503 var allDataForNode = ko.utils.domData.getAll(node, true);
504 allDataForNode[key] = value;
506 getAll: function (node, createIfNotFound) {
507 var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
508 var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null");
509 if (!hasExistingDataStore) {
510 if (!createIfNotFound)
512 dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
513 dataStore[dataStoreKey] = {};
515 return dataStore[dataStoreKey];
517 clear: function (node) {
518 var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
520 delete dataStore[dataStoreKey];
521 node[dataStoreKeyExpandoPropertyName] = null;
527 ko.exportSymbol('utils.domData', ko.utils.domData);
528 ko.exportSymbol('utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully
530 ko.utils.domNodeDisposal = new (function () {
531 var domDataKey = "__ko_domNodeDisposal__" + (new Date).getTime();
532 var cleanableNodeTypes = { 1: true, 8: true, 9: true }; // Element, Comment, Document
533 var cleanableNodeTypesWithDescendants = { 1: true, 9: true }; // Element, Document
535 function getDisposeCallbacksCollection(node, createIfNotFound) {
536 var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);
537 if ((allDisposeCallbacks === undefined) && createIfNotFound) {
538 allDisposeCallbacks = [];
539 ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);
541 return allDisposeCallbacks;
543 function destroyCallbacksCollection(node) {
544 ko.utils.domData.set(node, domDataKey, undefined);
547 function cleanSingleNode(node) {
548 // Run all the dispose callbacks
549 var callbacks = getDisposeCallbacksCollection(node, false);
551 callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
552 for (var i = 0; i < callbacks.length; i++)
556 // Also erase the DOM data
557 ko.utils.domData.clear(node);
559 // Special support for jQuery here because it's so commonly used.
560 // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
561 // so notify it to tear down any resources associated with the node & descendants here.
562 if ((typeof jQuery == "function") && (typeof jQuery['cleanData'] == "function"))
563 jQuery['cleanData']([node]);
565 // Also clear any immediate-child comment nodes, as these wouldn't have been found by
566 // node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
567 if (cleanableNodeTypesWithDescendants[node.nodeType])
568 cleanImmediateCommentTypeChildren(node);
571 function cleanImmediateCommentTypeChildren(nodeWithChildren) {
572 var child, nextChild = nodeWithChildren.firstChild;
573 while (child = nextChild) {
574 nextChild = child.nextSibling;
575 if (child.nodeType === 8)
576 cleanSingleNode(child);
581 addDisposeCallback : function(node, callback) {
582 if (typeof callback != "function")
583 throw new Error("Callback must be a function");
584 getDisposeCallbacksCollection(node, true).push(callback);
587 removeDisposeCallback : function(node, callback) {
588 var callbacksCollection = getDisposeCallbacksCollection(node, false);
589 if (callbacksCollection) {
590 ko.utils.arrayRemoveItem(callbacksCollection, callback);
591 if (callbacksCollection.length == 0)
592 destroyCallbacksCollection(node);
596 cleanNode : function(node) {
597 // First clean this node, where applicable
598 if (cleanableNodeTypes[node.nodeType]) {
599 cleanSingleNode(node);
601 // ... then its descendants, where applicable
602 if (cleanableNodeTypesWithDescendants[node.nodeType]) {
603 // Clone the descendants list in case it changes during iteration
604 var descendants = [];
605 ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
606 for (var i = 0, j = descendants.length; i < j; i++)
607 cleanSingleNode(descendants[i]);
612 removeNode : function(node) {
615 node.parentNode.removeChild(node);
619 ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience
620 ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience
621 ko.exportSymbol('cleanNode', ko.cleanNode);
622 ko.exportSymbol('removeNode', ko.removeNode);
623 ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal);
624 ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);
625 ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);
627 var leadingCommentRegex = /^(\s*)<!--(.*?)-->/;
629 function simpleHtmlParse(html) {
630 // Based on jQuery's "clean" function, but only accounting for table-related elements.
631 // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly
633 // Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of
634 // a descendant node. For example: "<div><!-- mycomment -->abc</div>" will get parsed as "<div>abc</div>"
635 // This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node
636 // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
638 // Trim whitespace, otherwise indexOf won't work as expected
639 var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div");
641 // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column
642 var wrap = tags.match(/^<(thead|tbody|tfoot)/) && [1, "<table>", "</table>"] ||
643 !tags.indexOf("<tr") && [2, "<table><tbody>", "</tbody></table>"] ||
644 (!tags.indexOf("<td") || !tags.indexOf("<th")) && [3, "<table><tbody><tr>", "</tr></tbody></table>"] ||
645 /* anything else */ [0, "", ""];
647 // Go to html and back, then peel off extra wrappers
648 // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
649 var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
650 if (typeof window['innerShiv'] == "function") {
651 div.appendChild(window['innerShiv'](markup));
653 div.innerHTML = markup;
656 // Move to the right depth
660 return ko.utils.makeArray(div.lastChild.childNodes);
663 function jQueryHtmlParse(html) {
664 var elems = jQuery['clean']([html]);
666 // 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.
667 // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
668 // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.
669 if (elems && elems[0]) {
670 // Find the top-most parent element that's a direct child of a document fragment
672 while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)
673 elem = elem.parentNode;
674 // ... then detach it
676 elem.parentNode.removeChild(elem);
682 ko.utils.parseHtmlFragment = function(html) {
683 return typeof jQuery != 'undefined' ? jQueryHtmlParse(html) // As below, benefit from jQuery's optimisations where possible
684 : simpleHtmlParse(html); // ... otherwise, this simple logic will do in most common cases.
687 ko.utils.setHtml = function(node, html) {
688 ko.utils.emptyDomNode(node);
690 if ((html !== null) && (html !== undefined)) {
691 if (typeof html != 'string')
692 html = html.toString();
694 // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
695 // for example <tr> elements which are not normally allowed to exist on their own.
696 // If you've referenced jQuery we'll use that rather than duplicating its code.
697 if (typeof jQuery != 'undefined') {
698 jQuery(node)['html'](html);
700 // ... otherwise, use KO's own parsing logic.
701 var parsedNodes = ko.utils.parseHtmlFragment(html);
702 for (var i = 0; i < parsedNodes.length; i++)
703 node.appendChild(parsedNodes[i]);
709 ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
710 ko.exportSymbol('utils.setHtml', ko.utils.setHtml);
712 ko.memoization = (function () {
715 function randomMax8HexChars() {
716 return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
718 function generateRandomId() {
719 return randomMax8HexChars() + randomMax8HexChars();
721 function findMemoNodes(rootNode, appendToArray) {
724 if (rootNode.nodeType == 8) {
725 var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
727 appendToArray.push({ domNode: rootNode, memoId: memoId });
728 } else if (rootNode.nodeType == 1) {
729 for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)
730 findMemoNodes(childNodes[i], appendToArray);
735 memoize: function (callback) {
736 if (typeof callback != "function")
737 throw new Error("You can only pass a function to ko.memoization.memoize()");
738 var memoId = generateRandomId();
739 memos[memoId] = callback;
740 return "<!--[ko_memo:" + memoId + "]-->";
743 unmemoize: function (memoId, callbackParams) {
744 var callback = memos[memoId];
745 if (callback === undefined)
746 throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");
748 callback.apply(null, callbackParams || []);
751 finally { delete memos[memoId]; }
754 unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {
756 findMemoNodes(domNode, memos);
757 for (var i = 0, j = memos.length; i < j; i++) {
758 var node = memos[i].domNode;
759 var combinedParams = [node];
760 if (extraCallbackParamsArray)
761 ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray);
762 ko.memoization.unmemoize(memos[i].memoId, combinedParams);
763 node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
765 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)
769 parseMemoText: function (memoText) {
770 var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
771 return match ? match[1] : null;
776 ko.exportSymbol('memoization', ko.memoization);
777 ko.exportSymbol('memoization.memoize', ko.memoization.memoize);
778 ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize);
779 ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText);
780 ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);
782 'throttle': function(target, timeout) {
783 // Throttling means two things:
785 // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies
786 // notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate
787 target['throttleEvaluation'] = timeout;
789 // (2) For writable targets (observables, or writable dependent observables), we throttle *writes*
790 // so the target cannot change value synchronously or faster than a certain rate
791 var writeTimeoutInstance = null;
792 return ko.dependentObservable({
794 'write': function(value) {
795 clearTimeout(writeTimeoutInstance);
796 writeTimeoutInstance = setTimeout(function() {
803 'notify': function(target, notifyWhen) {
804 target["equalityComparer"] = notifyWhen == "always"
805 ? function() { return false } // Treat all values as not equal
806 : ko.observable["fn"]["equalityComparer"];
811 function applyExtenders(requestedExtenders) {
813 if (requestedExtenders) {
814 for (var key in requestedExtenders) {
815 var extenderHandler = ko.extenders[key];
816 if (typeof extenderHandler == 'function') {
817 target = extenderHandler(target, requestedExtenders[key]);
824 ko.exportSymbol('extenders', ko.extenders);
826 ko.subscription = function (target, callback, disposeCallback) {
827 this.target = target;
828 this.callback = callback;
829 this.disposeCallback = disposeCallback;
830 ko.exportProperty(this, 'dispose', this.dispose);
832 ko.subscription.prototype.dispose = function () {
833 this.isDisposed = true;
834 this.disposeCallback();
837 ko.subscribable = function () {
838 this._subscriptions = {};
840 ko.utils.extend(this, ko.subscribable['fn']);
841 ko.exportProperty(this, 'subscribe', this.subscribe);
842 ko.exportProperty(this, 'extend', this.extend);
843 ko.exportProperty(this, 'getSubscriptionsCount', this.getSubscriptionsCount);
846 var defaultEvent = "change";
848 ko.subscribable['fn'] = {
849 subscribe: function (callback, callbackTarget, event) {
850 event = event || defaultEvent;
851 var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
853 var subscription = new ko.subscription(this, boundCallback, function () {
854 ko.utils.arrayRemoveItem(this._subscriptions[event], subscription);
857 if (!this._subscriptions[event])
858 this._subscriptions[event] = [];
859 this._subscriptions[event].push(subscription);
863 "notifySubscribers": function (valueToNotify, event) {
864 event = event || defaultEvent;
865 if (this._subscriptions[event]) {
866 ko.utils.arrayForEach(this._subscriptions[event].slice(0), function (subscription) {
867 // In case a subscription was disposed during the arrayForEach cycle, check
868 // for isDisposed on each subscription before invoking its callback
869 if (subscription && (subscription.isDisposed !== true))
870 subscription.callback(valueToNotify);
875 getSubscriptionsCount: function () {
877 for (var eventName in this._subscriptions) {
878 if (this._subscriptions.hasOwnProperty(eventName))
879 total += this._subscriptions[eventName].length;
884 extend: applyExtenders
888 ko.isSubscribable = function (instance) {
889 return typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
892 ko.exportSymbol('subscribable', ko.subscribable);
893 ko.exportSymbol('isSubscribable', ko.isSubscribable);
895 ko.dependencyDetection = (function () {
899 begin: function (callback) {
900 _frames.push({ callback: callback, distinctDependencies:[] });
907 registerDependency: function (subscribable) {
908 if (!ko.isSubscribable(subscribable))
909 throw new Error("Only subscribable things can act as dependencies");
910 if (_frames.length > 0) {
911 var topFrame = _frames[_frames.length - 1];
912 if (ko.utils.arrayIndexOf(topFrame.distinctDependencies, subscribable) >= 0)
914 topFrame.distinctDependencies.push(subscribable);
915 topFrame.callback(subscribable);
920 var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true };
922 ko.observable = function (initialValue) {
923 var _latestValue = initialValue;
925 function observable() {
926 if (arguments.length > 0) {
929 // Ignore writes if the value hasn't changed
930 if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
931 observable.valueWillMutate();
932 _latestValue = arguments[0];
933 if (DEBUG) observable._latestValue = _latestValue;
934 observable.valueHasMutated();
936 return this; // Permits chained assignments
940 ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
944 if (DEBUG) observable._latestValue = _latestValue;
945 ko.subscribable.call(observable);
946 observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }
947 observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }
948 ko.utils.extend(observable, ko.observable['fn']);
950 ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
951 ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
956 ko.observable['fn'] = {
957 "equalityComparer": function valuesArePrimitiveAndEqual(a, b) {
958 var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
959 return oldValueIsPrimitive ? (a === b) : false;
963 var protoProperty = ko.observable.protoProperty = "__ko_proto__";
964 ko.observable['fn'][protoProperty] = ko.observable;
966 ko.hasPrototype = function(instance, prototype) {
967 if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
968 if (instance[protoProperty] === prototype) return true;
969 return ko.hasPrototype(instance[protoProperty], prototype); // Walk the prototype chain
972 ko.isObservable = function (instance) {
973 return ko.hasPrototype(instance, ko.observable);
975 ko.isWriteableObservable = function (instance) {
977 if ((typeof instance == "function") && instance[protoProperty] === ko.observable)
979 // Writeable dependent observable
980 if ((typeof instance == "function") && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
987 ko.exportSymbol('observable', ko.observable);
988 ko.exportSymbol('isObservable', ko.isObservable);
989 ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
990 ko.observableArray = function (initialValues) {
991 if (arguments.length == 0) {
992 // Zero-parameter constructor initializes to empty array
995 if ((initialValues !== null) && (initialValues !== undefined) && !('length' in initialValues))
996 throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
998 var result = ko.observable(initialValues);
999 ko.utils.extend(result, ko.observableArray['fn']);
1003 ko.observableArray['fn'] = {
1004 'remove': function (valueOrPredicate) {
1005 var underlyingArray = this();
1006 var removedValues = [];
1007 var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
1008 for (var i = 0; i < underlyingArray.length; i++) {
1009 var value = underlyingArray[i];
1010 if (predicate(value)) {
1011 if (removedValues.length === 0) {
1012 this.valueWillMutate();
1014 removedValues.push(value);
1015 underlyingArray.splice(i, 1);
1019 if (removedValues.length) {
1020 this.valueHasMutated();
1022 return removedValues;
1025 'removeAll': function (arrayOfValues) {
1026 // If you passed zero args, we remove everything
1027 if (arrayOfValues === undefined) {
1028 var underlyingArray = this();
1029 var allValues = underlyingArray.slice(0);
1030 this.valueWillMutate();
1031 underlyingArray.splice(0, underlyingArray.length);
1032 this.valueHasMutated();
1035 // If you passed an arg, we interpret it as an array of entries to remove
1038 return this['remove'](function (value) {
1039 return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
1043 'destroy': function (valueOrPredicate) {
1044 var underlyingArray = this();
1045 var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
1046 this.valueWillMutate();
1047 for (var i = underlyingArray.length - 1; i >= 0; i--) {
1048 var value = underlyingArray[i];
1049 if (predicate(value))
1050 underlyingArray[i]["_destroy"] = true;
1052 this.valueHasMutated();
1055 'destroyAll': function (arrayOfValues) {
1056 // If you passed zero args, we destroy everything
1057 if (arrayOfValues === undefined)
1058 return this['destroy'](function() { return true });
1060 // If you passed an arg, we interpret it as an array of entries to destroy
1063 return this['destroy'](function (value) {
1064 return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
1068 'indexOf': function (item) {
1069 var underlyingArray = this();
1070 return ko.utils.arrayIndexOf(underlyingArray, item);
1073 'replace': function(oldItem, newItem) {
1074 var index = this['indexOf'](oldItem);
1076 this.valueWillMutate();
1077 this()[index] = newItem;
1078 this.valueHasMutated();
1083 // Populate ko.observableArray.fn with read/write functions from native arrays
1084 ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
1085 ko.observableArray['fn'][methodName] = function () {
1086 var underlyingArray = this();
1087 this.valueWillMutate();
1088 var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
1089 this.valueHasMutated();
1090 return methodCallResult;
1094 // Populate ko.observableArray.fn with read-only functions from native arrays
1095 ko.utils.arrayForEach(["slice"], function (methodName) {
1096 ko.observableArray['fn'][methodName] = function () {
1097 var underlyingArray = this();
1098 return underlyingArray[methodName].apply(underlyingArray, arguments);
1102 ko.exportSymbol('observableArray', ko.observableArray);
1103 ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
1105 _hasBeenEvaluated = false,
1106 _isBeingEvaluated = false,
1107 readFunction = evaluatorFunctionOrOptions;
1109 if (readFunction && typeof readFunction == "object") {
1110 // Single-parameter syntax - everything is on this "options" param
1111 options = readFunction;
1112 readFunction = options["read"];
1114 // Multi-parameter syntax - construct the options according to the params passed
1115 options = options || {};
1117 readFunction = options["read"];
1119 // By here, "options" is always non-null
1120 if (typeof readFunction != "function")
1121 throw new Error("Pass a function that returns the value of the ko.computed");
1123 var writeFunction = options["write"];
1124 if (!evaluatorFunctionTarget)
1125 evaluatorFunctionTarget = options["owner"];
1127 var _subscriptionsToDependencies = [];
1128 function disposeAllSubscriptionsToDependencies() {
1129 ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) {
1130 subscription.dispose();
1132 _subscriptionsToDependencies = [];
1134 var dispose = disposeAllSubscriptionsToDependencies;
1136 // Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values
1137 // (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(),
1138 // plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
1139 var disposeWhenNodeIsRemoved = (typeof options["disposeWhenNodeIsRemoved"] == "object") ? options["disposeWhenNodeIsRemoved"] : null;
1140 var disposeWhen = options["disposeWhen"] || function() { return false; };
1141 if (disposeWhenNodeIsRemoved) {
1142 dispose = function() {
1143 ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, arguments.callee);
1144 disposeAllSubscriptionsToDependencies();
1146 ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
1147 var existingDisposeWhenFunction = disposeWhen;
1148 disposeWhen = function () {
1149 return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || existingDisposeWhenFunction();
1153 var evaluationTimeoutInstance = null;
1154 function evaluatePossiblyAsync() {
1155 var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
1156 if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
1157 clearTimeout(evaluationTimeoutInstance);
1158 evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
1160 evaluateImmediate();
1163 function evaluateImmediate() {
1164 if (_isBeingEvaluated) {
1165 // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
1166 // This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
1167 // certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
1168 // their own re-evaluation. Further discussion at https://github.com/SteveSanderson/knockout/pull/387
1172 // Don't dispose on first evaluation, because the "disposeWhen" callback might
1173 // e.g., dispose when the associated DOM element isn't in the doc, and it's not
1174 // going to be in the doc until *after* the first evaluation
1175 if (_hasBeenEvaluated && disposeWhen()) {
1180 _isBeingEvaluated = true;
1182 // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
1183 // Then, during evaluation, we cross off any that are in fact still being used.
1184 var disposalCandidates = ko.utils.arrayMap(_subscriptionsToDependencies, function(item) {return item.target;});
1186 ko.dependencyDetection.begin(function(subscribable) {
1188 if ((inOld = ko.utils.arrayIndexOf(disposalCandidates, subscribable)) >= 0)
1189 disposalCandidates[inOld] = undefined; // Don't want to dispose this subscription, as it's still being used
1191 _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync)); // Brand new subscription - add it
1194 var newValue = readFunction.call(evaluatorFunctionTarget);
1196 // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
1197 for (var i = disposalCandidates.length - 1; i >= 0; i--) {
1198 if (disposalCandidates[i])
1199 _subscriptionsToDependencies.splice(i, 1)[0].dispose();
1201 _hasBeenEvaluated = true;
1203 dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
1204 _latestValue = newValue;
1205 if (DEBUG) dependentObservable._latestValue = _latestValue;
1207 ko.dependencyDetection.end();
1210 dependentObservable["notifySubscribers"](_latestValue);
1211 _isBeingEvaluated = false;
1215 function dependentObservable() {
1216 if (arguments.length > 0) {
1217 set.apply(dependentObservable, arguments);
1224 if (typeof writeFunction === "function") {
1226 writeFunction.apply(evaluatorFunctionTarget, arguments);
1228 throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
1233 // Reading the value
1234 if (!_hasBeenEvaluated)
1235 evaluateImmediate();
1236 ko.dependencyDetection.registerDependency(dependentObservable);
1237 return _latestValue;
1240 dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; };
1241 dependentObservable.hasWriteFunction = typeof options["write"] === "function";
1242 dependentObservable.dispose = function () { dispose(); };
1244 ko.subscribable.call(dependentObservable);
1245 ko.utils.extend(dependentObservable, ko.dependentObservable['fn']);
1247 if (options['deferEvaluation'] !== true)
1248 evaluateImmediate();
1250 ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
1251 ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount);
1253 return dependentObservable;
1256 ko.isComputed = function(instance) {
1257 return ko.hasPrototype(instance, ko.dependentObservable);
1260 var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
1261 ko.dependentObservable[protoProp] = ko.observable;
1263 ko.dependentObservable['fn'] = {};
1264 ko.dependentObservable['fn'][protoProp] = ko.dependentObservable;
1266 ko.exportSymbol('dependentObservable', ko.dependentObservable);
1267 ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
1268 ko.exportSymbol('isComputed', ko.isComputed);
1271 var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
1273 ko.toJS = function(rootObject) {
1274 if (arguments.length == 0)
1275 throw new Error("When calling ko.toJS, pass the object you want to convert.");
1277 // We just unwrap everything at every level in the object graph
1278 return mapJsObjectGraph(rootObject, function(valueToMap) {
1279 // Loop because an observable's value might in turn be another observable wrapper
1280 for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++)
1281 valueToMap = valueToMap();
1286 ko.toJSON = function(rootObject, replacer, space) { // replacer and space are optional
1287 var plainJavaScriptObject = ko.toJS(rootObject);
1288 return ko.utils.stringifyJson(plainJavaScriptObject, replacer, space);
1291 function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
1292 visitedObjects = visitedObjects || new objectLookup();
1294 rootObject = mapInputCallback(rootObject);
1295 var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date));
1296 if (!canHaveProperties)
1299 var outputProperties = rootObject instanceof Array ? [] : {};
1300 visitedObjects.save(rootObject, outputProperties);
1302 visitPropertiesOrArrayEntries(rootObject, function(indexer) {
1303 var propertyValue = mapInputCallback(rootObject[indexer]);
1305 switch (typeof propertyValue) {
1310 outputProperties[indexer] = propertyValue;
1314 var previouslyMappedValue = visitedObjects.get(propertyValue);
1315 outputProperties[indexer] = (previouslyMappedValue !== undefined)
1316 ? previouslyMappedValue
1317 : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects);
1322 return outputProperties;
1325 function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
1326 if (rootObject instanceof Array) {
1327 for (var i = 0; i < rootObject.length; i++)
1330 // For arrays, also respect toJSON property for custom mappings (fixes #278)
1331 if (typeof rootObject['toJSON'] == 'function')
1332 visitorCallback('toJSON');
1334 for (var propertyName in rootObject)
1335 visitorCallback(propertyName);
1339 function objectLookup() {
1342 this.save = function(key, value) {
1343 var existingIndex = ko.utils.arrayIndexOf(keys, key);
1344 if (existingIndex >= 0)
1345 values[existingIndex] = value;
1351 this.get = function(key) {
1352 var existingIndex = ko.utils.arrayIndexOf(keys, key);
1353 return (existingIndex >= 0) ? values[existingIndex] : undefined;
1358 ko.exportSymbol('toJS', ko.toJS);
1359 ko.exportSymbol('toJSON', ko.toJSON);
1361 var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
1363 // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
1364 // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
1365 // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
1366 ko.selectExtensions = {
1367 readValue : function(element) {
1368 switch (ko.utils.tagNameLower(element)) {
1370 if (element[hasDomDataExpandoProperty] === true)
1371 return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
1372 return element.getAttribute("value");
1374 return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
1376 return element.value;
1380 writeValue: function(element, value) {
1381 switch (ko.utils.tagNameLower(element)) {
1383 switch(typeof value) {
1385 ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
1386 if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
1387 delete element[hasDomDataExpandoProperty];
1389 element.value = value;
1392 // Store arbitrary object using DomData
1393 ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
1394 element[hasDomDataExpandoProperty] = true;
1396 // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
1397 element.value = typeof value === "number" ? value : "";
1402 for (var i = element.options.length - 1; i >= 0; i--) {
1403 if (ko.selectExtensions.readValue(element.options[i]) == value) {
1404 element.selectedIndex = i;
1410 if ((value === null) || (value === undefined))
1412 element.value = value;
1419 ko.exportSymbol('selectExtensions', ko.selectExtensions);
1420 ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
1421 ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
1423 ko.jsonExpressionRewriting = (function () {
1424 var restoreCapturedTokensRegex = /\@ko_token_(\d+)\@/g;
1425 var javaScriptAssignmentTarget = /^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i;
1426 var javaScriptReservedWords = ["true", "false"];
1428 function restoreTokens(string, tokens) {
1429 var prevValue = null;
1430 while (string != prevValue) { // Keep restoring tokens until it no longer makes a difference (they may be nested)
1432 string = string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) {
1433 return tokens[tokenIndex];
1439 function isWriteableValue(expression) {
1440 if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0)
1442 return expression.match(javaScriptAssignmentTarget) !== null;
1445 function ensureQuoted(key) {
1446 var trimmedKey = ko.utils.stringTrim(key);
1447 switch (trimmedKey.length && trimmedKey.charAt(0)) {
1452 return "'" + trimmedKey + "'";
1457 bindingRewriteValidators: [],
1459 parseObjectLiteral: function(objectLiteralString) {
1460 // A full tokeniser+lexer would add too much weight to this library, so here's a simple parser
1461 // that is sufficient just to split an object literal string into a set of top-level key-value pairs
1463 var str = ko.utils.stringTrim(objectLiteralString);
1466 if (str.charAt(0) === "{")// Ignore any braces surrounding the whole object literal
1467 str = str.substring(1, str.length - 1);
1469 // Pull out any string literals and regex literals
1471 var tokenStart = null, tokenEndChar;
1472 for (var position = 0; position < str.length; position++) {
1473 var c = str.charAt(position);
1474 if (tokenStart === null) {
1479 tokenStart = position;
1483 } else if ((c == tokenEndChar) && (str.charAt(position - 1) !== "\\")) {
1484 var token = str.substring(tokenStart, position + 1);
1486 var replacement = "@ko_token_" + (tokens.length - 1) + "@";
1487 str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
1488 position -= (token.length - replacement.length);
1493 // Next pull out balanced paren, brace, and bracket blocks
1495 tokenEndChar = null;
1496 var tokenDepth = 0, tokenStartChar = null;
1497 for (var position = 0; position < str.length; position++) {
1498 var c = str.charAt(position);
1499 if (tokenStart === null) {
1501 case "{": tokenStart = position; tokenStartChar = c;
1504 case "(": tokenStart = position; tokenStartChar = c;
1507 case "[": tokenStart = position; tokenStartChar = c;
1513 if (c === tokenStartChar)
1515 else if (c === tokenEndChar) {
1517 if (tokenDepth === 0) {
1518 var token = str.substring(tokenStart, position + 1);
1520 var replacement = "@ko_token_" + (tokens.length - 1) + "@";
1521 str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
1522 position -= (token.length - replacement.length);
1528 // Now we can safely split on commas to get the key/value pairs
1530 var keyValuePairs = str.split(",");
1531 for (var i = 0, j = keyValuePairs.length; i < j; i++) {
1532 var pair = keyValuePairs[i];
1533 var colonPos = pair.indexOf(":");
1534 if ((colonPos > 0) && (colonPos < pair.length - 1)) {
1535 var key = pair.substring(0, colonPos);
1536 var value = pair.substring(colonPos + 1);
1537 result.push({ 'key': restoreTokens(key, tokens), 'value': restoreTokens(value, tokens) });
1539 result.push({ 'unknown': restoreTokens(pair, tokens) });
1545 insertPropertyAccessorsIntoJson: function (objectLiteralStringOrKeyValueArray) {
1546 var keyValueArray = typeof objectLiteralStringOrKeyValueArray === "string"
1547 ? ko.jsonExpressionRewriting.parseObjectLiteral(objectLiteralStringOrKeyValueArray)
1548 : objectLiteralStringOrKeyValueArray;
1549 var resultStrings = [], propertyAccessorResultStrings = [];
1552 for (var i = 0; keyValueEntry = keyValueArray[i]; i++) {
1553 if (resultStrings.length > 0)
1554 resultStrings.push(",");
1556 if (keyValueEntry['key']) {
1557 var quotedKey = ensureQuoted(keyValueEntry['key']), val = keyValueEntry['value'];
1558 resultStrings.push(quotedKey);
1559 resultStrings.push(":");
1560 resultStrings.push(val);
1562 if (isWriteableValue(ko.utils.stringTrim(val))) {
1563 if (propertyAccessorResultStrings.length > 0)
1564 propertyAccessorResultStrings.push(", ");
1565 propertyAccessorResultStrings.push(quotedKey + " : function(__ko_value) { " + val + " = __ko_value; }");
1567 } else if (keyValueEntry['unknown']) {
1568 resultStrings.push(keyValueEntry['unknown']);
1572 var combinedResult = resultStrings.join("");
1573 if (propertyAccessorResultStrings.length > 0) {
1574 var allPropertyAccessors = propertyAccessorResultStrings.join("");
1575 combinedResult = combinedResult + ", '_ko_property_writers' : { " + allPropertyAccessors + " } ";
1578 return combinedResult;
1581 keyValueArrayContainsKey: function(keyValueArray, key) {
1582 for (var i = 0; i < keyValueArray.length; i++)
1583 if (ko.utils.stringTrim(keyValueArray[i]['key']) == key)
1588 // Internal, private KO utility for updating model properties from within bindings
1589 // property: If the property being updated is (or might be) an observable, pass it here
1590 // If it turns out to be a writable observable, it will be written to directly
1591 // allBindingsAccessor: All bindings in the current execution context.
1592 // This will be searched for a '_ko_property_writers' property in case you're writing to a non-observable
1593 // key: The key identifying the property to be written. Example: for { hasFocus: myValue }, write to 'myValue' by specifying the key 'hasFocus'
1594 // value: The value to be written
1595 // checkIfDifferent: If true, and if the property being written is a writable observable, the value will only be written if
1596 // it is !== existing value on that writable observable
1597 writeValueToProperty: function(property, allBindingsAccessor, key, value, checkIfDifferent) {
1598 if (!property || !ko.isWriteableObservable(property)) {
1599 var propWriters = allBindingsAccessor()['_ko_property_writers'];
1600 if (propWriters && propWriters[key])
1601 propWriters[key](value);
1602 } else if (!checkIfDifferent || property() !== value) {
1609 ko.exportSymbol('jsonExpressionRewriting', ko.jsonExpressionRewriting);
1610 ko.exportSymbol('jsonExpressionRewriting.bindingRewriteValidators', ko.jsonExpressionRewriting.bindingRewriteValidators);
1611 ko.exportSymbol('jsonExpressionRewriting.parseObjectLiteral', ko.jsonExpressionRewriting.parseObjectLiteral);
1612 ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson);
1614 // "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
1615 // may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
1616 // If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
1617 // of that virtual hierarchy
1619 // The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
1620 // without having to scatter special cases all over the binding and templating code.
1622 // IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
1623 // but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
1624 // So, use node.text where available, and node.nodeValue elsewhere
1625 var commentNodesHaveTextProperty = document.createComment("test").text === "<!--test-->";
1627 var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko\s+(.*\:.*)\s*-->$/ : /^\s*ko\s+(.*\:.*)\s*$/;
1628 var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
1629 var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
1631 function isStartComment(node) {
1632 return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
1635 function isEndComment(node) {
1636 return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(endCommentRegex);
1639 function getVirtualChildren(startComment, allowUnbalanced) {
1640 var currentNode = startComment;
1643 while (currentNode = currentNode.nextSibling) {
1644 if (isEndComment(currentNode)) {
1650 children.push(currentNode);
1652 if (isStartComment(currentNode))
1655 if (!allowUnbalanced)
1656 throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
1660 function getMatchingEndComment(startComment, allowUnbalanced) {
1661 var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
1662 if (allVirtualChildren) {
1663 if (allVirtualChildren.length > 0)
1664 return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
1665 return startComment.nextSibling;
1667 return null; // Must have no matching end comment, and allowUnbalanced is true
1670 function getUnbalancedChildTags(node) {
1671 // e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span>
1672 // from <div>OK</div><!-- /ko --><!-- /ko -->, returns: <!-- /ko --><!-- /ko -->
1673 var childNode = node.firstChild, captureRemaining = null;
1676 if (captureRemaining) // We already hit an unbalanced node and are now just scooping up all subsequent nodes
1677 captureRemaining.push(childNode);
1678 else if (isStartComment(childNode)) {
1679 var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true);
1680 if (matchingEndComment) // It's a balanced tag, so skip immediately to the end of this virtual set
1681 childNode = matchingEndComment;
1683 captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point
1684 } else if (isEndComment(childNode)) {
1685 captureRemaining = [childNode]; // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing
1687 } while (childNode = childNode.nextSibling);
1689 return captureRemaining;
1692 ko.virtualElements = {
1693 allowedBindings: {},
1695 childNodes: function(node) {
1696 return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
1699 emptyNode: function(node) {
1700 if (!isStartComment(node))
1701 ko.utils.emptyDomNode(node);
1703 var virtualChildren = ko.virtualElements.childNodes(node);
1704 for (var i = 0, j = virtualChildren.length; i < j; i++)
1705 ko.removeNode(virtualChildren[i]);
1709 setDomNodeChildren: function(node, childNodes) {
1710 if (!isStartComment(node))
1711 ko.utils.setDomNodeChildren(node, childNodes);
1713 ko.virtualElements.emptyNode(node);
1714 var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
1715 for (var i = 0, j = childNodes.length; i < j; i++)
1716 endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
1720 prepend: function(containerNode, nodeToPrepend) {
1721 if (!isStartComment(containerNode)) {
1722 if (containerNode.firstChild)
1723 containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
1725 containerNode.appendChild(nodeToPrepend);
1727 // Start comments must always have a parent and at least one following sibling (the end comment)
1728 containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
1732 insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
1733 if (!isStartComment(containerNode)) {
1734 // Insert after insertion point
1735 if (insertAfterNode.nextSibling)
1736 containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
1738 containerNode.appendChild(nodeToInsert);
1740 // Children of start comments must always have a parent and at least one following sibling (the end comment)
1741 containerNode.parentNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
1745 firstChild: function(node) {
1746 if (!isStartComment(node))
1747 return node.firstChild;
1748 if (!node.nextSibling || isEndComment(node.nextSibling))
1750 return node.nextSibling;
1753 nextSibling: function(node) {
1754 if (isStartComment(node))
1755 node = getMatchingEndComment(node);
1756 if (node.nextSibling && isEndComment(node.nextSibling))
1758 return node.nextSibling;
1761 virtualNodeBindingValue: function(node) {
1762 var regexMatch = isStartComment(node);
1763 return regexMatch ? regexMatch[1] : null;
1766 normaliseVirtualElementDomStructure: function(elementVerified) {
1767 // Workaround for https://github.com/SteveSanderson/knockout/issues/155
1768 // (IE <= 8 or IE 9 quirks mode parses your HTML weirdly, treating closing </li> tags as if they don't exist, thereby moving comment nodes
1769 // that are direct descendants of <ul> into the preceding <li>)
1770 if (!htmlTagsWithOptionallyClosingChildren[ko.utils.tagNameLower(elementVerified)])
1773 // Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags
1774 // must be intended to appear *after* that child, so move them there.
1775 var childNode = elementVerified.firstChild;
1778 if (childNode.nodeType === 1) {
1779 var unbalancedTags = getUnbalancedChildTags(childNode);
1780 if (unbalancedTags) {
1781 // Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child
1782 var nodeToInsertBefore = childNode.nextSibling;
1783 for (var i = 0; i < unbalancedTags.length; i++) {
1784 if (nodeToInsertBefore)
1785 elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore);
1787 elementVerified.appendChild(unbalancedTags[i]);
1791 } while (childNode = childNode.nextSibling);
1796 ko.exportSymbol('virtualElements', ko.virtualElements);
1797 ko.exportSymbol('virtualElements.allowedBindings', ko.virtualElements.allowedBindings);
1798 ko.exportSymbol('virtualElements.emptyNode', ko.virtualElements.emptyNode);
1799 //ko.exportSymbol('virtualElements.firstChild', ko.virtualElements.firstChild); // firstChild is not minified
1800 ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter);
1801 //ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling); // nextSibling is not minified
1802 ko.exportSymbol('virtualElements.prepend', ko.virtualElements.prepend);
1803 ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomNodeChildren);
1805 var defaultBindingAttributeName = "data-bind";
1807 ko.bindingProvider = function() {
1808 this.bindingCache = {};
1811 ko.utils.extend(ko.bindingProvider.prototype, {
1812 'nodeHasBindings': function(node) {
1813 switch (node.nodeType) {
1814 case 1: return node.getAttribute(defaultBindingAttributeName) != null; // Element
1815 case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node
1816 default: return false;
1820 'getBindings': function(node, bindingContext) {
1821 var bindingsString = this['getBindingsString'](node, bindingContext);
1822 return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext) : null;
1825 // The following function is only used internally by this default provider.
1826 // It's not part of the interface definition for a general binding provider.
1827 'getBindingsString': function(node, bindingContext) {
1828 switch (node.nodeType) {
1829 case 1: return node.getAttribute(defaultBindingAttributeName); // Element
1830 case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
1831 default: return null;
1835 // The following function is only used internally by this default provider.
1836 // It's not part of the interface definition for a general binding provider.
1837 'parseBindingsString': function(bindingsString, bindingContext) {
1839 var viewModel = bindingContext['$data'],
1840 scopes = (typeof viewModel == 'object' && viewModel != null) ? [viewModel, bindingContext] : [bindingContext],
1841 bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, scopes.length, this.bindingCache);
1842 return bindingFunction(scopes);
1844 throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
1849 ko.bindingProvider['instance'] = new ko.bindingProvider();
1851 function createBindingsStringEvaluatorViaCache(bindingsString, scopesCount, cache) {
1852 var cacheKey = scopesCount + '_' + bindingsString;
1853 return cache[cacheKey]
1854 || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, scopesCount));
1857 function createBindingsStringEvaluator(bindingsString, scopesCount) {
1858 var rewrittenBindings = " { " + ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(bindingsString) + " } ";
1859 return ko.utils.buildEvalWithinScopeFunction(rewrittenBindings, scopesCount);
1863 ko.exportSymbol('bindingProvider', ko.bindingProvider);
1865 ko.bindingHandlers = {};
1867 ko.bindingContext = function(dataItem, parentBindingContext) {
1868 if (parentBindingContext) {
1869 ko.utils.extend(this, parentBindingContext); // Inherit $root and any custom properties
1870 this['$parentContext'] = parentBindingContext;
1871 this['$parent'] = parentBindingContext['$data'];
1872 this['$parents'] = (parentBindingContext['$parents'] || []).slice(0);
1873 this['$parents'].unshift(this['$parent']);
1875 this['$parents'] = [];
1876 this['$root'] = dataItem;
1878 this['$data'] = dataItem;
1880 ko.bindingContext.prototype['createChildContext'] = function (dataItem) {
1881 return new ko.bindingContext(dataItem, this);
1883 ko.bindingContext.prototype['extend'] = function(properties) {
1884 var clone = ko.utils.extend(new ko.bindingContext(), this);
1885 return ko.utils.extend(clone, properties);
1888 function validateThatBindingIsAllowedForVirtualElements(bindingName) {
1889 var validator = ko.virtualElements.allowedBindings[bindingName];
1891 throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
1894 function applyBindingsToDescendantsInternal (viewModel, elementOrVirtualElement, bindingContextsMayDifferFromDomParentElement) {
1895 var currentChild, nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
1896 while (currentChild = nextInQueue) {
1897 // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
1898 nextInQueue = ko.virtualElements.nextSibling(currentChild);
1899 applyBindingsToNodeAndDescendantsInternal(viewModel, currentChild, bindingContextsMayDifferFromDomParentElement);
1903 function applyBindingsToNodeAndDescendantsInternal (viewModel, nodeVerified, bindingContextMayDifferFromDomParentElement) {
1904 var shouldBindDescendants = true;
1906 // Perf optimisation: Apply bindings only if...
1907 // (1) We need to store the binding context on this node (because it may differ from the DOM parent node's binding context)
1908 // Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those
1909 // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
1910 var isElement = (nodeVerified.nodeType === 1);
1911 if (isElement) // Workaround IE <= 8 HTML parsing weirdness
1912 ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
1914 var shouldApplyBindings = (isElement && bindingContextMayDifferFromDomParentElement) // Case (1)
1915 || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified); // Case (2)
1916 if (shouldApplyBindings)
1917 shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, viewModel, bindingContextMayDifferFromDomParentElement).shouldBindDescendants;
1919 if (shouldBindDescendants) {
1920 // We're recursing automatically into (real or virtual) child nodes without changing binding contexts. So,
1921 // * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
1922 // hence bindingContextsMayDifferFromDomParentElement is false
1923 // * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
1924 // skip over any number of intermediate virtual elements, any of which might define a custom binding context,
1925 // hence bindingContextsMayDifferFromDomParentElement is true
1926 applyBindingsToDescendantsInternal(viewModel, nodeVerified, /* bindingContextsMayDifferFromDomParentElement: */ !isElement);
1930 function applyBindingsToNodeInternal (node, bindings, viewModelOrBindingContext, bindingContextMayDifferFromDomParentElement) {
1931 // Need to be sure that inits are only run once, and updates never run until all the inits have been run
1932 var initPhase = 0; // 0 = before all inits, 1 = during inits, 2 = after all inits
1934 // Each time the dependentObservable is evaluated (after data changes),
1935 // the binding attribute is reparsed so that it can pick out the correct
1936 // model properties in the context of the changed data.
1937 // DOM event callbacks need to be able to access this changed data,
1938 // so we need a single parsedBindings variable (shared by all callbacks
1939 // associated with this node's bindings) that all the closures can access.
1941 function makeValueAccessor(bindingKey) {
1942 return function () { return parsedBindings[bindingKey] }
1944 function parsedBindingsAccessor() {
1945 return parsedBindings;
1948 var bindingHandlerThatControlsDescendantBindings;
1949 ko.dependentObservable(
1951 // Ensure we have a nonnull binding context to work with
1952 var bindingContextInstance = viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
1953 ? viewModelOrBindingContext
1954 : new ko.bindingContext(ko.utils.unwrapObservable(viewModelOrBindingContext));
1955 var viewModel = bindingContextInstance['$data'];
1957 // Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
1958 // we can easily recover it just by scanning up the node's ancestors in the DOM
1959 // (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
1960 if (bindingContextMayDifferFromDomParentElement)
1961 ko.storedBindingContextForNode(node, bindingContextInstance);
1963 // Use evaluatedBindings if given, otherwise fall back on asking the bindings provider to give us some bindings
1964 var evaluatedBindings = (typeof bindings == "function") ? bindings() : bindings;
1965 parsedBindings = evaluatedBindings || ko.bindingProvider['instance']['getBindings'](node, bindingContextInstance);
1967 if (parsedBindings) {
1968 // First run all the inits, so bindings can register for notification on changes
1969 if (initPhase === 0) {
1971 for (var bindingKey in parsedBindings) {
1972 var binding = ko.bindingHandlers[bindingKey];
1973 if (binding && node.nodeType === 8)
1974 validateThatBindingIsAllowedForVirtualElements(bindingKey);
1976 if (binding && typeof binding["init"] == "function") {
1977 var handlerInitFn = binding["init"];
1978 var initResult = handlerInitFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
1980 // If this binding handler claims to control descendant bindings, make a note of this
1981 if (initResult && initResult['controlsDescendantBindings']) {
1982 if (bindingHandlerThatControlsDescendantBindings !== undefined)
1983 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.");
1984 bindingHandlerThatControlsDescendantBindings = bindingKey;
1991 // ... then run all the updates, which might trigger changes even on the first evaluation
1992 if (initPhase === 2) {
1993 for (var bindingKey in parsedBindings) {
1994 var binding = ko.bindingHandlers[bindingKey];
1995 if (binding && typeof binding["update"] == "function") {
1996 var handlerUpdateFn = binding["update"];
1997 handlerUpdateFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
2004 { 'disposeWhenNodeIsRemoved' : node }
2008 shouldBindDescendants: bindingHandlerThatControlsDescendantBindings === undefined
2012 var storedBindingContextDomDataKey = "__ko_bindingContext__";
2013 ko.storedBindingContextForNode = function (node, bindingContext) {
2014 if (arguments.length == 2)
2015 ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
2017 return ko.utils.domData.get(node, storedBindingContextDomDataKey);
2020 ko.applyBindingsToNode = function (node, bindings, viewModel) {
2021 if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
2022 ko.virtualElements.normaliseVirtualElementDomStructure(node);
2023 return applyBindingsToNodeInternal(node, bindings, viewModel, true);
2026 ko.applyBindingsToDescendants = function(viewModel, rootNode) {
2027 if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
2028 applyBindingsToDescendantsInternal(viewModel, rootNode, true);
2031 ko.applyBindings = function (viewModel, rootNode) {
2032 if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
2033 throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
2034 rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
2036 applyBindingsToNodeAndDescendantsInternal(viewModel, rootNode, true);
2039 // Retrieving binding context from arbitrary nodes
2040 ko.contextFor = function(node) {
2041 // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
2042 switch (node.nodeType) {
2045 var context = ko.storedBindingContextForNode(node);
2046 if (context) return context;
2047 if (node.parentNode) return ko.contextFor(node.parentNode);
2052 ko.dataFor = function(node) {
2053 var context = ko.contextFor(node);
2054 return context ? context['$data'] : undefined;
2057 ko.exportSymbol('bindingHandlers', ko.bindingHandlers);
2058 ko.exportSymbol('applyBindings', ko.applyBindings);
2059 ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants);
2060 ko.exportSymbol('applyBindingsToNode', ko.applyBindingsToNode);
2061 ko.exportSymbol('contextFor', ko.contextFor);
2062 ko.exportSymbol('dataFor', ko.dataFor);
2064 // For certain common events (currently just 'click'), allow a simplified data-binding syntax
2065 // e.g. click:handler instead of the usual full-length event:{click:handler}
2066 var eventHandlersWithShortcuts = ['click'];
2067 ko.utils.arrayForEach(eventHandlersWithShortcuts, function(eventName) {
2068 ko.bindingHandlers[eventName] = {
2069 'init': function(element, valueAccessor, allBindingsAccessor, viewModel) {
2070 var newValueAccessor = function () {
2072 result[eventName] = valueAccessor();
2075 return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindingsAccessor, viewModel);
2081 ko.bindingHandlers['event'] = {
2082 'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) {
2083 var eventsToHandle = valueAccessor() || {};
2084 for(var eventNameOutsideClosure in eventsToHandle) {
2086 var eventName = eventNameOutsideClosure; // Separate variable to be captured by event handler closure
2087 if (typeof eventName == "string") {
2088 ko.utils.registerEventHandler(element, eventName, function (event) {
2089 var handlerReturnValue;
2090 var handlerFunction = valueAccessor()[eventName];
2091 if (!handlerFunction)
2093 var allBindings = allBindingsAccessor();
2096 // Take all the event args, and prefix with the viewmodel
2097 var argsForHandler = ko.utils.makeArray(arguments);
2098 argsForHandler.unshift(viewModel);
2099 handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);
2101 if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
2102 if (event.preventDefault)
2103 event.preventDefault();
2105 event.returnValue = false;
2109 var bubble = allBindings[eventName + 'Bubble'] !== false;
2111 event.cancelBubble = true;
2112 if (event.stopPropagation)
2113 event.stopPropagation();
2122 ko.bindingHandlers['submit'] = {
2123 'init': function (element, valueAccessor, allBindingsAccessor, viewModel) {
2124 if (typeof valueAccessor() != "function")
2125 throw new Error("The value for a submit binding must be a function");
2126 ko.utils.registerEventHandler(element, "submit", function (event) {
2127 var handlerReturnValue;
2128 var value = valueAccessor();
2129 try { handlerReturnValue = value.call(viewModel, element); }
2131 if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
2132 if (event.preventDefault)
2133 event.preventDefault();
2135 event.returnValue = false;
2142 ko.bindingHandlers['visible'] = {
2143 'update': function (element, valueAccessor) {
2144 var value = ko.utils.unwrapObservable(valueAccessor());
2145 var isCurrentlyVisible = !(element.style.display == "none");
2146 if (value && !isCurrentlyVisible)
2147 element.style.display = "";
2148 else if ((!value) && isCurrentlyVisible)
2149 element.style.display = "none";
2153 ko.bindingHandlers['enable'] = {
2154 'update': function (element, valueAccessor) {
2155 var value = ko.utils.unwrapObservable(valueAccessor());
2156 if (value && element.disabled)
2157 element.removeAttribute("disabled");
2158 else if ((!value) && (!element.disabled))
2159 element.disabled = true;
2163 ko.bindingHandlers['disable'] = {
2164 'update': function (element, valueAccessor) {
2165 ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
2169 function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) {
2170 if (preferModelValue) {
2171 if (modelValue !== ko.selectExtensions.readValue(element))
2172 ko.selectExtensions.writeValue(element, modelValue);
2175 // No matter which direction we're syncing in, we want the end result to be equality between dropdown value and model value.
2176 // If they aren't equal, either we prefer the dropdown value, or the model value couldn't be represented, so either way,
2177 // change the model value to match the dropdown.
2178 if (modelValue !== ko.selectExtensions.readValue(element))
2179 ko.utils.triggerEvent(element, "change");
2182 ko.bindingHandlers['value'] = {
2183 'init': function (element, valueAccessor, allBindingsAccessor) {
2184 // Always catch "change" event; possibly other events too if asked
2185 var eventsToCatch = ["change"];
2186 var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
2187 if (requestedEventsToCatch) {
2188 if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
2189 requestedEventsToCatch = [requestedEventsToCatch];
2190 ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
2191 eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
2194 var valueUpdateHandler = function() {
2195 var modelValue = valueAccessor();
2196 var elementValue = ko.selectExtensions.readValue(element);
2197 ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue, /* checkIfDifferent: */ true);
2200 // Workaround for https://github.com/SteveSanderson/knockout/issues/122
2201 // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
2202 var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
2203 && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
2204 if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
2205 var propertyChangedFired = false;
2206 ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
2207 ko.utils.registerEventHandler(element, "blur", function() {
2208 if (propertyChangedFired) {
2209 propertyChangedFired = false;
2210 valueUpdateHandler();
2215 ko.utils.arrayForEach(eventsToCatch, function(eventName) {
2216 // The syntax "after<eventname>" means "run the handler asynchronously after the event"
2217 // This is useful, for example, to catch "keydown" events after the browser has updated the control
2218 // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
2219 var handler = valueUpdateHandler;
2220 if (ko.utils.stringStartsWith(eventName, "after")) {
2221 handler = function() { setTimeout(valueUpdateHandler, 0) };
2222 eventName = eventName.substring("after".length);
2224 ko.utils.registerEventHandler(element, eventName, handler);
2227 'update': function (element, valueAccessor) {
2228 var valueIsSelectOption = ko.utils.tagNameLower(element) === "select";
2229 var newValue = ko.utils.unwrapObservable(valueAccessor());
2230 var elementValue = ko.selectExtensions.readValue(element);
2231 var valueHasChanged = (newValue != elementValue);
2233 // 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).
2234 // 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.
2235 if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0"))
2236 valueHasChanged = true;
2238 if (valueHasChanged) {
2239 var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
2242 // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
2243 // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
2244 // to apply the value as well.
2245 var alsoApplyAsynchronously = valueIsSelectOption;
2246 if (alsoApplyAsynchronously)
2247 setTimeout(applyValueAction, 0);
2250 // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
2251 // because you're not allowed to have a model value that disagrees with a visible UI selection.
2252 if (valueIsSelectOption && (element.length > 0))
2253 ensureDropdownSelectionIsConsistentWithModelValue(element, newValue, /* preferModelValue */ false);
2257 ko.bindingHandlers['options'] = {
2258 'update': function (element, valueAccessor, allBindingsAccessor) {
2259 if (ko.utils.tagNameLower(element) !== "select")
2260 throw new Error("options binding applies only to SELECT elements");
2262 var selectWasPreviouslyEmpty = element.length == 0;
2263 var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
2264 return node.tagName && (ko.utils.tagNameLower(node) === "option") && node.selected;
2265 }), function (node) {
2266 return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
2268 var previousScrollTop = element.scrollTop;
2270 var value = ko.utils.unwrapObservable(valueAccessor());
2271 var selectedValue = element.value;
2273 // Remove all existing <option>s.
2274 // Need to use .remove() rather than .removeChild() for <option>s otherwise IE behaves oddly (https://github.com/SteveSanderson/knockout/issues/134)
2275 while (element.length > 0) {
2276 ko.cleanNode(element.options[0]);
2281 var allBindings = allBindingsAccessor();
2282 if (typeof value.length != "number")
2284 if (allBindings['optionsCaption']) {
2285 var option = document.createElement("option");
2286 ko.utils.setHtml(option, allBindings['optionsCaption']);
2287 ko.selectExtensions.writeValue(option, undefined);
2288 element.appendChild(option);
2290 for (var i = 0, j = value.length; i < j; i++) {
2291 var option = document.createElement("option");
2293 // Apply a value to the option element
2294 var optionValue = typeof allBindings['optionsValue'] == "string" ? value[i][allBindings['optionsValue']] : value[i];
2295 optionValue = ko.utils.unwrapObservable(optionValue);
2296 ko.selectExtensions.writeValue(option, optionValue);
2298 // Apply some text to the option element
2299 var optionsTextValue = allBindings['optionsText'];
2301 if (typeof optionsTextValue == "function")
2302 optionText = optionsTextValue(value[i]); // Given a function; run it against the data value
2303 else if (typeof optionsTextValue == "string")
2304 optionText = value[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
2306 optionText = optionValue; // Given no optionsText arg; use the data value itself
2307 if ((optionText === null) || (optionText === undefined))
2310 ko.utils.setTextContent(option, optionText);
2312 element.appendChild(option);
2315 // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
2316 // That's why we first added them without selection. Now it's time to set the selection.
2317 var newOptions = element.getElementsByTagName("option");
2318 var countSelectionsRetained = 0;
2319 for (var i = 0, j = newOptions.length; i < j; i++) {
2320 if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
2321 ko.utils.setOptionNodeSelectionState(newOptions[i], true);
2322 countSelectionsRetained++;
2326 element.scrollTop = previousScrollTop;
2328 if (selectWasPreviouslyEmpty && ('value' in allBindings)) {
2329 // Ensure consistency between model value and selected option.
2330 // If the dropdown is being populated for the first time here (or was otherwise previously empty),
2331 // the dropdown selection state is meaningless, so we preserve the model value.
2332 ensureDropdownSelectionIsConsistentWithModelValue(element, ko.utils.unwrapObservable(allBindings['value']), /* preferModelValue */ true);
2335 // Workaround for IE9 bug
2336 ko.utils.ensureSelectElementIsRenderedCorrectly(element);
2340 ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.optionValueDomData__';
2342 ko.bindingHandlers['selectedOptions'] = {
2343 getSelectedValuesFromSelectNode: function (selectNode) {
2345 var nodes = selectNode.childNodes;
2346 for (var i = 0, j = nodes.length; i < j; i++) {
2347 var node = nodes[i], tagName = ko.utils.tagNameLower(node);
2348 if (tagName == "option" && node.selected)
2349 result.push(ko.selectExtensions.readValue(node));
2350 else if (tagName == "optgroup") {
2351 var selectedValuesFromOptGroup = ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(node);
2352 Array.prototype.splice.apply(result, [result.length, 0].concat(selectedValuesFromOptGroup)); // Add new entries to existing 'result' instance
2357 'init': function (element, valueAccessor, allBindingsAccessor) {
2358 ko.utils.registerEventHandler(element, "change", function () {
2359 var value = valueAccessor();
2360 var valueToWrite = ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this);
2361 ko.jsonExpressionRewriting.writeValueToProperty(value, allBindingsAccessor, 'value', valueToWrite);
2364 'update': function (element, valueAccessor) {
2365 if (ko.utils.tagNameLower(element) != "select")
2366 throw new Error("values binding applies only to SELECT elements");
2368 var newValue = ko.utils.unwrapObservable(valueAccessor());
2369 if (newValue && typeof newValue.length == "number") {
2370 var nodes = element.childNodes;
2371 for (var i = 0, j = nodes.length; i < j; i++) {
2372 var node = nodes[i];
2373 if (ko.utils.tagNameLower(node) === "option")
2374 ko.utils.setOptionNodeSelectionState(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0);
2380 ko.bindingHandlers['text'] = {
2381 'update': function (element, valueAccessor) {
2382 ko.utils.setTextContent(element, valueAccessor());
2386 ko.bindingHandlers['html'] = {
2387 'init': function() {
2388 // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
2389 return { 'controlsDescendantBindings': true };
2391 'update': function (element, valueAccessor) {
2392 var value = ko.utils.unwrapObservable(valueAccessor());
2393 ko.utils.setHtml(element, value);
2397 ko.bindingHandlers['css'] = {
2398 'update': function (element, valueAccessor) {
2399 var value = ko.utils.unwrapObservable(valueAccessor() || {});
2400 for (var className in value) {
2401 if (typeof className == "string") {
2402 var shouldHaveClass = ko.utils.unwrapObservable(value[className]);
2403 ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
2409 ko.bindingHandlers['style'] = {
2410 'update': function (element, valueAccessor) {
2411 var value = ko.utils.unwrapObservable(valueAccessor() || {});
2412 for (var styleName in value) {
2413 if (typeof styleName == "string") {
2414 var styleValue = ko.utils.unwrapObservable(value[styleName]);
2415 element.style[styleName] = styleValue || ""; // Empty string removes the value, whereas null/undefined have no effect
2421 ko.bindingHandlers['uniqueName'] = {
2422 'init': function (element, valueAccessor) {
2423 if (valueAccessor()) {
2424 element.name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
2426 // Workaround IE 6/7 issue
2427 // - https://github.com/SteveSanderson/knockout/issues/197
2428 // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
2429 if (ko.utils.isIe6 || ko.utils.isIe7)
2430 element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
2434 ko.bindingHandlers['uniqueName'].currentIndex = 0;
2436 ko.bindingHandlers['checked'] = {
2437 'init': function (element, valueAccessor, allBindingsAccessor) {
2438 var updateHandler = function() {
2440 if (element.type == "checkbox") {
2441 valueToWrite = element.checked;
2442 } else if ((element.type == "radio") && (element.checked)) {
2443 valueToWrite = element.value;
2445 return; // "checked" binding only responds to checkboxes and selected radio buttons
2448 var modelValue = valueAccessor();
2449 if ((element.type == "checkbox") && (ko.utils.unwrapObservable(modelValue) instanceof Array)) {
2450 // For checkboxes bound to an array, we add/remove the checkbox value to that array
2451 // This works for both observable and non-observable arrays
2452 var existingEntryIndex = ko.utils.arrayIndexOf(ko.utils.unwrapObservable(modelValue), element.value);
2453 if (element.checked && (existingEntryIndex < 0))
2454 modelValue.push(element.value);
2455 else if ((!element.checked) && (existingEntryIndex >= 0))
2456 modelValue.splice(existingEntryIndex, 1);
2458 ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
2461 ko.utils.registerEventHandler(element, "click", updateHandler);
2463 // IE 6 won't allow radio buttons to be selected unless they have a name
2464 if ((element.type == "radio") && !element.name)
2465 ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
2467 'update': function (element, valueAccessor) {
2468 var value = ko.utils.unwrapObservable(valueAccessor());
2470 if (element.type == "checkbox") {
2471 if (value instanceof Array) {
2472 // When bound to an array, the checkbox being checked represents its value being present in that array
2473 element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0;
2475 // When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
2476 element.checked = value;
2478 } else if (element.type == "radio") {
2479 element.checked = (element.value == value);
2484 var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
2485 ko.bindingHandlers['attr'] = {
2486 'update': function(element, valueAccessor, allBindingsAccessor) {
2487 var value = ko.utils.unwrapObservable(valueAccessor()) || {};
2488 for (var attrName in value) {
2489 if (typeof attrName == "string") {
2490 var attrValue = ko.utils.unwrapObservable(value[attrName]);
2492 // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
2493 // when someProp is a "no value"-like value (strictly null, false, or undefined)
2494 // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
2495 var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
2497 element.removeAttribute(attrName);
2499 // In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
2500 // HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
2501 // but instead of figuring out the mode, we'll just set the attribute through the Javascript
2502 // property for IE <= 8.
2503 if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
2504 attrName = attrHtmlToJavascriptMap[attrName];
2506 element.removeAttribute(attrName);
2508 element[attrName] = attrValue;
2509 } else if (!toRemove) {
2510 element.setAttribute(attrName, attrValue.toString());
2517 ko.bindingHandlers['hasfocus'] = {
2518 'init': function(element, valueAccessor, allBindingsAccessor) {
2519 var writeValue = function(valueToWrite) {
2520 var modelValue = valueAccessor();
2521 ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'hasfocus', valueToWrite, true);
2523 ko.utils.registerEventHandler(element, "focus", function() { writeValue(true) });
2524 ko.utils.registerEventHandler(element, "focusin", function() { writeValue(true) }); // For IE
2525 ko.utils.registerEventHandler(element, "blur", function() { writeValue(false) });
2526 ko.utils.registerEventHandler(element, "focusout", function() { writeValue(false) }); // For IE
2528 'update': function(element, valueAccessor) {
2529 var value = ko.utils.unwrapObservable(valueAccessor());
2530 value ? element.focus() : element.blur();
2531 ko.utils.triggerEvent(element, value ? "focusin" : "focusout"); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
2535 // "with: someExpression" is equivalent to "template: { if: someExpression, data: someExpression }"
2536 ko.bindingHandlers['with'] = {
2537 makeTemplateValueAccessor: function(valueAccessor) {
2538 return function() { var value = valueAccessor(); return { 'if': value, 'data': value, 'templateEngine': ko.nativeTemplateEngine.instance } };
2540 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2541 return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor));
2543 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2544 return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
2547 ko.jsonExpressionRewriting.bindingRewriteValidators['with'] = false; // Can't rewrite control flow bindings
2548 ko.virtualElements.allowedBindings['with'] = true;
2550 // "if: someExpression" is equivalent to "template: { if: someExpression }"
2551 ko.bindingHandlers['if'] = {
2552 makeTemplateValueAccessor: function(valueAccessor) {
2553 return function() { return { 'if': valueAccessor(), 'templateEngine': ko.nativeTemplateEngine.instance } };
2555 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2556 return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor));
2558 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2559 return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
2562 ko.jsonExpressionRewriting.bindingRewriteValidators['if'] = false; // Can't rewrite control flow bindings
2563 ko.virtualElements.allowedBindings['if'] = true;
2565 // "ifnot: someExpression" is equivalent to "template: { ifnot: someExpression }"
2566 ko.bindingHandlers['ifnot'] = {
2567 makeTemplateValueAccessor: function(valueAccessor) {
2568 return function() { return { 'ifnot': valueAccessor(), 'templateEngine': ko.nativeTemplateEngine.instance } };
2570 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2571 return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['ifnot'].makeTemplateValueAccessor(valueAccessor));
2573 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2574 return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['ifnot'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
2577 ko.jsonExpressionRewriting.bindingRewriteValidators['ifnot'] = false; // Can't rewrite control flow bindings
2578 ko.virtualElements.allowedBindings['ifnot'] = true;
2580 // "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
2581 // "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
2582 ko.bindingHandlers['foreach'] = {
2583 makeTemplateValueAccessor: function(valueAccessor) {
2585 var bindingValue = ko.utils.unwrapObservable(valueAccessor());
2587 // If bindingValue is the array, just pass it on its own
2588 if ((!bindingValue) || typeof bindingValue.length == "number")
2589 return { 'foreach': bindingValue, 'templateEngine': ko.nativeTemplateEngine.instance };
2591 // If bindingValue.data is the array, preserve all relevant options
2593 'foreach': bindingValue['data'],
2594 'includeDestroyed': bindingValue['includeDestroyed'],
2595 'afterAdd': bindingValue['afterAdd'],
2596 'beforeRemove': bindingValue['beforeRemove'],
2597 'afterRender': bindingValue['afterRender'],
2598 'templateEngine': ko.nativeTemplateEngine.instance
2602 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2603 return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
2605 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
2606 return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
2609 ko.jsonExpressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
2610 ko.virtualElements.allowedBindings['foreach'] = true;
2611 // If you want to make a custom template engine,
2613 // [1] Inherit from this class (like ko.nativeTemplateEngine does)
2614 // [2] Override 'renderTemplateSource', supplying a function with this signature:
2616 // function (templateSource, bindingContext, options) {
2617 // // - templateSource.text() is the text of the template you should render
2618 // // - bindingContext.$data is the data you should pass into the template
2619 // // - you might also want to make bindingContext.$parent, bindingContext.$parents,
2620 // // and bindingContext.$root available in the template too
2621 // // - options gives you access to any other properties set on "data-bind: { template: options }"
2623 // // Return value: an array of DOM nodes
2626 // [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
2628 // function (script) {
2629 // // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
2630 // // For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
2633 // This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
2634 // If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
2635 // and then you don't need to override 'createJavaScriptEvaluatorBlock'.
2637 ko.templateEngine = function () { };
2639 ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
2640 throw new Error("Override renderTemplateSource");
2643 ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
2644 throw new Error("Override createJavaScriptEvaluatorBlock");
2647 ko.templateEngine.prototype['makeTemplateSource'] = function(template, templateDocument) {
2649 if (typeof template == "string") {
2650 templateDocument = templateDocument || document;
2651 var elem = templateDocument.getElementById(template);
2653 throw new Error("Cannot find template with ID " + template);
2654 return new ko.templateSources.domElement(elem);
2655 } else if ((template.nodeType == 1) || (template.nodeType == 8)) {
2656 // Anonymous template
2657 return new ko.templateSources.anonymousTemplate(template);
2659 throw new Error("Unknown template type: " + template);
2662 ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) {
2663 var templateSource = this['makeTemplateSource'](template, templateDocument);
2664 return this['renderTemplateSource'](templateSource, bindingContext, options);
2667 ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) {
2668 // Skip rewriting if requested
2669 if (this['allowTemplateRewriting'] === false)
2672 // Perf optimisation - see below
2673 var templateIsInExternalDocument = templateDocument && templateDocument != document;
2674 if (!templateIsInExternalDocument && this.knownRewrittenTemplates && this.knownRewrittenTemplates[template])
2677 return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten");
2680 ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, templateDocument) {
2681 var templateSource = this['makeTemplateSource'](template, templateDocument);
2682 var rewritten = rewriterCallback(templateSource['text']());
2683 templateSource['text'](rewritten);
2684 templateSource['data']("isRewritten", true);
2686 // Perf optimisation - for named templates, track which ones have been rewritten so we can
2687 // answer 'isTemplateRewritten' *without* having to use getElementById (which is slow on IE < 8)
2689 // Note that we only cache the status for templates in the main document, because caching on a per-doc
2690 // basis complicates the implementation excessively. In a future version of KO, we will likely remove
2691 // this 'isRewritten' cache entirely anyway, because the benefit is extremely minor and only applies
2692 // to rewritable templates, which are pretty much deprecated since KO 2.0.
2693 var templateIsInExternalDocument = templateDocument && templateDocument != document;
2694 if (!templateIsInExternalDocument && typeof template == "string") {
2695 this.knownRewrittenTemplates = this.knownRewrittenTemplates || {};
2696 this.knownRewrittenTemplates[template] = true;
2700 ko.exportSymbol('templateEngine', ko.templateEngine);
2702 ko.templateRewriting = (function () {
2703 var memoizeDataBindingAttributeSyntaxRegex = /(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9\-]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi;
2704 var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
2706 function validateDataBindValuesForRewriting(keyValueArray) {
2707 var allValidators = ko.jsonExpressionRewriting.bindingRewriteValidators;
2708 for (var i = 0; i < keyValueArray.length; i++) {
2709 var key = keyValueArray[i]['key'];
2710 if (allValidators.hasOwnProperty(key)) {
2711 var validator = allValidators[key];
2713 if (typeof validator === "function") {
2714 var possibleErrorMessage = validator(keyValueArray[i]['value']);
2715 if (possibleErrorMessage)
2716 throw new Error(possibleErrorMessage);
2717 } else if (!validator) {
2718 throw new Error("This template engine does not support the '" + key + "' binding within its templates");
2724 function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, templateEngine) {
2725 var dataBindKeyValueArray = ko.jsonExpressionRewriting.parseObjectLiteral(dataBindAttributeValue);
2726 validateDataBindValuesForRewriting(dataBindKeyValueArray);
2727 var rewrittenDataBindAttributeValue = ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(dataBindKeyValueArray);
2729 // For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
2730 // anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
2731 // extra indirection.
2732 var applyBindingsToNextSiblingScript = "ko.templateRewriting.applyMemoizedBindingsToNextSibling(function() { \
2733 return (function() { return { " + rewrittenDataBindAttributeValue + " } })() \
2735 return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
2739 ensureTemplateIsRewritten: function (template, templateEngine, templateDocument) {
2740 if (!templateEngine['isTemplateRewritten'](template, templateDocument))
2741 templateEngine['rewriteTemplate'](template, function (htmlString) {
2742 return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine);
2743 }, templateDocument);
2746 memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
2747 return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
2748 return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[6], /* tagToRetain: */ arguments[1], templateEngine);
2749 }).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
2750 return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", templateEngine);
2754 applyMemoizedBindingsToNextSibling: function (bindings) {
2755 return ko.memoization.memoize(function (domNode, bindingContext) {
2756 if (domNode.nextSibling)
2757 ko.applyBindingsToNode(domNode.nextSibling, bindings, bindingContext);
2763 ko.exportSymbol('templateRewriting', ko.templateRewriting);
2764 ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templateRewriting.applyMemoizedBindingsToNextSibling); // Exported only because it has to be referenced by string lookup from within rewritten template
2766 // A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving
2767 // logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.)
2769 // Two are provided by default:
2770 // 1. ko.templateSources.domElement - reads/writes the text content of an arbitrary DOM element
2771 // 2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but
2772 // without reading/writing the actual element text content, since it will be overwritten
2773 // with the rendered template output.
2774 // You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
2775 // Template sources need to have the following functions:
2776 // text() - returns the template text from your storage location
2777 // text(value) - writes the supplied template text to your storage location
2778 // data(key) - reads values stored using data(key, value) - see below
2779 // data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
2781 // Optionally, template sources can also have the following functions:
2782 // nodes() - returns a DOM element containing the nodes of this template, where available
2783 // nodes(value) - writes the given DOM element to your storage location
2784 // If a DOM element is available for a given template source, template engines are encouraged to use it in preference over text()
2785 // for improved speed. However, all templateSources must supply text() even if they don't supply nodes().
2787 // Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were
2788 // using and overriding "makeTemplateSource" to return an instance of your custom template source.
2790 ko.templateSources = {};
2792 // ---- ko.templateSources.domElement -----
2794 ko.templateSources.domElement = function(element) {
2795 this.domElement = element;
2798 ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
2799 var tagNameLower = ko.utils.tagNameLower(this.domElement),
2800 elemContentsProperty = tagNameLower === "script" ? "text"
2801 : tagNameLower === "textarea" ? "value"
2804 if (arguments.length == 0) {
2805 return this.domElement[elemContentsProperty];
2807 var valueToWrite = arguments[0];
2808 if (elemContentsProperty === "innerHTML")
2809 ko.utils.setHtml(this.domElement, valueToWrite);
2811 this.domElement[elemContentsProperty] = valueToWrite;
2815 ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
2816 if (arguments.length === 1) {
2817 return ko.utils.domData.get(this.domElement, "templateSourceData_" + key);
2819 ko.utils.domData.set(this.domElement, "templateSourceData_" + key, arguments[1]);
2823 // ---- ko.templateSources.anonymousTemplate -----
2824 // Anonymous templates are normally saved/retrieved as DOM nodes through "nodes".
2825 // For compatibility, you can also read "text"; it will be serialized from the nodes on demand.
2826 // Writing to "text" is still supported, but then the template data will not be available as DOM nodes.
2828 var anonymousTemplatesDomDataKey = "__ko_anon_template__";
2829 ko.templateSources.anonymousTemplate = function(element) {
2830 this.domElement = element;
2832 ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
2833 ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
2834 if (arguments.length == 0) {
2835 var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
2836 if (templateData.textData === undefined && templateData.containerData)
2837 templateData.textData = templateData.containerData.innerHTML;
2838 return templateData.textData;
2840 var valueToWrite = arguments[0];
2841 ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {textData: valueToWrite});
2844 ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
2845 if (arguments.length == 0) {
2846 var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
2847 return templateData.containerData;
2849 var valueToWrite = arguments[0];
2850 ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {containerData: valueToWrite});
2854 ko.exportSymbol('templateSources', ko.templateSources);
2855 ko.exportSymbol('templateSources.domElement', ko.templateSources.domElement);
2856 ko.exportSymbol('templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate);
2859 var _templateEngine;
2860 ko.setTemplateEngine = function (templateEngine) {
2861 if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
2862 throw new Error("templateEngine must inherit from ko.templateEngine");
2863 _templateEngine = templateEngine;
2866 function invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, action) {
2867 var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode);
2868 while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) {
2869 nextInQueue = ko.virtualElements.nextSibling(node);
2870 if (node.nodeType === 1 || node.nodeType === 8)
2875 function activateBindingsOnContinuousNodeArray(continuousNodeArray, bindingContext) {
2876 // To be used on any nodes that have been rendered by a template and have been inserted into some parent element
2877 // Walks through continuousNodeArray (which *must* be continuous, i.e., an uninterrupted sequence of sibling nodes, because
2878 // the algorithm for walking them relies on this), and for each top-level item in the virtual-element sense,
2879 // (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings
2880 // (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting)
2882 if (continuousNodeArray.length) {
2883 var firstNode = continuousNodeArray[0], lastNode = continuousNodeArray[continuousNodeArray.length - 1];
2885 // Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
2886 // whereas a regular applyBindings won't introduce new memoized nodes
2887 invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, function(node) {
2888 ko.applyBindings(bindingContext, node);
2890 invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, function(node) {
2891 ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
2896 function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
2897 return nodeOrNodeArray.nodeType ? nodeOrNodeArray
2898 : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
2902 function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
2903 options = options || {};
2904 var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
2905 var templateDocument = firstTargetNode && firstTargetNode.ownerDocument;
2906 var templateEngineToUse = (options['templateEngine'] || _templateEngine);
2907 ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument);
2908 var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument);
2910 // Loosely check result is an array of DOM nodes
2911 if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
2912 throw new Error("Template engine must return an array of DOM nodes");
2914 var haveAddedNodesToParent = false;
2915 switch (renderMode) {
2916 case "replaceChildren":
2917 ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
2918 haveAddedNodesToParent = true;
2921 ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
2922 haveAddedNodesToParent = true;
2924 case "ignoreTargetNode": break;
2926 throw new Error("Unknown renderMode: " + renderMode);
2929 if (haveAddedNodesToParent) {
2930 activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
2931 if (options['afterRender'])
2932 options['afterRender'](renderedNodesArray, bindingContext['$data']);
2935 return renderedNodesArray;
2938 ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
2939 options = options || {};
2940 if ((options['templateEngine'] || _templateEngine) == undefined)
2941 throw new Error("Set a template engine before calling renderTemplate");
2942 renderMode = renderMode || "replaceChildren";
2944 if (targetNodeOrNodeArray) {
2945 var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
2947 var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
2948 var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
2950 return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
2952 // Ensure we've got a proper binding context to work with
2953 var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
2954 ? dataOrBindingContext
2955 : new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
2957 // Support selecting template as a function of the data being rendered
2958 var templateName = typeof(template) == 'function' ? template(bindingContext['$data']) : template;
2960 var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
2961 if (renderMode == "replaceNode") {
2962 targetNodeOrNodeArray = renderedNodesArray;
2963 firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
2967 { 'disposeWhen': whenToDispose, 'disposeWhenNodeIsRemoved': activelyDisposeWhenNodeIsRemoved }
2970 // We don't yet have a DOM node to evaluate, so use a memo and render the template later when there is a DOM node
2971 return ko.memoization.memoize(function (domNode) {
2972 ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
2977 ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
2978 // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
2979 // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
2980 var arrayItemContext;
2982 // This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
2983 var executeTemplateForArrayItem = function (arrayValue, index) {
2984 // Support selecting template as a function of the data being rendered
2985 var templateName = typeof(template) == 'function' ? template(arrayValue) : template;
2986 arrayItemContext = parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue));
2987 arrayItemContext['$index'] = index;
2988 return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
2991 // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
2992 var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
2993 activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
2994 if (options['afterRender'])
2995 options['afterRender'](addedNodesArray, arrayValue);
2998 return ko.dependentObservable(function () {
2999 var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
3000 if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
3001 unwrappedArray = [unwrappedArray];
3003 // Filter out any entries marked as destroyed
3004 var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
3005 return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
3008 ko.utils.setDomNodeChildrenFromArrayMapping(targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback);
3010 }, null, { 'disposeWhenNodeIsRemoved': targetNode });
3013 var templateSubscriptionDomDataKey = '__ko__templateSubscriptionDomDataKey__';
3014 function disposeOldSubscriptionAndStoreNewOne(element, newSubscription) {
3015 var oldSubscription = ko.utils.domData.get(element, templateSubscriptionDomDataKey);
3016 if (oldSubscription && (typeof(oldSubscription.dispose) == 'function'))
3017 oldSubscription.dispose();
3018 ko.utils.domData.set(element, templateSubscriptionDomDataKey, newSubscription);
3021 ko.bindingHandlers['template'] = {
3022 'init': function(element, valueAccessor) {
3023 // Support anonymous templates
3024 var bindingValue = ko.utils.unwrapObservable(valueAccessor());
3025 if ((typeof bindingValue != "string") && (!bindingValue['name']) && (element.nodeType == 1 || element.nodeType == 8)) {
3026 // It's an anonymous template - store the element contents, then clear the element
3027 var templateNodes = element.nodeType == 1 ? element.childNodes : ko.virtualElements.childNodes(element),
3028 container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
3029 new ko.templateSources.anonymousTemplate(element)['nodes'](container);
3031 return { 'controlsDescendantBindings': true };
3033 'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
3034 var bindingValue = ko.utils.unwrapObservable(valueAccessor());
3036 var shouldDisplay = true;
3038 if (typeof bindingValue == "string") {
3039 templateName = bindingValue;
3041 templateName = bindingValue['name'];
3043 // Support "if"/"ifnot" conditions
3044 if ('if' in bindingValue)
3045 shouldDisplay = shouldDisplay && ko.utils.unwrapObservable(bindingValue['if']);
3046 if ('ifnot' in bindingValue)
3047 shouldDisplay = shouldDisplay && !ko.utils.unwrapObservable(bindingValue['ifnot']);
3050 var templateSubscription = null;
3052 if ((typeof bindingValue === 'object') && ('foreach' in bindingValue)) { // Note: can't use 'in' operator on strings
3053 // Render once for each data point (treating data set as empty if shouldDisplay==false)
3054 var dataArray = (shouldDisplay && bindingValue['foreach']) || [];
3055 templateSubscription = ko.renderTemplateForEach(templateName || element, dataArray, /* options: */ bindingValue, element, bindingContext);
3057 if (shouldDisplay) {
3058 // Render once for this single data point (or use the viewModel if no data was provided)
3059 var innerBindingContext = (typeof bindingValue == 'object') && ('data' in bindingValue)
3060 ? bindingContext['createChildContext'](ko.utils.unwrapObservable(bindingValue['data'])) // Given an explitit 'data' value, we create a child binding context for it
3061 : bindingContext; // Given no explicit 'data' value, we retain the same binding context
3062 templateSubscription = ko.renderTemplate(templateName || element, innerBindingContext, /* options: */ bindingValue, element);
3064 ko.virtualElements.emptyNode(element);
3067 // It only makes sense to have a single template subscription per element (otherwise which one should have its output displayed?)
3068 disposeOldSubscriptionAndStoreNewOne(element, templateSubscription);
3072 // Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
3073 ko.jsonExpressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
3074 var parsedBindingValue = ko.jsonExpressionRewriting.parseObjectLiteral(bindingValue);
3076 if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
3077 return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting)
3079 if (ko.jsonExpressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
3080 return null; // Named templates can be rewritten, so return "no error"
3081 return "This template engine does not support anonymous templates nested within its templates";
3084 ko.virtualElements.allowedBindings['template'] = true;
3087 ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
3088 ko.exportSymbol('renderTemplate', ko.renderTemplate);
3091 // Simple calculation based on Levenshtein distance.
3092 function calculateEditDistanceMatrix(oldArray, newArray, maxAllowedDistance) {
3094 for (var i = 0; i <= newArray.length; i++)
3097 // Top row - transform old array into empty array via deletions
3098 for (var i = 0, j = Math.min(oldArray.length, maxAllowedDistance); i <= j; i++)
3099 distances[0][i] = i;
3101 // Left row - transform empty array into new array via additions
3102 for (var i = 1, j = Math.min(newArray.length, maxAllowedDistance); i <= j; i++) {
3103 distances[i][0] = i;
3106 // Fill out the body of the array
3107 var oldIndex, oldIndexMax = oldArray.length, newIndex, newIndexMax = newArray.length;
3108 var distanceViaAddition, distanceViaDeletion;
3109 for (oldIndex = 1; oldIndex <= oldIndexMax; oldIndex++) {
3110 var newIndexMinForRow = Math.max(1, oldIndex - maxAllowedDistance);
3111 var newIndexMaxForRow = Math.min(newIndexMax, oldIndex + maxAllowedDistance);
3112 for (newIndex = newIndexMinForRow; newIndex <= newIndexMaxForRow; newIndex++) {
3113 if (oldArray[oldIndex - 1] === newArray[newIndex - 1])
3114 distances[newIndex][oldIndex] = distances[newIndex - 1][oldIndex - 1];
3116 var northDistance = distances[newIndex - 1][oldIndex] === undefined ? Number.MAX_VALUE : distances[newIndex - 1][oldIndex] + 1;
3117 var westDistance = distances[newIndex][oldIndex - 1] === undefined ? Number.MAX_VALUE : distances[newIndex][oldIndex - 1] + 1;
3118 distances[newIndex][oldIndex] = Math.min(northDistance, westDistance);
3126 function findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray) {
3127 var oldIndex = oldArray.length;
3128 var newIndex = newArray.length;
3129 var editScript = [];
3130 var maxDistance = editDistanceMatrix[newIndex][oldIndex];
3131 if (maxDistance === undefined)
3132 return null; // maxAllowedDistance must be too small
3133 while ((oldIndex > 0) || (newIndex > 0)) {
3134 var me = editDistanceMatrix[newIndex][oldIndex];
3135 var distanceViaAdd = (newIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex] : maxDistance + 1;
3136 var distanceViaDelete = (oldIndex > 0) ? editDistanceMatrix[newIndex][oldIndex - 1] : maxDistance + 1;
3137 var distanceViaRetain = (newIndex > 0) && (oldIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex - 1] : maxDistance + 1;
3138 if ((distanceViaAdd === undefined) || (distanceViaAdd < me - 1)) distanceViaAdd = maxDistance + 1;
3139 if ((distanceViaDelete === undefined) || (distanceViaDelete < me - 1)) distanceViaDelete = maxDistance + 1;
3140 if (distanceViaRetain < me - 1) distanceViaRetain = maxDistance + 1;
3142 if ((distanceViaAdd <= distanceViaDelete) && (distanceViaAdd < distanceViaRetain)) {
3143 editScript.push({ status: "added", value: newArray[newIndex - 1] });
3145 } else if ((distanceViaDelete < distanceViaAdd) && (distanceViaDelete < distanceViaRetain)) {
3146 editScript.push({ status: "deleted", value: oldArray[oldIndex - 1] });
3149 editScript.push({ status: "retained", value: oldArray[oldIndex - 1] });
3154 return editScript.reverse();
3157 ko.utils.compareArrays = function (oldArray, newArray, maxEditsToConsider) {
3158 if (maxEditsToConsider === undefined) {
3159 return ko.utils.compareArrays(oldArray, newArray, 1) // First consider likely case where there is at most one edit (very fast)
3160 || ko.utils.compareArrays(oldArray, newArray, 10) // If that fails, account for a fair number of changes while still being fast
3161 || ko.utils.compareArrays(oldArray, newArray, Number.MAX_VALUE); // Ultimately give the right answer, even though it may take a long time
3163 oldArray = oldArray || [];
3164 newArray = newArray || [];
3165 var editDistanceMatrix = calculateEditDistanceMatrix(oldArray, newArray, maxEditsToConsider);
3166 return findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray);
3171 ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
3175 // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
3176 // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
3177 // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
3178 // so that its children is again the concatenation of the mappings of the array elements, but don't re-map any array elements that we
3179 // previously mapped - retain those nodes, and just insert/delete other ones
3181 // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
3182 // You can use this, for example, to activate bindings on those nodes.
3184 function fixUpVirtualElements(contiguousNodeArray) {
3185 // Ensures that contiguousNodeArray really *is* an array of contiguous siblings, even if some of the interior
3186 // ones have changed since your array was first built (e.g., because your array contains virtual elements, and
3187 // their virtual children changed when binding was applied to them).
3188 // This is needed so that we can reliably remove or update the nodes corresponding to a given array item
3190 if (contiguousNodeArray.length > 2) {
3191 // Build up the actual new contiguous node set
3192 var current = contiguousNodeArray[0], last = contiguousNodeArray[contiguousNodeArray.length - 1], newContiguousSet = [current];
3193 while (current !== last) {
3194 current = current.nextSibling;
3195 if (!current) // Won't happen, except if the developer has manually removed some DOM elements (then we're in an undefined scenario)
3197 newContiguousSet.push(current);
3200 // ... then mutate the input array to match this.
3201 // (The following line replaces the contents of contiguousNodeArray with newContiguousSet)
3202 Array.prototype.splice.apply(contiguousNodeArray, [0, contiguousNodeArray.length].concat(newContiguousSet));
3206 function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
3207 // Map this array value inside a dependentObservable so we re-map when any dependency changes
3208 var mappedNodes = [];
3209 var dependentObservable = ko.dependentObservable(function() {
3210 var newMappedNodes = mapping(valueToMap, index) || [];
3212 // On subsequent evaluations, just replace the previously-inserted DOM nodes
3213 if (mappedNodes.length > 0) {
3214 fixUpVirtualElements(mappedNodes);
3215 ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
3216 if (callbackAfterAddingNodes)
3217 callbackAfterAddingNodes(valueToMap, newMappedNodes);
3220 // Replace the contents of the mappedNodes array, thereby updating the record
3221 // of which nodes would be deleted if valueToMap was itself later removed
3222 mappedNodes.splice(0, mappedNodes.length);
3223 ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
3224 }, null, { 'disposeWhenNodeIsRemoved': containerNode, 'disposeWhen': function() { return (mappedNodes.length == 0) || !ko.utils.domNodeIsAttachedToDocument(mappedNodes[0]) } });
3225 return { mappedNodes : mappedNodes, dependentObservable : dependentObservable };
3228 var lastMappingResultDomDataKey = "setDomNodeChildrenFromArrayMapping_lastMappingResult";
3230 ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
3231 // Compare the provided array against the previous one
3232 array = array || [];
3233 options = options || {};
3234 var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
3235 var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
3236 var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
3237 var editScript = ko.utils.compareArrays(lastArray, array);
3239 // Build the new mapping result
3240 var newMappingResult = [];
3241 var lastMappingResultIndex = 0;
3242 var nodesToDelete = [];
3243 var newMappingResultIndex = 0;
3244 var nodesAdded = [];
3245 var insertAfterNode = null;
3246 for (var i = 0, j = editScript.length; i < j; i++) {
3247 switch (editScript[i].status) {
3249 // Just keep the information - don't touch the nodes
3250 var dataToRetain = lastMappingResult[lastMappingResultIndex];
3251 dataToRetain.indexObservable(newMappingResultIndex);
3252 newMappingResultIndex = newMappingResult.push(dataToRetain);
3253 if (dataToRetain.domNodes.length > 0)
3254 insertAfterNode = dataToRetain.domNodes[dataToRetain.domNodes.length - 1];
3255 lastMappingResultIndex++;
3259 // Stop tracking changes to the mapping for these nodes
3260 lastMappingResult[lastMappingResultIndex].dependentObservable.dispose();
3262 // Queue these nodes for later removal
3263 fixUpVirtualElements(lastMappingResult[lastMappingResultIndex].domNodes);
3264 ko.utils.arrayForEach(lastMappingResult[lastMappingResultIndex].domNodes, function (node) {
3265 nodesToDelete.push({
3268 value: editScript[i].value
3270 insertAfterNode = node;
3272 lastMappingResultIndex++;
3276 var valueToMap = editScript[i].value;
3277 var indexObservable = ko.observable(newMappingResultIndex);
3278 var mapData = mapNodeAndRefreshWhenChanged(domNode, mapping, valueToMap, callbackAfterAddingNodes, indexObservable);
3279 var mappedNodes = mapData.mappedNodes;
3281 // On the first evaluation, insert the nodes at the current insertion point
3282 newMappingResultIndex = newMappingResult.push({
3283 arrayEntry: editScript[i].value,
3284 domNodes: mappedNodes,
3285 dependentObservable: mapData.dependentObservable,
3286 indexObservable: indexObservable
3288 for (var nodeIndex = 0, nodeIndexMax = mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) {
3289 var node = mappedNodes[nodeIndex];
3293 value: editScript[i].value
3295 if (insertAfterNode == null) {
3296 // Insert "node" (the newly-created node) as domNode's first child
3297 ko.virtualElements.prepend(domNode, node);
3299 // Insert "node" into "domNode" immediately after "insertAfterNode"
3300 ko.virtualElements.insertAfter(domNode, node, insertAfterNode);
3302 insertAfterNode = node;
3304 if (callbackAfterAddingNodes)
3305 callbackAfterAddingNodes(valueToMap, mappedNodes, indexObservable);
3310 ko.utils.arrayForEach(nodesToDelete, function (node) { ko.cleanNode(node.element) });
3312 var invokedBeforeRemoveCallback = false;
3313 if (!isFirstExecution) {
3314 if (options['afterAdd']) {
3315 for (var i = 0; i < nodesAdded.length; i++)
3316 options['afterAdd'](nodesAdded[i].element, nodesAdded[i].index, nodesAdded[i].value);
3318 if (options['beforeRemove']) {
3319 for (var i = 0; i < nodesToDelete.length; i++)
3320 options['beforeRemove'](nodesToDelete[i].element, nodesToDelete[i].index, nodesToDelete[i].value);
3321 invokedBeforeRemoveCallback = true;
3324 if (!invokedBeforeRemoveCallback && nodesToDelete.length) {
3325 for (var i = 0; i < nodesToDelete.length; i++) {
3326 var element = nodesToDelete[i].element;
3327 if (element.parentNode)
3328 element.parentNode.removeChild(element);
3332 // Store a copy of the array items we just considered so we can difference it next time
3333 ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
3337 ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
3338 ko.nativeTemplateEngine = function () {
3339 this['allowTemplateRewriting'] = false;
3342 ko.nativeTemplateEngine.prototype = new ko.templateEngine();
3343 ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
3344 var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly
3345 templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null,
3346 templateNodes = templateNodesFunc ? templateSource['nodes']() : null;
3348 if (templateNodes) {
3349 return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes);
3351 var templateText = templateSource['text']();
3352 return ko.utils.parseHtmlFragment(templateText);
3356 ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine();
3357 ko.setTemplateEngine(ko.nativeTemplateEngine.instance);
3359 ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
3361 ko.jqueryTmplTemplateEngine = function () {
3362 // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
3363 // doesn't expose a version number, so we have to infer it.
3364 // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
3365 // which KO internally refers to as version "2", so older versions are no longer detected.
3366 var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
3367 if ((typeof(jQuery) == "undefined") || !(jQuery['tmpl']))
3369 // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
3371 if (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
3372 // Since 1.0.0pre, custom tags should append markup to an array called "__"
3373 return 2; // Final version of jquery.tmpl
3375 } catch(ex) { /* Apparently not the version we were looking for */ }
3377 return 1; // Any older version that we don't support
3380 function ensureHasReferencedJQueryTemplates() {
3381 if (jQueryTmplVersion < 2)
3382 throw new Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");
3385 function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
3386 return jQuery['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
3389 this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
3390 options = options || {};
3391 ensureHasReferencedJQueryTemplates();
3393 // Ensure we have stored a precompiled version of this template (don't want to reparse on every render)
3394 var precompiled = templateSource['data']('precompiled');
3396 var templateText = templateSource['text']() || "";
3397 // Wrap in "with($whatever.koBindingContext) { ... }"
3398 templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
3400 precompiled = jQuery['template'](null, templateText);
3401 templateSource['data']('precompiled', precompiled);
3404 var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
3405 var jQueryTemplateOptions = jQuery['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
3407 var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
3408 resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
3410 jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
3414 this['createJavaScriptEvaluatorBlock'] = function(script) {
3415 return "{{ko_code ((function() { return " + script + " })()) }}";
3418 this['addTemplate'] = function(templateName, templateMarkup) {
3419 document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
3422 if (jQueryTmplVersion > 0) {
3423 jQuery['tmpl']['tag']['ko_code'] = {
3424 open: "__.push($1 || '');"
3426 jQuery['tmpl']['tag']['ko_with'] = {
3433 ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
3435 // Use this one by default *only if jquery.tmpl is referenced*
3436 var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
3437 if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
3438 ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
3440 ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
3443 })(window,document,navigator);