Resources/Web/js/knockout-2.1.0.js
author moel.mich
Sun, 23 Sep 2012 18:37:43 +0000
changeset 380 573f1fff48b2
permissions -rw-r--r--
Fixed Issue 387. The new implementation does not try to start a ring 0 driver that already exists, but could not be opened. It tries to delete the driver and install it new. The driver is now stored temporarily in the application folder. The driver is not correctly removed on system shutdown.
     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)
     4 
     5 (function(window,document,navigator,undefined){
     6 var DEBUG=true;
     7 !function(factory) {
     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
    12         factory(target);
    13     } else if (typeof define === 'function' && define['amd']) {
    14         // [2] AMD anonymous module
    15         define(['exports'], factory);
    16     } else {
    17         // [3] No module loader (plain <script> tag) - put directly in global namespace
    18         factory(window['ko'] = {});
    19     }
    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(".");
    27 
    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)
    30 	var target = ko;
    31 
    32 	for (var i = 0; i < tokens.length - 1; i++)
    33 		target = target[tokens[i]];
    34 	target[tokens[tokens.length - 1]] = object;
    35 };
    36 ko.exportProperty = function(owner, publicName, object) {
    37   owner[publicName] = object;
    38 };
    39 ko.version = "2.1.0";
    40 
    41 ko.exportSymbol('version', ko.version);
    42 ko.utils = new (function () {
    43     var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
    44 
    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;
    55         }
    56     }
    57     var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
    58 
    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');
    62 
    63         // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
    64         while (
    65             div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
    66             iElems[0]
    67         );
    68         return version > 4 ? version : undefined;
    69     }());
    70     var isIe6 = ieVersion === 6,
    71         isIe7 = ieVersion === 7;
    72 
    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");
    78     }
    79 
    80     return {
    81         fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
    82 
    83         arrayForEach: function (array, action) {
    84             for (var i = 0, j = array.length; i < j; i++)
    85                 action(array[i]);
    86         },
    87 
    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)
    93                     return i;
    94             return -1;
    95         },
    96 
    97         arrayFirst: function (array, predicate, predicateOwner) {
    98             for (var i = 0, j = array.length; i < j; i++)
    99                 if (predicate.call(predicateOwner, array[i]))
   100                     return array[i];
   101             return null;
   102         },
   103 
   104         arrayRemoveItem: function (array, itemToRemove) {
   105             var index = ko.utils.arrayIndexOf(array, itemToRemove);
   106             if (index >= 0)
   107                 array.splice(index, 1);
   108         },
   109 
   110         arrayGetDistinctValues: function (array) {
   111             array = array || [];
   112             var result = [];
   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]);
   116             }
   117             return result;
   118         },
   119 
   120         arrayMap: function (array, mapping) {
   121             array = array || [];
   122             var result = [];
   123             for (var i = 0, j = array.length; i < j; i++)
   124                 result.push(mapping(array[i]));
   125             return result;
   126         },
   127 
   128         arrayFilter: function (array, predicate) {
   129             array = array || [];
   130             var result = [];
   131             for (var i = 0, j = array.length; i < j; i++)
   132                 if (predicate(array[i]))
   133                     result.push(array[i]);
   134             return result;
   135         },
   136 
   137         arrayPushAll: function (array, valuesToPush) {
   138             if (valuesToPush instanceof Array)
   139                 array.push.apply(array, valuesToPush);
   140             else
   141                 for (var i = 0, j = valuesToPush.length; i < j; i++)
   142                     array.push(valuesToPush[i]);
   143             return array;
   144         },
   145 
   146         extend: function (target, source) {
   147             if (source) {
   148                 for(var prop in source) {
   149                     if(source.hasOwnProperty(prop)) {
   150                         target[prop] = source[prop];
   151                     }
   152                 }
   153             }
   154             return target;
   155         },
   156 
   157         emptyDomNode: function (domNode) {
   158             while (domNode.firstChild) {
   159                 ko.removeNode(domNode.firstChild);
   160             }
   161         },
   162 
   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);
   167 
   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]);
   172             }
   173             return container;
   174         },
   175 
   176         setDomNodeChildren: function (domNode, childNodes) {
   177             ko.utils.emptyDomNode(domNode);
   178             if (childNodes) {
   179                 for (var i = 0, j = childNodes.length; i < j; i++)
   180                     domNode.appendChild(childNodes[i]);
   181             }
   182         },
   183 
   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]);
   193                 }
   194             }
   195         },
   196 
   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);
   201             else
   202                 optionNode.selected = isSelected;
   203         },
   204 
   205         stringTrim: function (string) {
   206             return (string || "").replace(stringTrimRegex, "");
   207         },
   208 
   209         stringTokenize: function (string, delimiter) {
   210             var result = [];
   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]);
   214                 if (trimmed !== "")
   215                     result.push(trimmed);
   216             }
   217             return result;
   218         },
   219 
   220         stringStartsWith: function (string, startsWith) {
   221             string = string || "";
   222             if (startsWith.length > string.length)
   223                 return false;
   224             return string.substring(0, startsWith.length) === startsWith;
   225         },
   226 
   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 + " } ";
   234             }
   235             return new Function("sc", functionBody);
   236         },
   237 
   238         domNodeIsContainedBy: function (node, containedByNode) {
   239             if (containedByNode.compareDocumentPosition)
   240                 return (containedByNode.compareDocumentPosition(node) & 16) == 16;
   241             while (node != null) {
   242                 if (node == containedByNode)
   243                     return true;
   244                 node = node.parentNode;
   245             }
   246             return false;
   247         },
   248 
   249         domNodeIsAttachedToDocument: function (node) {
   250             return ko.utils.domNodeIsContainedBy(node, node.ownerDocument);
   251         },
   252 
   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();
   258         },
   259 
   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;
   271                         if (eventData)
   272                             this.checked = eventData.checkedStateBeforeEvent !== true;
   273                         originalHandler.call(this, event);
   274                         this.checked = jQuerySuppliedCheckedState; // Restore the state jQuery applied
   275                     };
   276                 }
   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);
   283                 });
   284             else
   285                 throw new Error("Browser doesn't support addEventListener or attachEvent");
   286         },
   287 
   288         triggerEvent: function (element, eventType) {
   289             if (!(element && element.nodeType))
   290                 throw new Error("element must be a DOM node when calling triggerEvent");
   291 
   292             if (typeof jQuery != "undefined") {
   293                 var eventData = [];
   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 });
   297                 }
   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);
   305                 }
   306                 else
   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);
   314             }
   315             else
   316                 throw new Error("Browser doesn't support triggering events");
   317         },
   318 
   319         unwrapObservable: function (value) {
   320             return ko.isObservable(value) ? value() : value;
   321         },
   322 
   323         toggleDomNodeCssClass: function (node, className, shouldHaveClass) {
   324             var currentClassNames = (node.className || "").split(/\s+/);
   325             var hasClass = ko.utils.arrayIndexOf(currentClassNames, className) >= 0;
   326 
   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);
   335             }
   336         },
   337 
   338         setTextContent: function(element, textContent) {
   339             var value = ko.utils.unwrapObservable(textContent);
   340             if ((value === null) || (value === undefined))
   341                 value = "";
   342 
   343             'innerText' in element ? element.innerText = value
   344                                    : element.textContent = value;
   345 
   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;
   350             }
   351         },
   352 
   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;
   360             }
   361         },
   362 
   363         range: function (min, max) {
   364             min = ko.utils.unwrapObservable(min);
   365             max = ko.utils.unwrapObservable(max);
   366             var result = [];
   367             for (var i = min; i <= max; i++)
   368                 result.push(i);
   369             return result;
   370         },
   371 
   372         makeArray: function(arrayLikeObject) {
   373             var result = [];
   374             for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
   375                 result.push(arrayLikeObject[i]);
   376             };
   377             return result;
   378         },
   379 
   380         isIe6 : isIe6,
   381         isIe7 : isIe7,
   382         ieVersion : ieVersion,
   383 
   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
   389             var matches = [];
   390             for (var i = fields.length - 1; i >= 0; i--) {
   391                 if (isMatchingField(fields[i]))
   392                     matches.push(fields[i]);
   393             };
   394             return matches;
   395         },
   396 
   397         parseJson: function (jsonString) {
   398             if (typeof jsonString == "string") {
   399                 jsonString = ko.utils.stringTrim(jsonString);
   400                 if (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
   404                 }
   405             }
   406             return null;
   407         },
   408 
   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);
   413         },
   414 
   415         postJson: function (urlOrForm, data, options) {
   416             options = options || {};
   417             var params = options['params'] || {};
   418             var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
   419             var url = urlOrForm;
   420 
   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;
   429                 }
   430             }
   431 
   432             data = ko.utils.unwrapObservable(data);
   433             var form = document.createElement("form");
   434             form.style.display = "none";
   435             form.action = url;
   436             form.method = "post";
   437             for (var key in data) {
   438                 var input = document.createElement("input");
   439                 input.name = key;
   440                 input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
   441                 form.appendChild(input);
   442             }
   443             for (var key in params) {
   444                 var input = document.createElement("input");
   445                 input.name = key;
   446                 input.value = params[key];
   447                 form.appendChild(input);
   448             }
   449             document.body.appendChild(form);
   450             options['submitter'] ? options['submitter'](form) : form.submit();
   451             setTimeout(function () { form.parentNode.removeChild(form); }, 0);
   452         }
   453     }
   454 })();
   455 
   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);
   476 
   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();
   482         return function () {
   483             return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
   484         };
   485     };
   486 }
   487 
   488 ko.utils.domData = new (function () {
   489     var uniqueId = 0;
   490     var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
   491     var dataStore = {};
   492     return {
   493         get: function (node, key) {
   494             var allDataForNode = ko.utils.domData.getAll(node, false);
   495             return allDataForNode === undefined ? undefined : allDataForNode[key];
   496         },
   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)
   501                     return;
   502             }
   503             var allDataForNode = ko.utils.domData.getAll(node, true);
   504             allDataForNode[key] = value;
   505         },
   506         getAll: function (node, createIfNotFound) {
   507             var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
   508             var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null");
   509             if (!hasExistingDataStore) {
   510                 if (!createIfNotFound)
   511                     return undefined;
   512                 dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
   513                 dataStore[dataStoreKey] = {};
   514             }
   515             return dataStore[dataStoreKey];
   516         },
   517         clear: function (node) {
   518             var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
   519             if (dataStoreKey) {
   520                 delete dataStore[dataStoreKey];
   521                 node[dataStoreKeyExpandoPropertyName] = null;
   522             }
   523         }
   524     }
   525 })();
   526 
   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
   529 
   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
   534 
   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);
   540         }
   541         return allDisposeCallbacks;
   542     }
   543     function destroyCallbacksCollection(node) {
   544         ko.utils.domData.set(node, domDataKey, undefined);
   545     }
   546 
   547     function cleanSingleNode(node) {
   548         // Run all the dispose callbacks
   549         var callbacks = getDisposeCallbacksCollection(node, false);
   550         if (callbacks) {
   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++)
   553                 callbacks[i](node);
   554         }
   555 
   556         // Also erase the DOM data
   557         ko.utils.domData.clear(node);
   558 
   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]);
   564 
   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);
   569     }
   570 
   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);
   577         }
   578     }
   579 
   580     return {
   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);
   585         },
   586 
   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);
   593             }
   594         },
   595 
   596         cleanNode : function(node) {
   597             // First clean this node, where applicable
   598             if (cleanableNodeTypes[node.nodeType]) {
   599                 cleanSingleNode(node);
   600 
   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]);
   608                 }
   609             }
   610         },
   611 
   612         removeNode : function(node) {
   613             ko.cleanNode(node);
   614             if (node.parentNode)
   615                 node.parentNode.removeChild(node);
   616         }
   617     }
   618 })();
   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);
   626 (function () {
   627     var leadingCommentRegex = /^(\s*)<!--(.*?)-->/;
   628 
   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
   632 
   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.
   637 
   638         // Trim whitespace, otherwise indexOf won't work as expected
   639         var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div");
   640 
   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, "", ""];
   646 
   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));
   652         } else {
   653             div.innerHTML = markup;
   654         }
   655 
   656         // Move to the right depth
   657         while (wrap[0]--)
   658             div = div.lastChild;
   659 
   660         return ko.utils.makeArray(div.lastChild.childNodes);
   661     }
   662 
   663     function jQueryHtmlParse(html) {
   664         var elems = jQuery['clean']([html]);
   665 
   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
   671             var elem = elems[0];
   672             while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)
   673                 elem = elem.parentNode;
   674             // ... then detach it
   675             if (elem.parentNode)
   676                 elem.parentNode.removeChild(elem);
   677         }
   678 
   679         return elems;
   680     }
   681 
   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.
   685     };
   686 
   687     ko.utils.setHtml = function(node, html) {
   688         ko.utils.emptyDomNode(node);
   689 
   690         if ((html !== null) && (html !== undefined)) {
   691             if (typeof html != 'string')
   692                 html = html.toString();
   693 
   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);
   699             } else {
   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]);
   704             }
   705         }
   706     };
   707 })();
   708 
   709 ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
   710 ko.exportSymbol('utils.setHtml', ko.utils.setHtml);
   711 
   712 ko.memoization = (function () {
   713     var memos = {};
   714 
   715     function randomMax8HexChars() {
   716         return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
   717     }
   718     function generateRandomId() {
   719         return randomMax8HexChars() + randomMax8HexChars();
   720     }
   721     function findMemoNodes(rootNode, appendToArray) {
   722         if (!rootNode)
   723             return;
   724         if (rootNode.nodeType == 8) {
   725             var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
   726             if (memoId != null)
   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);
   731         }
   732     }
   733 
   734     return {
   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 + "]-->";
   741         },
   742 
   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.");
   747             try {
   748                 callback.apply(null, callbackParams || []);
   749                 return true;
   750             }
   751             finally { delete memos[memoId]; }
   752         },
   753 
   754         unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {
   755             var memos = [];
   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
   764                 if (node.parentNode)
   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)
   766             }
   767         },
   768 
   769         parseMemoText: function (memoText) {
   770             var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
   771             return match ? match[1] : null;
   772         }
   773     };
   774 })();
   775 
   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);
   781 ko.extenders = {
   782     'throttle': function(target, timeout) {
   783         // Throttling means two things:
   784 
   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;
   788 
   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({
   793             'read': target,
   794             'write': function(value) {
   795                 clearTimeout(writeTimeoutInstance);
   796                 writeTimeoutInstance = setTimeout(function() {
   797                     target(value);
   798                 }, timeout);
   799             }
   800         });
   801     },
   802 
   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"];
   807         return target;
   808     }
   809 };
   810 
   811 function applyExtenders(requestedExtenders) {
   812     var target = this;
   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]);
   818             }
   819         }
   820     }
   821     return target;
   822 }
   823 
   824 ko.exportSymbol('extenders', ko.extenders);
   825 
   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);
   831 };
   832 ko.subscription.prototype.dispose = function () {
   833     this.isDisposed = true;
   834     this.disposeCallback();
   835 };
   836 
   837 ko.subscribable = function () {
   838     this._subscriptions = {};
   839 
   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);
   844 }
   845 
   846 var defaultEvent = "change";
   847 
   848 ko.subscribable['fn'] = {
   849     subscribe: function (callback, callbackTarget, event) {
   850         event = event || defaultEvent;
   851         var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
   852 
   853         var subscription = new ko.subscription(this, boundCallback, function () {
   854             ko.utils.arrayRemoveItem(this._subscriptions[event], subscription);
   855         }.bind(this));
   856 
   857         if (!this._subscriptions[event])
   858             this._subscriptions[event] = [];
   859         this._subscriptions[event].push(subscription);
   860         return subscription;
   861     },
   862 
   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);
   871             });
   872         }
   873     },
   874 
   875     getSubscriptionsCount: function () {
   876         var total = 0;
   877         for (var eventName in this._subscriptions) {
   878             if (this._subscriptions.hasOwnProperty(eventName))
   879                 total += this._subscriptions[eventName].length;
   880         }
   881         return total;
   882     },
   883 
   884     extend: applyExtenders
   885 };
   886 
   887 
   888 ko.isSubscribable = function (instance) {
   889     return typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
   890 };
   891 
   892 ko.exportSymbol('subscribable', ko.subscribable);
   893 ko.exportSymbol('isSubscribable', ko.isSubscribable);
   894 
   895 ko.dependencyDetection = (function () {
   896     var _frames = [];
   897 
   898     return {
   899         begin: function (callback) {
   900             _frames.push({ callback: callback, distinctDependencies:[] });
   901         },
   902 
   903         end: function () {
   904             _frames.pop();
   905         },
   906 
   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)
   913                     return;
   914                 topFrame.distinctDependencies.push(subscribable);
   915                 topFrame.callback(subscribable);
   916             }
   917         }
   918     };
   919 })();
   920 var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true };
   921 
   922 ko.observable = function (initialValue) {
   923     var _latestValue = initialValue;
   924 
   925     function observable() {
   926         if (arguments.length > 0) {
   927             // Write
   928 
   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();
   935             }
   936             return this; // Permits chained assignments
   937         }
   938         else {
   939             // Read
   940             ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
   941             return _latestValue;
   942         }
   943     }
   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']);
   949 
   950     ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
   951     ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
   952 
   953     return observable;
   954 }
   955 
   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;
   960     }
   961 };
   962 
   963 var protoProperty = ko.observable.protoProperty = "__ko_proto__";
   964 ko.observable['fn'][protoProperty] = ko.observable;
   965 
   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
   970 };
   971 
   972 ko.isObservable = function (instance) {
   973     return ko.hasPrototype(instance, ko.observable);
   974 }
   975 ko.isWriteableObservable = function (instance) {
   976     // Observable
   977     if ((typeof instance == "function") && instance[protoProperty] === ko.observable)
   978         return true;
   979     // Writeable dependent observable
   980     if ((typeof instance == "function") && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
   981         return true;
   982     // Anything else
   983     return false;
   984 }
   985 
   986 
   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
   993         initialValues = [];
   994     }
   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.");
   997 
   998     var result = ko.observable(initialValues);
   999     ko.utils.extend(result, ko.observableArray['fn']);
  1000     return result;
  1001 }
  1002 
  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();
  1013                 }
  1014                 removedValues.push(value);
  1015                 underlyingArray.splice(i, 1);
  1016                 i--;
  1017             }
  1018         }
  1019         if (removedValues.length) {
  1020             this.valueHasMutated();
  1021         }
  1022         return removedValues;
  1023     },
  1024 
  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();
  1033             return allValues;
  1034         }
  1035         // If you passed an arg, we interpret it as an array of entries to remove
  1036         if (!arrayOfValues)
  1037             return [];
  1038         return this['remove'](function (value) {
  1039             return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
  1040         });
  1041     },
  1042 
  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;
  1051         }
  1052         this.valueHasMutated();
  1053     },
  1054 
  1055     'destroyAll': function (arrayOfValues) {
  1056         // If you passed zero args, we destroy everything
  1057         if (arrayOfValues === undefined)
  1058             return this['destroy'](function() { return true });
  1059 
  1060         // If you passed an arg, we interpret it as an array of entries to destroy
  1061         if (!arrayOfValues)
  1062             return [];
  1063         return this['destroy'](function (value) {
  1064             return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
  1065         });
  1066     },
  1067 
  1068     'indexOf': function (item) {
  1069         var underlyingArray = this();
  1070         return ko.utils.arrayIndexOf(underlyingArray, item);
  1071     },
  1072 
  1073     'replace': function(oldItem, newItem) {
  1074         var index = this['indexOf'](oldItem);
  1075         if (index >= 0) {
  1076             this.valueWillMutate();
  1077             this()[index] = newItem;
  1078             this.valueHasMutated();
  1079         }
  1080     }
  1081 }
  1082 
  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;
  1091     };
  1092 });
  1093 
  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);
  1099     };
  1100 });
  1101 
  1102 ko.exportSymbol('observableArray', ko.observableArray);
  1103 ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
  1104     var _latestValue,
  1105         _hasBeenEvaluated = false,
  1106         _isBeingEvaluated = false,
  1107         readFunction = evaluatorFunctionOrOptions;
  1108 
  1109     if (readFunction && typeof readFunction == "object") {
  1110         // Single-parameter syntax - everything is on this "options" param
  1111         options = readFunction;
  1112         readFunction = options["read"];
  1113     } else {
  1114         // Multi-parameter syntax - construct the options according to the params passed
  1115         options = options || {};
  1116         if (!readFunction)
  1117             readFunction = options["read"];
  1118     }
  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");
  1122 
  1123     var writeFunction = options["write"];
  1124     if (!evaluatorFunctionTarget)
  1125         evaluatorFunctionTarget = options["owner"];
  1126 
  1127     var _subscriptionsToDependencies = [];
  1128     function disposeAllSubscriptionsToDependencies() {
  1129         ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) {
  1130             subscription.dispose();
  1131         });
  1132         _subscriptionsToDependencies = [];
  1133     }
  1134     var dispose = disposeAllSubscriptionsToDependencies;
  1135 
  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();
  1145         };
  1146         ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
  1147         var existingDisposeWhenFunction = disposeWhen;
  1148         disposeWhen = function () {
  1149             return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || existingDisposeWhenFunction();
  1150         }
  1151     }
  1152 
  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);
  1159         } else
  1160             evaluateImmediate();
  1161     }
  1162 
  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
  1169             return;
  1170         }
  1171 
  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()) {
  1176             dispose();
  1177             return;
  1178         }
  1179 
  1180         _isBeingEvaluated = true;
  1181         try {
  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;});
  1185 
  1186             ko.dependencyDetection.begin(function(subscribable) {
  1187                 var inOld;
  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
  1190                 else
  1191                     _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync)); // Brand new subscription - add it
  1192             });
  1193 
  1194             var newValue = readFunction.call(evaluatorFunctionTarget);
  1195 
  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();
  1200             }
  1201             _hasBeenEvaluated = true;
  1202 
  1203             dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
  1204             _latestValue = newValue;
  1205             if (DEBUG) dependentObservable._latestValue = _latestValue;
  1206         } finally {
  1207             ko.dependencyDetection.end();
  1208         }
  1209 
  1210         dependentObservable["notifySubscribers"](_latestValue);
  1211         _isBeingEvaluated = false;
  1212 
  1213     }
  1214 
  1215     function dependentObservable() {
  1216         if (arguments.length > 0) {
  1217             set.apply(dependentObservable, arguments);
  1218         } else {
  1219             return get();
  1220         }
  1221     }
  1222 
  1223     function set() {
  1224         if (typeof writeFunction === "function") {
  1225             // Writing a value
  1226             writeFunction.apply(evaluatorFunctionTarget, arguments);
  1227         } else {
  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.");
  1229         }
  1230     }
  1231 
  1232     function get() {
  1233         // Reading the value
  1234         if (!_hasBeenEvaluated)
  1235             evaluateImmediate();
  1236         ko.dependencyDetection.registerDependency(dependentObservable);
  1237         return _latestValue;
  1238     }
  1239 
  1240     dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; };
  1241     dependentObservable.hasWriteFunction = typeof options["write"] === "function";
  1242     dependentObservable.dispose = function () { dispose(); };
  1243 
  1244     ko.subscribable.call(dependentObservable);
  1245     ko.utils.extend(dependentObservable, ko.dependentObservable['fn']);
  1246 
  1247     if (options['deferEvaluation'] !== true)
  1248         evaluateImmediate();
  1249 
  1250     ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
  1251     ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount);
  1252 
  1253     return dependentObservable;
  1254 };
  1255 
  1256 ko.isComputed = function(instance) {
  1257     return ko.hasPrototype(instance, ko.dependentObservable);
  1258 };
  1259 
  1260 var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
  1261 ko.dependentObservable[protoProp] = ko.observable;
  1262 
  1263 ko.dependentObservable['fn'] = {};
  1264 ko.dependentObservable['fn'][protoProp] = ko.dependentObservable;
  1265 
  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);
  1269 
  1270 (function() {
  1271     var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
  1272 
  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.");
  1276 
  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();
  1282             return valueToMap;
  1283         });
  1284     };
  1285 
  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);
  1289     };
  1290 
  1291     function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
  1292         visitedObjects = visitedObjects || new objectLookup();
  1293 
  1294         rootObject = mapInputCallback(rootObject);
  1295         var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date));
  1296         if (!canHaveProperties)
  1297             return rootObject;
  1298 
  1299         var outputProperties = rootObject instanceof Array ? [] : {};
  1300         visitedObjects.save(rootObject, outputProperties);
  1301 
  1302         visitPropertiesOrArrayEntries(rootObject, function(indexer) {
  1303             var propertyValue = mapInputCallback(rootObject[indexer]);
  1304 
  1305             switch (typeof propertyValue) {
  1306                 case "boolean":
  1307                 case "number":
  1308                 case "string":
  1309                 case "function":
  1310                     outputProperties[indexer] = propertyValue;
  1311                     break;
  1312                 case "object":
  1313                 case "undefined":
  1314                     var previouslyMappedValue = visitedObjects.get(propertyValue);
  1315                     outputProperties[indexer] = (previouslyMappedValue !== undefined)
  1316                         ? previouslyMappedValue
  1317                         : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects);
  1318                     break;
  1319             }
  1320         });
  1321 
  1322         return outputProperties;
  1323     }
  1324 
  1325     function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
  1326         if (rootObject instanceof Array) {
  1327             for (var i = 0; i < rootObject.length; i++)
  1328                 visitorCallback(i);
  1329 
  1330             // For arrays, also respect toJSON property for custom mappings (fixes #278)
  1331             if (typeof rootObject['toJSON'] == 'function')
  1332                 visitorCallback('toJSON');
  1333         } else {
  1334             for (var propertyName in rootObject)
  1335                 visitorCallback(propertyName);
  1336         }
  1337     };
  1338 
  1339     function objectLookup() {
  1340         var keys = [];
  1341         var values = [];
  1342         this.save = function(key, value) {
  1343             var existingIndex = ko.utils.arrayIndexOf(keys, key);
  1344             if (existingIndex >= 0)
  1345                 values[existingIndex] = value;
  1346             else {
  1347                 keys.push(key);
  1348                 values.push(value);
  1349             }
  1350         };
  1351         this.get = function(key) {
  1352             var existingIndex = ko.utils.arrayIndexOf(keys, key);
  1353             return (existingIndex >= 0) ? values[existingIndex] : undefined;
  1354         };
  1355     };
  1356 })();
  1357 
  1358 ko.exportSymbol('toJS', ko.toJS);
  1359 ko.exportSymbol('toJSON', ko.toJSON);
  1360 (function () {
  1361     var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
  1362 
  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)) {
  1369                 case 'option':
  1370                     if (element[hasDomDataExpandoProperty] === true)
  1371                         return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
  1372                     return element.getAttribute("value");
  1373                 case 'select':
  1374                     return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
  1375                 default:
  1376                     return element.value;
  1377             }
  1378         },
  1379 
  1380         writeValue: function(element, value) {
  1381             switch (ko.utils.tagNameLower(element)) {
  1382                 case 'option':
  1383                     switch(typeof value) {
  1384                         case "string":
  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];
  1388                             }
  1389                             element.value = value;
  1390                             break;
  1391                         default:
  1392                             // Store arbitrary object using DomData
  1393                             ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
  1394                             element[hasDomDataExpandoProperty] = true;
  1395 
  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 : "";
  1398                             break;
  1399                     }
  1400                     break;
  1401                 case 'select':
  1402                     for (var i = element.options.length - 1; i >= 0; i--) {
  1403                         if (ko.selectExtensions.readValue(element.options[i]) == value) {
  1404                             element.selectedIndex = i;
  1405                             break;
  1406                         }
  1407                     }
  1408                     break;
  1409                 default:
  1410                     if ((value === null) || (value === undefined))
  1411                         value = "";
  1412                     element.value = value;
  1413                     break;
  1414             }
  1415         }
  1416     };
  1417 })();
  1418 
  1419 ko.exportSymbol('selectExtensions', ko.selectExtensions);
  1420 ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
  1421 ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
  1422 
  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"];
  1427 
  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)
  1431             prevValue = string;
  1432             string = string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) {
  1433                 return tokens[tokenIndex];
  1434             });
  1435         }
  1436         return string;
  1437     }
  1438 
  1439     function isWriteableValue(expression) {
  1440         if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0)
  1441             return false;
  1442         return expression.match(javaScriptAssignmentTarget) !== null;
  1443     }
  1444 
  1445     function ensureQuoted(key) {
  1446         var trimmedKey = ko.utils.stringTrim(key);
  1447         switch (trimmedKey.length && trimmedKey.charAt(0)) {
  1448             case "'":
  1449             case '"':
  1450                 return key;
  1451             default:
  1452                 return "'" + trimmedKey + "'";
  1453         }
  1454     }
  1455 
  1456     return {
  1457         bindingRewriteValidators: [],
  1458 
  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
  1462 
  1463             var str = ko.utils.stringTrim(objectLiteralString);
  1464             if (str.length < 3)
  1465                 return [];
  1466             if (str.charAt(0) === "{")// Ignore any braces surrounding the whole object literal
  1467                 str = str.substring(1, str.length - 1);
  1468 
  1469             // Pull out any string literals and regex literals
  1470             var tokens = [];
  1471             var tokenStart = null, tokenEndChar;
  1472             for (var position = 0; position < str.length; position++) {
  1473                 var c = str.charAt(position);
  1474                 if (tokenStart === null) {
  1475                     switch (c) {
  1476                         case '"':
  1477                         case "'":
  1478                         case "/":
  1479                             tokenStart = position;
  1480                             tokenEndChar = c;
  1481                             break;
  1482                     }
  1483                 } else if ((c == tokenEndChar) && (str.charAt(position - 1) !== "\\")) {
  1484                     var token = str.substring(tokenStart, position + 1);
  1485                     tokens.push(token);
  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);
  1489                     tokenStart = null;
  1490                 }
  1491             }
  1492 
  1493             // Next pull out balanced paren, brace, and bracket blocks
  1494             tokenStart = null;
  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) {
  1500                     switch (c) {
  1501                         case "{": tokenStart = position; tokenStartChar = c;
  1502                                   tokenEndChar = "}";
  1503                                   break;
  1504                         case "(": tokenStart = position; tokenStartChar = c;
  1505                                   tokenEndChar = ")";
  1506                                   break;
  1507                         case "[": tokenStart = position; tokenStartChar = c;
  1508                                   tokenEndChar = "]";
  1509                                   break;
  1510                     }
  1511                 }
  1512 
  1513                 if (c === tokenStartChar)
  1514                     tokenDepth++;
  1515                 else if (c === tokenEndChar) {
  1516                     tokenDepth--;
  1517                     if (tokenDepth === 0) {
  1518                         var token = str.substring(tokenStart, position + 1);
  1519                         tokens.push(token);
  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);
  1523                         tokenStart = null;
  1524                     }
  1525                 }
  1526             }
  1527 
  1528             // Now we can safely split on commas to get the key/value pairs
  1529             var result = [];
  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) });
  1538                 } else {
  1539                     result.push({ 'unknown': restoreTokens(pair, tokens) });
  1540                 }
  1541             }
  1542             return result;
  1543         },
  1544 
  1545         insertPropertyAccessorsIntoJson: function (objectLiteralStringOrKeyValueArray) {
  1546             var keyValueArray = typeof objectLiteralStringOrKeyValueArray === "string"
  1547                 ? ko.jsonExpressionRewriting.parseObjectLiteral(objectLiteralStringOrKeyValueArray)
  1548                 : objectLiteralStringOrKeyValueArray;
  1549             var resultStrings = [], propertyAccessorResultStrings = [];
  1550 
  1551             var keyValueEntry;
  1552             for (var i = 0; keyValueEntry = keyValueArray[i]; i++) {
  1553                 if (resultStrings.length > 0)
  1554                     resultStrings.push(",");
  1555 
  1556                 if (keyValueEntry['key']) {
  1557                     var quotedKey = ensureQuoted(keyValueEntry['key']), val = keyValueEntry['value'];
  1558                     resultStrings.push(quotedKey);
  1559                     resultStrings.push(":");
  1560                     resultStrings.push(val);
  1561 
  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; }");
  1566                     }
  1567                 } else if (keyValueEntry['unknown']) {
  1568                     resultStrings.push(keyValueEntry['unknown']);
  1569                 }
  1570             }
  1571 
  1572             var combinedResult = resultStrings.join("");
  1573             if (propertyAccessorResultStrings.length > 0) {
  1574                 var allPropertyAccessors = propertyAccessorResultStrings.join("");
  1575                 combinedResult = combinedResult + ", '_ko_property_writers' : { " + allPropertyAccessors + " } ";
  1576             }
  1577 
  1578             return combinedResult;
  1579         },
  1580 
  1581         keyValueArrayContainsKey: function(keyValueArray, key) {
  1582             for (var i = 0; i < keyValueArray.length; i++)
  1583                 if (ko.utils.stringTrim(keyValueArray[i]['key']) == key)
  1584                     return true;
  1585             return false;
  1586         },
  1587 
  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) {
  1603                 property(value);
  1604             }
  1605         }
  1606     };
  1607 })();
  1608 
  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);
  1613 (function() {
  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
  1618     //
  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.
  1621 
  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-->";
  1626 
  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 };
  1630 
  1631     function isStartComment(node) {
  1632         return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
  1633     }
  1634 
  1635     function isEndComment(node) {
  1636         return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(endCommentRegex);
  1637     }
  1638 
  1639     function getVirtualChildren(startComment, allowUnbalanced) {
  1640         var currentNode = startComment;
  1641         var depth = 1;
  1642         var children = [];
  1643         while (currentNode = currentNode.nextSibling) {
  1644             if (isEndComment(currentNode)) {
  1645                 depth--;
  1646                 if (depth === 0)
  1647                     return children;
  1648             }
  1649 
  1650             children.push(currentNode);
  1651 
  1652             if (isStartComment(currentNode))
  1653                 depth++;
  1654         }
  1655         if (!allowUnbalanced)
  1656             throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
  1657         return null;
  1658     }
  1659 
  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;
  1666         } else
  1667             return null; // Must have no matching end comment, and allowUnbalanced is true
  1668     }
  1669 
  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;
  1674         if (childNode) {
  1675             do {
  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;
  1682                     else
  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
  1686                 }
  1687             } while (childNode = childNode.nextSibling);
  1688         }
  1689         return captureRemaining;
  1690     }
  1691 
  1692     ko.virtualElements = {
  1693         allowedBindings: {},
  1694 
  1695         childNodes: function(node) {
  1696             return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
  1697         },
  1698 
  1699         emptyNode: function(node) {
  1700             if (!isStartComment(node))
  1701                 ko.utils.emptyDomNode(node);
  1702             else {
  1703                 var virtualChildren = ko.virtualElements.childNodes(node);
  1704                 for (var i = 0, j = virtualChildren.length; i < j; i++)
  1705                     ko.removeNode(virtualChildren[i]);
  1706             }
  1707         },
  1708 
  1709         setDomNodeChildren: function(node, childNodes) {
  1710             if (!isStartComment(node))
  1711                 ko.utils.setDomNodeChildren(node, childNodes);
  1712             else {
  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);
  1717             }
  1718         },
  1719 
  1720         prepend: function(containerNode, nodeToPrepend) {
  1721             if (!isStartComment(containerNode)) {
  1722                 if (containerNode.firstChild)
  1723                     containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
  1724                 else
  1725                     containerNode.appendChild(nodeToPrepend);
  1726             } else {
  1727                 // Start comments must always have a parent and at least one following sibling (the end comment)
  1728                 containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
  1729             }
  1730         },
  1731 
  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);
  1737                 else
  1738                     containerNode.appendChild(nodeToInsert);
  1739             } else {
  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);
  1742             }
  1743         },
  1744 
  1745         firstChild: function(node) {
  1746             if (!isStartComment(node))
  1747                 return node.firstChild;
  1748             if (!node.nextSibling || isEndComment(node.nextSibling))
  1749                 return null;
  1750             return node.nextSibling;
  1751         },
  1752 
  1753         nextSibling: function(node) {
  1754             if (isStartComment(node))
  1755                 node = getMatchingEndComment(node);
  1756             if (node.nextSibling && isEndComment(node.nextSibling))
  1757                 return null;
  1758             return node.nextSibling;
  1759         },
  1760 
  1761         virtualNodeBindingValue: function(node) {
  1762             var regexMatch = isStartComment(node);
  1763             return regexMatch ? regexMatch[1] : null;
  1764         },
  1765 
  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)])
  1771                 return;
  1772 
  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;
  1776             if (childNode) {
  1777                 do {
  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);
  1786                                 else
  1787                                     elementVerified.appendChild(unbalancedTags[i]);
  1788                             }
  1789                         }
  1790                     }
  1791                 } while (childNode = childNode.nextSibling);
  1792             }
  1793         }
  1794     };
  1795 })();
  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);
  1804 (function() {
  1805     var defaultBindingAttributeName = "data-bind";
  1806 
  1807     ko.bindingProvider = function() {
  1808         this.bindingCache = {};
  1809     };
  1810 
  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;
  1817             }
  1818         },
  1819 
  1820         'getBindings': function(node, bindingContext) {
  1821             var bindingsString = this['getBindingsString'](node, bindingContext);
  1822             return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext) : null;
  1823         },
  1824 
  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;
  1832             }
  1833         },
  1834 
  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) {
  1838             try {
  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);
  1843             } catch (ex) {
  1844                 throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
  1845             }
  1846         }
  1847     });
  1848 
  1849     ko.bindingProvider['instance'] = new ko.bindingProvider();
  1850 
  1851     function createBindingsStringEvaluatorViaCache(bindingsString, scopesCount, cache) {
  1852         var cacheKey = scopesCount + '_' + bindingsString;
  1853         return cache[cacheKey]
  1854             || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, scopesCount));
  1855     }
  1856 
  1857     function createBindingsStringEvaluator(bindingsString, scopesCount) {
  1858         var rewrittenBindings = " { " + ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(bindingsString) + " } ";
  1859         return ko.utils.buildEvalWithinScopeFunction(rewrittenBindings, scopesCount);
  1860     }
  1861 })();
  1862 
  1863 ko.exportSymbol('bindingProvider', ko.bindingProvider);
  1864 (function () {
  1865     ko.bindingHandlers = {};
  1866 
  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']);
  1874         } else {
  1875             this['$parents'] = [];
  1876             this['$root'] = dataItem;
  1877         }
  1878         this['$data'] = dataItem;
  1879     }
  1880     ko.bindingContext.prototype['createChildContext'] = function (dataItem) {
  1881         return new ko.bindingContext(dataItem, this);
  1882     };
  1883     ko.bindingContext.prototype['extend'] = function(properties) {
  1884         var clone = ko.utils.extend(new ko.bindingContext(), this);
  1885         return ko.utils.extend(clone, properties);
  1886     };
  1887 
  1888     function validateThatBindingIsAllowedForVirtualElements(bindingName) {
  1889         var validator = ko.virtualElements.allowedBindings[bindingName];
  1890         if (!validator)
  1891             throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
  1892     }
  1893 
  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);
  1900         }
  1901     }
  1902 
  1903     function applyBindingsToNodeAndDescendantsInternal (viewModel, nodeVerified, bindingContextMayDifferFromDomParentElement) {
  1904         var shouldBindDescendants = true;
  1905 
  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);
  1913 
  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;
  1918 
  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);
  1927         }
  1928     }
  1929 
  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
  1933 
  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.
  1940         var parsedBindings;
  1941         function makeValueAccessor(bindingKey) {
  1942             return function () { return parsedBindings[bindingKey] }
  1943         }
  1944         function parsedBindingsAccessor() {
  1945             return parsedBindings;
  1946         }
  1947 
  1948         var bindingHandlerThatControlsDescendantBindings;
  1949         ko.dependentObservable(
  1950             function () {
  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'];
  1956 
  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);
  1962 
  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);
  1966 
  1967                 if (parsedBindings) {
  1968                     // First run all the inits, so bindings can register for notification on changes
  1969                     if (initPhase === 0) {
  1970                         initPhase = 1;
  1971                         for (var bindingKey in parsedBindings) {
  1972                             var binding = ko.bindingHandlers[bindingKey];
  1973                             if (binding && node.nodeType === 8)
  1974                                 validateThatBindingIsAllowedForVirtualElements(bindingKey);
  1975 
  1976                             if (binding && typeof binding["init"] == "function") {
  1977                                 var handlerInitFn = binding["init"];
  1978                                 var initResult = handlerInitFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
  1979 
  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;
  1985                                 }
  1986                             }
  1987                         }
  1988                         initPhase = 2;
  1989                     }
  1990 
  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);
  1998                             }
  1999                         }
  2000                     }
  2001                 }
  2002             },
  2003             null,
  2004             { 'disposeWhenNodeIsRemoved' : node }
  2005         );
  2006 
  2007         return {
  2008             shouldBindDescendants: bindingHandlerThatControlsDescendantBindings === undefined
  2009         };
  2010     };
  2011 
  2012     var storedBindingContextDomDataKey = "__ko_bindingContext__";
  2013     ko.storedBindingContextForNode = function (node, bindingContext) {
  2014         if (arguments.length == 2)
  2015             ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
  2016         else
  2017             return ko.utils.domData.get(node, storedBindingContextDomDataKey);
  2018     }
  2019 
  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);
  2024     };
  2025 
  2026     ko.applyBindingsToDescendants = function(viewModel, rootNode) {
  2027         if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
  2028             applyBindingsToDescendantsInternal(viewModel, rootNode, true);
  2029     };
  2030 
  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
  2035 
  2036         applyBindingsToNodeAndDescendantsInternal(viewModel, rootNode, true);
  2037     };
  2038 
  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) {
  2043             case 1:
  2044             case 8:
  2045                 var context = ko.storedBindingContextForNode(node);
  2046                 if (context) return context;
  2047                 if (node.parentNode) return ko.contextFor(node.parentNode);
  2048                 break;
  2049         }
  2050         return undefined;
  2051     };
  2052     ko.dataFor = function(node) {
  2053         var context = ko.contextFor(node);
  2054         return context ? context['$data'] : undefined;
  2055     };
  2056 
  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);
  2063 })();
  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 () {
  2071                 var result = {};
  2072                 result[eventName] = valueAccessor();
  2073                 return result;
  2074             };
  2075             return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindingsAccessor, viewModel);
  2076         }
  2077     }
  2078 });
  2079 
  2080 
  2081 ko.bindingHandlers['event'] = {
  2082     'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) {
  2083         var eventsToHandle = valueAccessor() || {};
  2084         for(var eventNameOutsideClosure in eventsToHandle) {
  2085             (function() {
  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)
  2092                             return;
  2093                         var allBindings = allBindingsAccessor();
  2094 
  2095                         try {
  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);
  2100                         } finally {
  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();
  2104                                 else
  2105                                     event.returnValue = false;
  2106                             }
  2107                         }
  2108 
  2109                         var bubble = allBindings[eventName + 'Bubble'] !== false;
  2110                         if (!bubble) {
  2111                             event.cancelBubble = true;
  2112                             if (event.stopPropagation)
  2113                                 event.stopPropagation();
  2114                         }
  2115                     });
  2116                 }
  2117             })();
  2118         }
  2119     }
  2120 };
  2121 
  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); }
  2130             finally {
  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();
  2134                     else
  2135                         event.returnValue = false;
  2136                 }
  2137             }
  2138         });
  2139     }
  2140 };
  2141 
  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";
  2150     }
  2151 }
  2152 
  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;
  2160     }
  2161 };
  2162 
  2163 ko.bindingHandlers['disable'] = {
  2164     'update': function (element, valueAccessor) {
  2165         ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
  2166     }
  2167 };
  2168 
  2169 function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) {
  2170     if (preferModelValue) {
  2171         if (modelValue !== ko.selectExtensions.readValue(element))
  2172             ko.selectExtensions.writeValue(element, modelValue);
  2173     }
  2174 
  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");
  2180 };
  2181 
  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);
  2192         }
  2193 
  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);
  2198         }
  2199 
  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();
  2211                 }
  2212             });
  2213         }
  2214 
  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);
  2223             }
  2224             ko.utils.registerEventHandler(element, eventName, handler);
  2225         });
  2226     },
  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);
  2232 
  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;
  2237 
  2238         if (valueHasChanged) {
  2239             var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
  2240             applyValueAction();
  2241 
  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);
  2248         }
  2249 
  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);
  2254     }
  2255 };
  2256 
  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");
  2261 
  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;
  2267         });
  2268         var previousScrollTop = element.scrollTop;
  2269 
  2270         var value = ko.utils.unwrapObservable(valueAccessor());
  2271         var selectedValue = element.value;
  2272 
  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]);
  2277             element.remove(0);
  2278         }
  2279 
  2280         if (value) {
  2281             var allBindings = allBindingsAccessor();
  2282             if (typeof value.length != "number")
  2283                 value = [value];
  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);
  2289             }
  2290             for (var i = 0, j = value.length; i < j; i++) {
  2291                 var option = document.createElement("option");
  2292 
  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);
  2297 
  2298                 // Apply some text to the option element
  2299                 var optionsTextValue = allBindings['optionsText'];
  2300                 var optionText;
  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
  2305                 else
  2306                     optionText = optionValue;				 // Given no optionsText arg; use the data value itself
  2307                 if ((optionText === null) || (optionText === undefined))
  2308                     optionText = "";
  2309 
  2310                 ko.utils.setTextContent(option, optionText);
  2311 
  2312                 element.appendChild(option);
  2313             }
  2314 
  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++;
  2323                 }
  2324             }
  2325 
  2326             element.scrollTop = previousScrollTop;
  2327 
  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);
  2333             }
  2334 
  2335             // Workaround for IE9 bug
  2336             ko.utils.ensureSelectElementIsRenderedCorrectly(element);
  2337         }
  2338     }
  2339 };
  2340 ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.optionValueDomData__';
  2341 
  2342 ko.bindingHandlers['selectedOptions'] = {
  2343     getSelectedValuesFromSelectNode: function (selectNode) {
  2344         var result = [];
  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
  2353             }
  2354         }
  2355         return result;
  2356     },
  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);
  2362         });
  2363     },
  2364     'update': function (element, valueAccessor) {
  2365         if (ko.utils.tagNameLower(element) != "select")
  2366             throw new Error("values binding applies only to SELECT elements");
  2367 
  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);
  2375             }
  2376         }
  2377     }
  2378 };
  2379 
  2380 ko.bindingHandlers['text'] = {
  2381     'update': function (element, valueAccessor) {
  2382         ko.utils.setTextContent(element, valueAccessor());
  2383     }
  2384 };
  2385 
  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 };
  2390     },
  2391     'update': function (element, valueAccessor) {
  2392         var value = ko.utils.unwrapObservable(valueAccessor());
  2393         ko.utils.setHtml(element, value);
  2394     }
  2395 };
  2396 
  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);
  2404             }
  2405         }
  2406     }
  2407 };
  2408 
  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
  2416             }
  2417         }
  2418     }
  2419 };
  2420 
  2421 ko.bindingHandlers['uniqueName'] = {
  2422     'init': function (element, valueAccessor) {
  2423         if (valueAccessor()) {
  2424             element.name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
  2425 
  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);
  2431         }
  2432     }
  2433 };
  2434 ko.bindingHandlers['uniqueName'].currentIndex = 0;
  2435 
  2436 ko.bindingHandlers['checked'] = {
  2437     'init': function (element, valueAccessor, allBindingsAccessor) {
  2438         var updateHandler = function() {
  2439             var valueToWrite;
  2440             if (element.type == "checkbox") {
  2441                 valueToWrite = element.checked;
  2442             } else if ((element.type == "radio") && (element.checked)) {
  2443                 valueToWrite = element.value;
  2444             } else {
  2445                 return; // "checked" binding only responds to checkboxes and selected radio buttons
  2446             }
  2447 
  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);
  2457             } else {
  2458                 ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
  2459             }
  2460         };
  2461         ko.utils.registerEventHandler(element, "click", updateHandler);
  2462 
  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 });
  2466     },
  2467     'update': function (element, valueAccessor) {
  2468         var value = ko.utils.unwrapObservable(valueAccessor());
  2469 
  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;
  2474             } else {
  2475                 // When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
  2476                 element.checked = value;
  2477             }
  2478         } else if (element.type == "radio") {
  2479             element.checked = (element.value == value);
  2480         }
  2481     }
  2482 };
  2483 
  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]);
  2491 
  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);
  2496                 if (toRemove)
  2497                     element.removeAttribute(attrName);
  2498 
  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];
  2505                     if (toRemove)
  2506                         element.removeAttribute(attrName);
  2507                     else
  2508                         element[attrName] = attrValue;
  2509                 } else if (!toRemove) {
  2510                     element.setAttribute(attrName, attrValue.toString());
  2511                 }
  2512             }
  2513         }
  2514     }
  2515 };
  2516 
  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);
  2522         };
  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
  2527     },
  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
  2532     }
  2533 };
  2534 
  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 } };
  2539     },
  2540     'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  2541         return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor));
  2542     },
  2543     'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  2544         return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
  2545     }
  2546 };
  2547 ko.jsonExpressionRewriting.bindingRewriteValidators['with'] = false; // Can't rewrite control flow bindings
  2548 ko.virtualElements.allowedBindings['with'] = true;
  2549 
  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 } };
  2554     },
  2555     'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  2556         return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor));
  2557     },
  2558     'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  2559         return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
  2560     }
  2561 };
  2562 ko.jsonExpressionRewriting.bindingRewriteValidators['if'] = false; // Can't rewrite control flow bindings
  2563 ko.virtualElements.allowedBindings['if'] = true;
  2564 
  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 } };
  2569     },
  2570     'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  2571         return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['ifnot'].makeTemplateValueAccessor(valueAccessor));
  2572     },
  2573     'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  2574         return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['ifnot'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
  2575     }
  2576 };
  2577 ko.jsonExpressionRewriting.bindingRewriteValidators['ifnot'] = false; // Can't rewrite control flow bindings
  2578 ko.virtualElements.allowedBindings['ifnot'] = true;
  2579 
  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) {
  2584         return function() {
  2585             var bindingValue = ko.utils.unwrapObservable(valueAccessor());
  2586 
  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 };
  2590 
  2591             // If bindingValue.data is the array, preserve all relevant options
  2592             return {
  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
  2599             };
  2600         };
  2601     },
  2602     'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  2603         return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
  2604     },
  2605     'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  2606         return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
  2607     }
  2608 };
  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,
  2612 //
  2613 // [1] Inherit from this class (like ko.nativeTemplateEngine does)
  2614 // [2] Override 'renderTemplateSource', supplying a function with this signature:
  2615 //
  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 }"
  2622 //            //
  2623 //            // Return value: an array of DOM nodes
  2624 //        }
  2625 //
  2626 // [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
  2627 //
  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 }'
  2631 //        }
  2632 //
  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'.
  2636 
  2637 ko.templateEngine = function () { };
  2638 
  2639 ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
  2640     throw new Error("Override renderTemplateSource");
  2641 };
  2642 
  2643 ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
  2644     throw new Error("Override createJavaScriptEvaluatorBlock");
  2645 };
  2646 
  2647 ko.templateEngine.prototype['makeTemplateSource'] = function(template, templateDocument) {
  2648     // Named template
  2649     if (typeof template == "string") {
  2650         templateDocument = templateDocument || document;
  2651         var elem = templateDocument.getElementById(template);
  2652         if (!elem)
  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);
  2658     } else
  2659         throw new Error("Unknown template type: " + template);
  2660 };
  2661 
  2662 ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) {
  2663     var templateSource = this['makeTemplateSource'](template, templateDocument);
  2664     return this['renderTemplateSource'](templateSource, bindingContext, options);
  2665 };
  2666 
  2667 ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) {
  2668     // Skip rewriting if requested
  2669     if (this['allowTemplateRewriting'] === false)
  2670         return true;
  2671 
  2672     // Perf optimisation - see below
  2673     var templateIsInExternalDocument = templateDocument && templateDocument != document;
  2674     if (!templateIsInExternalDocument && this.knownRewrittenTemplates && this.knownRewrittenTemplates[template])
  2675         return true;
  2676 
  2677     return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten");
  2678 };
  2679 
  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);
  2685 
  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)
  2688     //
  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;
  2697     }
  2698 };
  2699 
  2700 ko.exportSymbol('templateEngine', ko.templateEngine);
  2701 
  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;
  2705 
  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];
  2712 
  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");
  2719                 }
  2720             }
  2721         }
  2722     }
  2723 
  2724     function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, templateEngine) {
  2725         var dataBindKeyValueArray = ko.jsonExpressionRewriting.parseObjectLiteral(dataBindAttributeValue);
  2726         validateDataBindValuesForRewriting(dataBindKeyValueArray);
  2727         var rewrittenDataBindAttributeValue = ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(dataBindKeyValueArray);
  2728 
  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 + " } })() \
  2734         })";
  2735         return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
  2736     }
  2737 
  2738     return {
  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);
  2744         },
  2745 
  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);
  2751             });
  2752         },
  2753 
  2754         applyMemoizedBindingsToNextSibling: function (bindings) {
  2755             return ko.memoization.memoize(function (domNode, bindingContext) {
  2756                 if (domNode.nextSibling)
  2757                     ko.applyBindingsToNode(domNode.nextSibling, bindings, bindingContext);
  2758             });
  2759         }
  2760     }
  2761 })();
  2762 
  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
  2765 (function() {
  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.)
  2768     //
  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".
  2780     //
  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().
  2786     //
  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.
  2789 
  2790     ko.templateSources = {};
  2791 
  2792     // ---- ko.templateSources.domElement -----
  2793 
  2794     ko.templateSources.domElement = function(element) {
  2795         this.domElement = element;
  2796     }
  2797 
  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"
  2802                                  : "innerHTML";
  2803 
  2804         if (arguments.length == 0) {
  2805             return this.domElement[elemContentsProperty];
  2806         } else {
  2807             var valueToWrite = arguments[0];
  2808             if (elemContentsProperty === "innerHTML")
  2809                 ko.utils.setHtml(this.domElement, valueToWrite);
  2810             else
  2811                 this.domElement[elemContentsProperty] = valueToWrite;
  2812         }
  2813     };
  2814 
  2815     ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
  2816         if (arguments.length === 1) {
  2817             return ko.utils.domData.get(this.domElement, "templateSourceData_" + key);
  2818         } else {
  2819             ko.utils.domData.set(this.domElement, "templateSourceData_" + key, arguments[1]);
  2820         }
  2821     };
  2822 
  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.
  2827 
  2828     var anonymousTemplatesDomDataKey = "__ko_anon_template__";
  2829     ko.templateSources.anonymousTemplate = function(element) {
  2830         this.domElement = element;
  2831     }
  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;
  2839         } else {
  2840             var valueToWrite = arguments[0];
  2841             ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {textData: valueToWrite});
  2842         }
  2843     };
  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;
  2848         } else {
  2849             var valueToWrite = arguments[0];
  2850             ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {containerData: valueToWrite});
  2851         }
  2852     };
  2853 
  2854     ko.exportSymbol('templateSources', ko.templateSources);
  2855     ko.exportSymbol('templateSources.domElement', ko.templateSources.domElement);
  2856     ko.exportSymbol('templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate);
  2857 })();
  2858 (function () {
  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;
  2864     }
  2865 
  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)
  2871                 action(node);
  2872         }
  2873     }
  2874 
  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)
  2881 
  2882         if (continuousNodeArray.length) {
  2883             var firstNode = continuousNodeArray[0], lastNode = continuousNodeArray[continuousNodeArray.length - 1];
  2884 
  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);
  2889             });
  2890             invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, function(node) {
  2891                 ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
  2892             });
  2893         }
  2894     }
  2895 
  2896     function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
  2897         return nodeOrNodeArray.nodeType ? nodeOrNodeArray
  2898                                         : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
  2899                                         : null;
  2900     }
  2901 
  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);
  2909 
  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");
  2913 
  2914         var haveAddedNodesToParent = false;
  2915         switch (renderMode) {
  2916             case "replaceChildren":
  2917                 ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
  2918                 haveAddedNodesToParent = true;
  2919                 break;
  2920             case "replaceNode":
  2921                 ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
  2922                 haveAddedNodesToParent = true;
  2923                 break;
  2924             case "ignoreTargetNode": break;
  2925             default:
  2926                 throw new Error("Unknown renderMode: " + renderMode);
  2927         }
  2928 
  2929         if (haveAddedNodesToParent) {
  2930             activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
  2931             if (options['afterRender'])
  2932                 options['afterRender'](renderedNodesArray, bindingContext['$data']);
  2933         }
  2934 
  2935         return renderedNodesArray;
  2936     }
  2937 
  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";
  2943 
  2944         if (targetNodeOrNodeArray) {
  2945             var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
  2946 
  2947             var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
  2948             var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
  2949 
  2950             return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
  2951                 function () {
  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));
  2956 
  2957                     // Support selecting template as a function of the data being rendered
  2958                     var templateName = typeof(template) == 'function' ? template(bindingContext['$data']) : template;
  2959 
  2960                     var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
  2961                     if (renderMode == "replaceNode") {
  2962                         targetNodeOrNodeArray = renderedNodesArray;
  2963                         firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
  2964                     }
  2965                 },
  2966                 null,
  2967                 { 'disposeWhen': whenToDispose, 'disposeWhenNodeIsRemoved': activelyDisposeWhenNodeIsRemoved }
  2968             );
  2969         } else {
  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");
  2973             });
  2974         }
  2975     };
  2976 
  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;
  2981 
  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);
  2989         }
  2990 
  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);
  2996         };
  2997 
  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];
  3002 
  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']);
  3006             });
  3007 
  3008             ko.utils.setDomNodeChildrenFromArrayMapping(targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback);
  3009 
  3010         }, null, { 'disposeWhenNodeIsRemoved': targetNode });
  3011     };
  3012 
  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);
  3019     }
  3020 
  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);
  3030             }
  3031             return { 'controlsDescendantBindings': true };
  3032         },
  3033         'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  3034             var bindingValue = ko.utils.unwrapObservable(valueAccessor());
  3035             var templateName;
  3036             var shouldDisplay = true;
  3037 
  3038             if (typeof bindingValue == "string") {
  3039                 templateName = bindingValue;
  3040             } else {
  3041                 templateName = bindingValue['name'];
  3042 
  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']);
  3048             }
  3049 
  3050             var templateSubscription = null;
  3051 
  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);
  3056             } else {
  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);
  3063                 } else
  3064                     ko.virtualElements.emptyNode(element);
  3065             }
  3066 
  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);
  3069         }
  3070     };
  3071 
  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);
  3075 
  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)
  3078 
  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";
  3082     };
  3083 
  3084     ko.virtualElements.allowedBindings['template'] = true;
  3085 })();
  3086 
  3087 ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
  3088 ko.exportSymbol('renderTemplate', ko.renderTemplate);
  3089 
  3090 (function () {
  3091     // Simple calculation based on Levenshtein distance.
  3092     function calculateEditDistanceMatrix(oldArray, newArray, maxAllowedDistance) {
  3093         var distances = [];
  3094         for (var i = 0; i <= newArray.length; i++)
  3095             distances[i] = [];
  3096 
  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;
  3100 
  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;
  3104         }
  3105 
  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];
  3115                 else {
  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);
  3119                 }
  3120             }
  3121         }
  3122 
  3123         return distances;
  3124     }
  3125 
  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;
  3141 
  3142             if ((distanceViaAdd <= distanceViaDelete) && (distanceViaAdd < distanceViaRetain)) {
  3143                 editScript.push({ status: "added", value: newArray[newIndex - 1] });
  3144                 newIndex--;
  3145             } else if ((distanceViaDelete < distanceViaAdd) && (distanceViaDelete < distanceViaRetain)) {
  3146                 editScript.push({ status: "deleted", value: oldArray[oldIndex - 1] });
  3147                 oldIndex--;
  3148             } else {
  3149                 editScript.push({ status: "retained", value: oldArray[oldIndex - 1] });
  3150                 newIndex--;
  3151                 oldIndex--;
  3152             }
  3153         }
  3154         return editScript.reverse();
  3155     }
  3156 
  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
  3162         } else {
  3163             oldArray = oldArray || [];
  3164             newArray = newArray || [];
  3165             var editDistanceMatrix = calculateEditDistanceMatrix(oldArray, newArray, maxEditsToConsider);
  3166             return findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray);
  3167         }
  3168     };
  3169 })();
  3170 
  3171 ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
  3172 
  3173 (function () {
  3174     // Objective:
  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
  3180 
  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.
  3183 
  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
  3189 
  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)
  3196                     return;
  3197                 newContiguousSet.push(current);
  3198             }
  3199 
  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));
  3203         }
  3204     }
  3205 
  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) || [];
  3211 
  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);
  3218             }
  3219 
  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 };
  3226     }
  3227 
  3228     var lastMappingResultDomDataKey = "setDomNodeChildrenFromArrayMapping_lastMappingResult";
  3229 
  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);
  3238 
  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) {
  3248                 case "retained":
  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++;
  3256                     break;
  3257 
  3258                 case "deleted":
  3259                     // Stop tracking changes to the mapping for these nodes
  3260                     lastMappingResult[lastMappingResultIndex].dependentObservable.dispose();
  3261 
  3262                     // Queue these nodes for later removal
  3263                     fixUpVirtualElements(lastMappingResult[lastMappingResultIndex].domNodes);
  3264                     ko.utils.arrayForEach(lastMappingResult[lastMappingResultIndex].domNodes, function (node) {
  3265                         nodesToDelete.push({
  3266                           element: node,
  3267                           index: i,
  3268                           value: editScript[i].value
  3269                         });
  3270                         insertAfterNode = node;
  3271                     });
  3272                     lastMappingResultIndex++;
  3273                     break;
  3274 
  3275                 case "added":
  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;
  3280 
  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
  3287                     });
  3288                     for (var nodeIndex = 0, nodeIndexMax = mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) {
  3289                         var node = mappedNodes[nodeIndex];
  3290                         nodesAdded.push({
  3291                           element: node,
  3292                           index: i,
  3293                           value: editScript[i].value
  3294                         });
  3295                         if (insertAfterNode == null) {
  3296                             // Insert "node" (the newly-created node) as domNode's first child
  3297                             ko.virtualElements.prepend(domNode, node);
  3298                         } else {
  3299                             // Insert "node" into "domNode" immediately after "insertAfterNode"
  3300                             ko.virtualElements.insertAfter(domNode, node, insertAfterNode);
  3301                         }
  3302                         insertAfterNode = node;
  3303                     }
  3304                     if (callbackAfterAddingNodes)
  3305                         callbackAfterAddingNodes(valueToMap, mappedNodes, indexObservable);
  3306                     break;
  3307             }
  3308         }
  3309 
  3310         ko.utils.arrayForEach(nodesToDelete, function (node) { ko.cleanNode(node.element) });
  3311 
  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);
  3317             }
  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;
  3322             }
  3323         }
  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);
  3329             }
  3330         }
  3331 
  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);
  3334     }
  3335 })();
  3336 
  3337 ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
  3338 ko.nativeTemplateEngine = function () {
  3339     this['allowTemplateRewriting'] = false;
  3340 }
  3341 
  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;
  3347 
  3348     if (templateNodes) {
  3349         return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes);
  3350     } else {
  3351         var templateText = templateSource['text']();
  3352         return ko.utils.parseHtmlFragment(templateText);
  3353     }
  3354 };
  3355 
  3356 ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine();
  3357 ko.setTemplateEngine(ko.nativeTemplateEngine.instance);
  3358 
  3359 ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
  3360 (function() {
  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']))
  3368                 return 0;
  3369             // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
  3370             try {
  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
  3374                 }
  3375             } catch(ex) { /* Apparently not the version we were looking for */ }
  3376 
  3377             return 1; // Any older version that we don't support
  3378         })();
  3379 
  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.");
  3383         }
  3384 
  3385         function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
  3386             return jQuery['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
  3387         }
  3388 
  3389         this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
  3390             options = options || {};
  3391             ensureHasReferencedJQueryTemplates();
  3392 
  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');
  3395             if (!precompiled) {
  3396                 var templateText = templateSource['text']() || "";
  3397                 // Wrap in "with($whatever.koBindingContext) { ... }"
  3398                 templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
  3399 
  3400                 precompiled = jQuery['template'](null, templateText);
  3401                 templateSource['data']('precompiled', precompiled);
  3402             }
  3403 
  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']);
  3406 
  3407             var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
  3408             resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
  3409 
  3410             jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
  3411             return resultNodes;
  3412         };
  3413 
  3414         this['createJavaScriptEvaluatorBlock'] = function(script) {
  3415             return "{{ko_code ((function() { return " + script + " })()) }}";
  3416         };
  3417 
  3418         this['addTemplate'] = function(templateName, templateMarkup) {
  3419             document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
  3420         };
  3421 
  3422         if (jQueryTmplVersion > 0) {
  3423             jQuery['tmpl']['tag']['ko_code'] = {
  3424                 open: "__.push($1 || '');"
  3425             };
  3426             jQuery['tmpl']['tag']['ko_with'] = {
  3427                 open: "with($1) {",
  3428                 close: "} "
  3429             };
  3430         }
  3431     };
  3432 
  3433     ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
  3434 
  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);
  3439 
  3440     ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
  3441 })();
  3442 });
  3443 })(window,document,navigator);