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.
moel@348
     1
// Knockout JavaScript library v2.1.0
moel@348
     2
// (c) Steven Sanderson - http://knockoutjs.com/
moel@348
     3
// License: MIT (http://www.opensource.org/licenses/mit-license.php)
moel@348
     4
moel@348
     5
(function(window,document,navigator,undefined){
moel@348
     6
var DEBUG=true;
moel@348
     7
!function(factory) {
moel@348
     8
    // Support three module loading scenarios
moel@348
     9
    if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
moel@348
    10
        // [1] CommonJS/Node.js
moel@348
    11
        var target = module['exports'] || exports; // module.exports is for Node.js
moel@348
    12
        factory(target);
moel@348
    13
    } else if (typeof define === 'function' && define['amd']) {
moel@348
    14
        // [2] AMD anonymous module
moel@348
    15
        define(['exports'], factory);
moel@348
    16
    } else {
moel@348
    17
        // [3] No module loader (plain <script> tag) - put directly in global namespace
moel@348
    18
        factory(window['ko'] = {});
moel@348
    19
    }
moel@348
    20
}(function(koExports){
moel@348
    21
// Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
moel@348
    22
// In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
moel@348
    23
var ko = typeof koExports !== 'undefined' ? koExports : {};
moel@348
    24
// Google Closure Compiler helpers (used only to make the minified file smaller)
moel@348
    25
ko.exportSymbol = function(koPath, object) {
moel@348
    26
	var tokens = koPath.split(".");
moel@348
    27
moel@348
    28
	// In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable)
moel@348
    29
	// At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko)
moel@348
    30
	var target = ko;
moel@348
    31
moel@348
    32
	for (var i = 0; i < tokens.length - 1; i++)
moel@348
    33
		target = target[tokens[i]];
moel@348
    34
	target[tokens[tokens.length - 1]] = object;
moel@348
    35
};
moel@348
    36
ko.exportProperty = function(owner, publicName, object) {
moel@348
    37
  owner[publicName] = object;
moel@348
    38
};
moel@348
    39
ko.version = "2.1.0";
moel@348
    40
moel@348
    41
ko.exportSymbol('version', ko.version);
moel@348
    42
ko.utils = new (function () {
moel@348
    43
    var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
moel@348
    44
moel@348
    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)
moel@348
    46
    var knownEvents = {}, knownEventTypesByEventName = {};
moel@348
    47
    var keyEventTypeName = /Firefox\/2/i.test(navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents';
moel@348
    48
    knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
moel@348
    49
    knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
moel@348
    50
    for (var eventType in knownEvents) {
moel@348
    51
        var knownEventsForType = knownEvents[eventType];
moel@348
    52
        if (knownEventsForType.length) {
moel@348
    53
            for (var i = 0, j = knownEventsForType.length; i < j; i++)
moel@348
    54
                knownEventTypesByEventName[knownEventsForType[i]] = eventType;
moel@348
    55
        }
moel@348
    56
    }
moel@348
    57
    var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
moel@348
    58
moel@348
    59
    // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
moel@348
    60
    var ieVersion = (function() {
moel@348
    61
        var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
moel@348
    62
moel@348
    63
        // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
moel@348
    64
        while (
moel@348
    65
            div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
moel@348
    66
            iElems[0]
moel@348
    67
        );
moel@348
    68
        return version > 4 ? version : undefined;
moel@348
    69
    }());
moel@348
    70
    var isIe6 = ieVersion === 6,
moel@348
    71
        isIe7 = ieVersion === 7;
moel@348
    72
moel@348
    73
    function isClickOnCheckableElement(element, eventType) {
moel@348
    74
        if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false;
moel@348
    75
        if (eventType.toLowerCase() != "click") return false;
moel@348
    76
        var inputType = element.type;
moel@348
    77
        return (inputType == "checkbox") || (inputType == "radio");
moel@348
    78
    }
moel@348
    79
moel@348
    80
    return {
moel@348
    81
        fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
moel@348
    82
moel@348
    83
        arrayForEach: function (array, action) {
moel@348
    84
            for (var i = 0, j = array.length; i < j; i++)
moel@348
    85
                action(array[i]);
moel@348
    86
        },
moel@348
    87
moel@348
    88
        arrayIndexOf: function (array, item) {
moel@348
    89
            if (typeof Array.prototype.indexOf == "function")
moel@348
    90
                return Array.prototype.indexOf.call(array, item);
moel@348
    91
            for (var i = 0, j = array.length; i < j; i++)
moel@348
    92
                if (array[i] === item)
moel@348
    93
                    return i;
moel@348
    94
            return -1;
moel@348
    95
        },
moel@348
    96
moel@348
    97
        arrayFirst: function (array, predicate, predicateOwner) {
moel@348
    98
            for (var i = 0, j = array.length; i < j; i++)
moel@348
    99
                if (predicate.call(predicateOwner, array[i]))
moel@348
   100
                    return array[i];
moel@348
   101
            return null;
moel@348
   102
        },
moel@348
   103
moel@348
   104
        arrayRemoveItem: function (array, itemToRemove) {
moel@348
   105
            var index = ko.utils.arrayIndexOf(array, itemToRemove);
moel@348
   106
            if (index >= 0)
moel@348
   107
                array.splice(index, 1);
moel@348
   108
        },
moel@348
   109
moel@348
   110
        arrayGetDistinctValues: function (array) {
moel@348
   111
            array = array || [];
moel@348
   112
            var result = [];
moel@348
   113
            for (var i = 0, j = array.length; i < j; i++) {
moel@348
   114
                if (ko.utils.arrayIndexOf(result, array[i]) < 0)
moel@348
   115
                    result.push(array[i]);
moel@348
   116
            }
moel@348
   117
            return result;
moel@348
   118
        },
moel@348
   119
moel@348
   120
        arrayMap: function (array, mapping) {
moel@348
   121
            array = array || [];
moel@348
   122
            var result = [];
moel@348
   123
            for (var i = 0, j = array.length; i < j; i++)
moel@348
   124
                result.push(mapping(array[i]));
moel@348
   125
            return result;
moel@348
   126
        },
moel@348
   127
moel@348
   128
        arrayFilter: function (array, predicate) {
moel@348
   129
            array = array || [];
moel@348
   130
            var result = [];
moel@348
   131
            for (var i = 0, j = array.length; i < j; i++)
moel@348
   132
                if (predicate(array[i]))
moel@348
   133
                    result.push(array[i]);
moel@348
   134
            return result;
moel@348
   135
        },
moel@348
   136
moel@348
   137
        arrayPushAll: function (array, valuesToPush) {
moel@348
   138
            if (valuesToPush instanceof Array)
moel@348
   139
                array.push.apply(array, valuesToPush);
moel@348
   140
            else
moel@348
   141
                for (var i = 0, j = valuesToPush.length; i < j; i++)
moel@348
   142
                    array.push(valuesToPush[i]);
moel@348
   143
            return array;
moel@348
   144
        },
moel@348
   145
moel@348
   146
        extend: function (target, source) {
moel@348
   147
            if (source) {
moel@348
   148
                for(var prop in source) {
moel@348
   149
                    if(source.hasOwnProperty(prop)) {
moel@348
   150
                        target[prop] = source[prop];
moel@348
   151
                    }
moel@348
   152
                }
moel@348
   153
            }
moel@348
   154
            return target;
moel@348
   155
        },
moel@348
   156
moel@348
   157
        emptyDomNode: function (domNode) {
moel@348
   158
            while (domNode.firstChild) {
moel@348
   159
                ko.removeNode(domNode.firstChild);
moel@348
   160
            }
moel@348
   161
        },
moel@348
   162
moel@348
   163
        moveCleanedNodesToContainerElement: function(nodes) {
moel@348
   164
            // Ensure it's a real array, as we're about to reparent the nodes and
moel@348
   165
            // we don't want the underlying collection to change while we're doing that.
moel@348
   166
            var nodesArray = ko.utils.makeArray(nodes);
moel@348
   167
moel@348
   168
            var container = document.createElement('div');
moel@348
   169
            for (var i = 0, j = nodesArray.length; i < j; i++) {
moel@348
   170
                ko.cleanNode(nodesArray[i]);
moel@348
   171
                container.appendChild(nodesArray[i]);
moel@348
   172
            }
moel@348
   173
            return container;
moel@348
   174
        },
moel@348
   175
moel@348
   176
        setDomNodeChildren: function (domNode, childNodes) {
moel@348
   177
            ko.utils.emptyDomNode(domNode);
moel@348
   178
            if (childNodes) {
moel@348
   179
                for (var i = 0, j = childNodes.length; i < j; i++)
moel@348
   180
                    domNode.appendChild(childNodes[i]);
moel@348
   181
            }
moel@348
   182
        },
moel@348
   183
moel@348
   184
        replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
moel@348
   185
            var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
moel@348
   186
            if (nodesToReplaceArray.length > 0) {
moel@348
   187
                var insertionPoint = nodesToReplaceArray[0];
moel@348
   188
                var parent = insertionPoint.parentNode;
moel@348
   189
                for (var i = 0, j = newNodesArray.length; i < j; i++)
moel@348
   190
                    parent.insertBefore(newNodesArray[i], insertionPoint);
moel@348
   191
                for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
moel@348
   192
                    ko.removeNode(nodesToReplaceArray[i]);
moel@348
   193
                }
moel@348
   194
            }
moel@348
   195
        },
moel@348
   196
moel@348
   197
        setOptionNodeSelectionState: function (optionNode, isSelected) {
moel@348
   198
            // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
moel@348
   199
            if (navigator.userAgent.indexOf("MSIE 6") >= 0)
moel@348
   200
                optionNode.setAttribute("selected", isSelected);
moel@348
   201
            else
moel@348
   202
                optionNode.selected = isSelected;
moel@348
   203
        },
moel@348
   204
moel@348
   205
        stringTrim: function (string) {
moel@348
   206
            return (string || "").replace(stringTrimRegex, "");
moel@348
   207
        },
moel@348
   208
moel@348
   209
        stringTokenize: function (string, delimiter) {
moel@348
   210
            var result = [];
moel@348
   211
            var tokens = (string || "").split(delimiter);
moel@348
   212
            for (var i = 0, j = tokens.length; i < j; i++) {
moel@348
   213
                var trimmed = ko.utils.stringTrim(tokens[i]);
moel@348
   214
                if (trimmed !== "")
moel@348
   215
                    result.push(trimmed);
moel@348
   216
            }
moel@348
   217
            return result;
moel@348
   218
        },
moel@348
   219
moel@348
   220
        stringStartsWith: function (string, startsWith) {
moel@348
   221
            string = string || "";
moel@348
   222
            if (startsWith.length > string.length)
moel@348
   223
                return false;
moel@348
   224
            return string.substring(0, startsWith.length) === startsWith;
moel@348
   225
        },
moel@348
   226
moel@348
   227
        buildEvalWithinScopeFunction: function (expression, scopeLevels) {
moel@348
   228
            // Build the source for a function that evaluates "expression"
moel@348
   229
            // For each scope variable, add an extra level of "with" nesting
moel@348
   230
            // Example result: with(sc[1]) { with(sc[0]) { return (expression) } }
moel@348
   231
            var functionBody = "return (" + expression + ")";
moel@348
   232
            for (var i = 0; i < scopeLevels; i++) {
moel@348
   233
                functionBody = "with(sc[" + i + "]) { " + functionBody + " } ";
moel@348
   234
            }
moel@348
   235
            return new Function("sc", functionBody);
moel@348
   236
        },
moel@348
   237
moel@348
   238
        domNodeIsContainedBy: function (node, containedByNode) {
moel@348
   239
            if (containedByNode.compareDocumentPosition)
moel@348
   240
                return (containedByNode.compareDocumentPosition(node) & 16) == 16;
moel@348
   241
            while (node != null) {
moel@348
   242
                if (node == containedByNode)
moel@348
   243
                    return true;
moel@348
   244
                node = node.parentNode;
moel@348
   245
            }
moel@348
   246
            return false;
moel@348
   247
        },
moel@348
   248
moel@348
   249
        domNodeIsAttachedToDocument: function (node) {
moel@348
   250
            return ko.utils.domNodeIsContainedBy(node, node.ownerDocument);
moel@348
   251
        },
moel@348
   252
moel@348
   253
        tagNameLower: function(element) {
moel@348
   254
            // For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case.
moel@348
   255
            // Possible future optimization: If we know it's an element from an XHTML document (not HTML),
moel@348
   256
            // we don't need to do the .toLowerCase() as it will always be lower case anyway.
moel@348
   257
            return element && element.tagName && element.tagName.toLowerCase();
moel@348
   258
        },
moel@348
   259
moel@348
   260
        registerEventHandler: function (element, eventType, handler) {
moel@348
   261
            var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
moel@348
   262
            if (!mustUseAttachEvent && typeof jQuery != "undefined") {
moel@348
   263
                if (isClickOnCheckableElement(element, eventType)) {
moel@348
   264
                    // For click events on checkboxes, jQuery interferes with the event handling in an awkward way:
moel@348
   265
                    // it toggles the element checked state *after* the click event handlers run, whereas native
moel@348
   266
                    // click events toggle the checked state *before* the event handler.
moel@348
   267
                    // Fix this by intecepting the handler and applying the correct checkedness before it runs.
moel@348
   268
                    var originalHandler = handler;
moel@348
   269
                    handler = function(event, eventData) {
moel@348
   270
                        var jQuerySuppliedCheckedState = this.checked;
moel@348
   271
                        if (eventData)
moel@348
   272
                            this.checked = eventData.checkedStateBeforeEvent !== true;
moel@348
   273
                        originalHandler.call(this, event);
moel@348
   274
                        this.checked = jQuerySuppliedCheckedState; // Restore the state jQuery applied
moel@348
   275
                    };
moel@348
   276
                }
moel@348
   277
                jQuery(element)['bind'](eventType, handler);
moel@348
   278
            } else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
moel@348
   279
                element.addEventListener(eventType, handler, false);
moel@348
   280
            else if (typeof element.attachEvent != "undefined")
moel@348
   281
                element.attachEvent("on" + eventType, function (event) {
moel@348
   282
                    handler.call(element, event);
moel@348
   283
                });
moel@348
   284
            else
moel@348
   285
                throw new Error("Browser doesn't support addEventListener or attachEvent");
moel@348
   286
        },
moel@348
   287
moel@348
   288
        triggerEvent: function (element, eventType) {
moel@348
   289
            if (!(element && element.nodeType))
moel@348
   290
                throw new Error("element must be a DOM node when calling triggerEvent");
moel@348
   291
moel@348
   292
            if (typeof jQuery != "undefined") {
moel@348
   293
                var eventData = [];
moel@348
   294
                if (isClickOnCheckableElement(element, eventType)) {
moel@348
   295
                    // Work around the jQuery "click events on checkboxes" issue described above by storing the original checked state before triggering the handler
moel@348
   296
                    eventData.push({ checkedStateBeforeEvent: element.checked });
moel@348
   297
                }
moel@348
   298
                jQuery(element)['trigger'](eventType, eventData);
moel@348
   299
            } else if (typeof document.createEvent == "function") {
moel@348
   300
                if (typeof element.dispatchEvent == "function") {
moel@348
   301
                    var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
moel@348
   302
                    var event = document.createEvent(eventCategory);
moel@348
   303
                    event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
moel@348
   304
                    element.dispatchEvent(event);
moel@348
   305
                }
moel@348
   306
                else
moel@348
   307
                    throw new Error("The supplied element doesn't support dispatchEvent");
moel@348
   308
            } else if (typeof element.fireEvent != "undefined") {
moel@348
   309
                // Unlike other browsers, IE doesn't change the checked state of checkboxes/radiobuttons when you trigger their "click" event
moel@348
   310
                // so to make it consistent, we'll do it manually here
moel@348
   311
                if (isClickOnCheckableElement(element, eventType))
moel@348
   312
                    element.checked = element.checked !== true;
moel@348
   313
                element.fireEvent("on" + eventType);
moel@348
   314
            }
moel@348
   315
            else
moel@348
   316
                throw new Error("Browser doesn't support triggering events");
moel@348
   317
        },
moel@348
   318
moel@348
   319
        unwrapObservable: function (value) {
moel@348
   320
            return ko.isObservable(value) ? value() : value;
moel@348
   321
        },
moel@348
   322
moel@348
   323
        toggleDomNodeCssClass: function (node, className, shouldHaveClass) {
moel@348
   324
            var currentClassNames = (node.className || "").split(/\s+/);
moel@348
   325
            var hasClass = ko.utils.arrayIndexOf(currentClassNames, className) >= 0;
moel@348
   326
moel@348
   327
            if (shouldHaveClass && !hasClass) {
moel@348
   328
                node.className += (currentClassNames[0] ? " " : "") + className;
moel@348
   329
            } else if (hasClass && !shouldHaveClass) {
moel@348
   330
                var newClassName = "";
moel@348
   331
                for (var i = 0; i < currentClassNames.length; i++)
moel@348
   332
                    if (currentClassNames[i] != className)
moel@348
   333
                        newClassName += currentClassNames[i] + " ";
moel@348
   334
                node.className = ko.utils.stringTrim(newClassName);
moel@348
   335
            }
moel@348
   336
        },
moel@348
   337
moel@348
   338
        setTextContent: function(element, textContent) {
moel@348
   339
            var value = ko.utils.unwrapObservable(textContent);
moel@348
   340
            if ((value === null) || (value === undefined))
moel@348
   341
                value = "";
moel@348
   342
moel@348
   343
            'innerText' in element ? element.innerText = value
moel@348
   344
                                   : element.textContent = value;
moel@348
   345
moel@348
   346
            if (ieVersion >= 9) {
moel@348
   347
                // Believe it or not, this actually fixes an IE9 rendering bug
moel@348
   348
                // (See https://github.com/SteveSanderson/knockout/issues/209)
moel@348
   349
                element.style.display = element.style.display;
moel@348
   350
            }
moel@348
   351
        },
moel@348
   352
moel@348
   353
        ensureSelectElementIsRenderedCorrectly: function(selectElement) {
moel@348
   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.
moel@348
   355
            // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
moel@348
   356
            if (ieVersion >= 9) {
moel@348
   357
                var originalWidth = selectElement.style.width;
moel@348
   358
                selectElement.style.width = 0;
moel@348
   359
                selectElement.style.width = originalWidth;
moel@348
   360
            }
moel@348
   361
        },
moel@348
   362
moel@348
   363
        range: function (min, max) {
moel@348
   364
            min = ko.utils.unwrapObservable(min);
moel@348
   365
            max = ko.utils.unwrapObservable(max);
moel@348
   366
            var result = [];
moel@348
   367
            for (var i = min; i <= max; i++)
moel@348
   368
                result.push(i);
moel@348
   369
            return result;
moel@348
   370
        },
moel@348
   371
moel@348
   372
        makeArray: function(arrayLikeObject) {
moel@348
   373
            var result = [];
moel@348
   374
            for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
moel@348
   375
                result.push(arrayLikeObject[i]);
moel@348
   376
            };
moel@348
   377
            return result;
moel@348
   378
        },
moel@348
   379
moel@348
   380
        isIe6 : isIe6,
moel@348
   381
        isIe7 : isIe7,
moel@348
   382
        ieVersion : ieVersion,
moel@348
   383
moel@348
   384
        getFormFields: function(form, fieldName) {
moel@348
   385
            var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
moel@348
   386
            var isMatchingField = (typeof fieldName == 'string')
moel@348
   387
                ? function(field) { return field.name === fieldName }
moel@348
   388
                : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
moel@348
   389
            var matches = [];
moel@348
   390
            for (var i = fields.length - 1; i >= 0; i--) {
moel@348
   391
                if (isMatchingField(fields[i]))
moel@348
   392
                    matches.push(fields[i]);
moel@348
   393
            };
moel@348
   394
            return matches;
moel@348
   395
        },
moel@348
   396
moel@348
   397
        parseJson: function (jsonString) {
moel@348
   398
            if (typeof jsonString == "string") {
moel@348
   399
                jsonString = ko.utils.stringTrim(jsonString);
moel@348
   400
                if (jsonString) {
moel@348
   401
                    if (window.JSON && window.JSON.parse) // Use native parsing where available
moel@348
   402
                        return window.JSON.parse(jsonString);
moel@348
   403
                    return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
moel@348
   404
                }
moel@348
   405
            }
moel@348
   406
            return null;
moel@348
   407
        },
moel@348
   408
moel@348
   409
        stringifyJson: function (data, replacer, space) {   // replacer and space are optional
moel@348
   410
            if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined"))
moel@348
   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");
moel@348
   412
            return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
moel@348
   413
        },
moel@348
   414
moel@348
   415
        postJson: function (urlOrForm, data, options) {
moel@348
   416
            options = options || {};
moel@348
   417
            var params = options['params'] || {};
moel@348
   418
            var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
moel@348
   419
            var url = urlOrForm;
moel@348
   420
moel@348
   421
            // If we were given a form, use its 'action' URL and pick out any requested field values
moel@348
   422
            if((typeof urlOrForm == 'object') && (ko.utils.tagNameLower(urlOrForm) === "form")) {
moel@348
   423
                var originalForm = urlOrForm;
moel@348
   424
                url = originalForm.action;
moel@348
   425
                for (var i = includeFields.length - 1; i >= 0; i--) {
moel@348
   426
                    var fields = ko.utils.getFormFields(originalForm, includeFields[i]);
moel@348
   427
                    for (var j = fields.length - 1; j >= 0; j--)
moel@348
   428
                        params[fields[j].name] = fields[j].value;
moel@348
   429
                }
moel@348
   430
            }
moel@348
   431
moel@348
   432
            data = ko.utils.unwrapObservable(data);
moel@348
   433
            var form = document.createElement("form");
moel@348
   434
            form.style.display = "none";
moel@348
   435
            form.action = url;
moel@348
   436
            form.method = "post";
moel@348
   437
            for (var key in data) {
moel@348
   438
                var input = document.createElement("input");
moel@348
   439
                input.name = key;
moel@348
   440
                input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
moel@348
   441
                form.appendChild(input);
moel@348
   442
            }
moel@348
   443
            for (var key in params) {
moel@348
   444
                var input = document.createElement("input");
moel@348
   445
                input.name = key;
moel@348
   446
                input.value = params[key];
moel@348
   447
                form.appendChild(input);
moel@348
   448
            }
moel@348
   449
            document.body.appendChild(form);
moel@348
   450
            options['submitter'] ? options['submitter'](form) : form.submit();
moel@348
   451
            setTimeout(function () { form.parentNode.removeChild(form); }, 0);
moel@348
   452
        }
moel@348
   453
    }
moel@348
   454
})();
moel@348
   455
moel@348
   456
ko.exportSymbol('utils', ko.utils);
moel@348
   457
ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach);
moel@348
   458
ko.exportSymbol('utils.arrayFirst', ko.utils.arrayFirst);
moel@348
   459
ko.exportSymbol('utils.arrayFilter', ko.utils.arrayFilter);
moel@348
   460
ko.exportSymbol('utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues);
moel@348
   461
ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
moel@348
   462
ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
moel@348
   463
ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
moel@348
   464
ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
moel@348
   465
ko.exportSymbol('utils.extend', ko.utils.extend);
moel@348
   466
ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
moel@348
   467
ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
moel@348
   468
ko.exportSymbol('utils.postJson', ko.utils.postJson);
moel@348
   469
ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
moel@348
   470
ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler);
moel@348
   471
ko.exportSymbol('utils.stringifyJson', ko.utils.stringifyJson);
moel@348
   472
ko.exportSymbol('utils.range', ko.utils.range);
moel@348
   473
ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);
moel@348
   474
ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
moel@348
   475
ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
moel@348
   476
moel@348
   477
if (!Function.prototype['bind']) {
moel@348
   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)
moel@348
   479
    // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
moel@348
   480
    Function.prototype['bind'] = function (object) {
moel@348
   481
        var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift();
moel@348
   482
        return function () {
moel@348
   483
            return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
moel@348
   484
        };
moel@348
   485
    };
moel@348
   486
}
moel@348
   487
moel@348
   488
ko.utils.domData = new (function () {
moel@348
   489
    var uniqueId = 0;
moel@348
   490
    var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
moel@348
   491
    var dataStore = {};
moel@348
   492
    return {
moel@348
   493
        get: function (node, key) {
moel@348
   494
            var allDataForNode = ko.utils.domData.getAll(node, false);
moel@348
   495
            return allDataForNode === undefined ? undefined : allDataForNode[key];
moel@348
   496
        },
moel@348
   497
        set: function (node, key, value) {
moel@348
   498
            if (value === undefined) {
moel@348
   499
                // Make sure we don't actually create a new domData key if we are actually deleting a value
moel@348
   500
                if (ko.utils.domData.getAll(node, false) === undefined)
moel@348
   501
                    return;
moel@348
   502
            }
moel@348
   503
            var allDataForNode = ko.utils.domData.getAll(node, true);
moel@348
   504
            allDataForNode[key] = value;
moel@348
   505
        },
moel@348
   506
        getAll: function (node, createIfNotFound) {
moel@348
   507
            var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
moel@348
   508
            var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null");
moel@348
   509
            if (!hasExistingDataStore) {
moel@348
   510
                if (!createIfNotFound)
moel@348
   511
                    return undefined;
moel@348
   512
                dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
moel@348
   513
                dataStore[dataStoreKey] = {};
moel@348
   514
            }
moel@348
   515
            return dataStore[dataStoreKey];
moel@348
   516
        },
moel@348
   517
        clear: function (node) {
moel@348
   518
            var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
moel@348
   519
            if (dataStoreKey) {
moel@348
   520
                delete dataStore[dataStoreKey];
moel@348
   521
                node[dataStoreKeyExpandoPropertyName] = null;
moel@348
   522
            }
moel@348
   523
        }
moel@348
   524
    }
moel@348
   525
})();
moel@348
   526
moel@348
   527
ko.exportSymbol('utils.domData', ko.utils.domData);
moel@348
   528
ko.exportSymbol('utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully
moel@348
   529
moel@348
   530
ko.utils.domNodeDisposal = new (function () {
moel@348
   531
    var domDataKey = "__ko_domNodeDisposal__" + (new Date).getTime();
moel@348
   532
    var cleanableNodeTypes = { 1: true, 8: true, 9: true };       // Element, Comment, Document
moel@348
   533
    var cleanableNodeTypesWithDescendants = { 1: true, 9: true }; // Element, Document
moel@348
   534
moel@348
   535
    function getDisposeCallbacksCollection(node, createIfNotFound) {
moel@348
   536
        var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);
moel@348
   537
        if ((allDisposeCallbacks === undefined) && createIfNotFound) {
moel@348
   538
            allDisposeCallbacks = [];
moel@348
   539
            ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);
moel@348
   540
        }
moel@348
   541
        return allDisposeCallbacks;
moel@348
   542
    }
moel@348
   543
    function destroyCallbacksCollection(node) {
moel@348
   544
        ko.utils.domData.set(node, domDataKey, undefined);
moel@348
   545
    }
moel@348
   546
moel@348
   547
    function cleanSingleNode(node) {
moel@348
   548
        // Run all the dispose callbacks
moel@348
   549
        var callbacks = getDisposeCallbacksCollection(node, false);
moel@348
   550
        if (callbacks) {
moel@348
   551
            callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
moel@348
   552
            for (var i = 0; i < callbacks.length; i++)
moel@348
   553
                callbacks[i](node);
moel@348
   554
        }
moel@348
   555
moel@348
   556
        // Also erase the DOM data
moel@348
   557
        ko.utils.domData.clear(node);
moel@348
   558
moel@348
   559
        // Special support for jQuery here because it's so commonly used.
moel@348
   560
        // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
moel@348
   561
        // so notify it to tear down any resources associated with the node & descendants here.
moel@348
   562
        if ((typeof jQuery == "function") && (typeof jQuery['cleanData'] == "function"))
moel@348
   563
            jQuery['cleanData']([node]);
moel@348
   564
moel@348
   565
        // Also clear any immediate-child comment nodes, as these wouldn't have been found by
moel@348
   566
        // node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
moel@348
   567
        if (cleanableNodeTypesWithDescendants[node.nodeType])
moel@348
   568
            cleanImmediateCommentTypeChildren(node);
moel@348
   569
    }
moel@348
   570
moel@348
   571
    function cleanImmediateCommentTypeChildren(nodeWithChildren) {
moel@348
   572
        var child, nextChild = nodeWithChildren.firstChild;
moel@348
   573
        while (child = nextChild) {
moel@348
   574
            nextChild = child.nextSibling;
moel@348
   575
            if (child.nodeType === 8)
moel@348
   576
                cleanSingleNode(child);
moel@348
   577
        }
moel@348
   578
    }
moel@348
   579
moel@348
   580
    return {
moel@348
   581
        addDisposeCallback : function(node, callback) {
moel@348
   582
            if (typeof callback != "function")
moel@348
   583
                throw new Error("Callback must be a function");
moel@348
   584
            getDisposeCallbacksCollection(node, true).push(callback);
moel@348
   585
        },
moel@348
   586
moel@348
   587
        removeDisposeCallback : function(node, callback) {
moel@348
   588
            var callbacksCollection = getDisposeCallbacksCollection(node, false);
moel@348
   589
            if (callbacksCollection) {
moel@348
   590
                ko.utils.arrayRemoveItem(callbacksCollection, callback);
moel@348
   591
                if (callbacksCollection.length == 0)
moel@348
   592
                    destroyCallbacksCollection(node);
moel@348
   593
            }
moel@348
   594
        },
moel@348
   595
moel@348
   596
        cleanNode : function(node) {
moel@348
   597
            // First clean this node, where applicable
moel@348
   598
            if (cleanableNodeTypes[node.nodeType]) {
moel@348
   599
                cleanSingleNode(node);
moel@348
   600
moel@348
   601
                // ... then its descendants, where applicable
moel@348
   602
                if (cleanableNodeTypesWithDescendants[node.nodeType]) {
moel@348
   603
                    // Clone the descendants list in case it changes during iteration
moel@348
   604
                    var descendants = [];
moel@348
   605
                    ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
moel@348
   606
                    for (var i = 0, j = descendants.length; i < j; i++)
moel@348
   607
                        cleanSingleNode(descendants[i]);
moel@348
   608
                }
moel@348
   609
            }
moel@348
   610
        },
moel@348
   611
moel@348
   612
        removeNode : function(node) {
moel@348
   613
            ko.cleanNode(node);
moel@348
   614
            if (node.parentNode)
moel@348
   615
                node.parentNode.removeChild(node);
moel@348
   616
        }
moel@348
   617
    }
moel@348
   618
})();
moel@348
   619
ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience
moel@348
   620
ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience
moel@348
   621
ko.exportSymbol('cleanNode', ko.cleanNode);
moel@348
   622
ko.exportSymbol('removeNode', ko.removeNode);
moel@348
   623
ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal);
moel@348
   624
ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);
moel@348
   625
ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);
moel@348
   626
(function () {
moel@348
   627
    var leadingCommentRegex = /^(\s*)<!--(.*?)-->/;
moel@348
   628
moel@348
   629
    function simpleHtmlParse(html) {
moel@348
   630
        // Based on jQuery's "clean" function, but only accounting for table-related elements.
moel@348
   631
        // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly
moel@348
   632
moel@348
   633
        // Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of
moel@348
   634
        // a descendant node. For example: "<div><!-- mycomment -->abc</div>" will get parsed as "<div>abc</div>"
moel@348
   635
        // This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node
moel@348
   636
        // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
moel@348
   637
moel@348
   638
        // Trim whitespace, otherwise indexOf won't work as expected
moel@348
   639
        var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div");
moel@348
   640
moel@348
   641
        // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column
moel@348
   642
        var wrap = tags.match(/^<(thead|tbody|tfoot)/)              && [1, "<table>", "</table>"] ||
moel@348
   643
                   !tags.indexOf("<tr")                             && [2, "<table><tbody>", "</tbody></table>"] ||
moel@348
   644
                   (!tags.indexOf("<td") || !tags.indexOf("<th"))   && [3, "<table><tbody><tr>", "</tr></tbody></table>"] ||
moel@348
   645
                   /* anything else */                                 [0, "", ""];
moel@348
   646
moel@348
   647
        // Go to html and back, then peel off extra wrappers
moel@348
   648
        // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
moel@348
   649
        var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
moel@348
   650
        if (typeof window['innerShiv'] == "function") {
moel@348
   651
            div.appendChild(window['innerShiv'](markup));
moel@348
   652
        } else {
moel@348
   653
            div.innerHTML = markup;
moel@348
   654
        }
moel@348
   655
moel@348
   656
        // Move to the right depth
moel@348
   657
        while (wrap[0]--)
moel@348
   658
            div = div.lastChild;
moel@348
   659
moel@348
   660
        return ko.utils.makeArray(div.lastChild.childNodes);
moel@348
   661
    }
moel@348
   662
moel@348
   663
    function jQueryHtmlParse(html) {
moel@348
   664
        var elems = jQuery['clean']([html]);
moel@348
   665
moel@348
   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.
moel@348
   667
        // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
moel@348
   668
        // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.
moel@348
   669
        if (elems && elems[0]) {
moel@348
   670
            // Find the top-most parent element that's a direct child of a document fragment
moel@348
   671
            var elem = elems[0];
moel@348
   672
            while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)
moel@348
   673
                elem = elem.parentNode;
moel@348
   674
            // ... then detach it
moel@348
   675
            if (elem.parentNode)
moel@348
   676
                elem.parentNode.removeChild(elem);
moel@348
   677
        }
moel@348
   678
moel@348
   679
        return elems;
moel@348
   680
    }
moel@348
   681
moel@348
   682
    ko.utils.parseHtmlFragment = function(html) {
moel@348
   683
        return typeof jQuery != 'undefined' ? jQueryHtmlParse(html)   // As below, benefit from jQuery's optimisations where possible
moel@348
   684
                                            : simpleHtmlParse(html);  // ... otherwise, this simple logic will do in most common cases.
moel@348
   685
    };
moel@348
   686
moel@348
   687
    ko.utils.setHtml = function(node, html) {
moel@348
   688
        ko.utils.emptyDomNode(node);
moel@348
   689
moel@348
   690
        if ((html !== null) && (html !== undefined)) {
moel@348
   691
            if (typeof html != 'string')
moel@348
   692
                html = html.toString();
moel@348
   693
moel@348
   694
            // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
moel@348
   695
            // for example <tr> elements which are not normally allowed to exist on their own.
moel@348
   696
            // If you've referenced jQuery we'll use that rather than duplicating its code.
moel@348
   697
            if (typeof jQuery != 'undefined') {
moel@348
   698
                jQuery(node)['html'](html);
moel@348
   699
            } else {
moel@348
   700
                // ... otherwise, use KO's own parsing logic.
moel@348
   701
                var parsedNodes = ko.utils.parseHtmlFragment(html);
moel@348
   702
                for (var i = 0; i < parsedNodes.length; i++)
moel@348
   703
                    node.appendChild(parsedNodes[i]);
moel@348
   704
            }
moel@348
   705
        }
moel@348
   706
    };
moel@348
   707
})();
moel@348
   708
moel@348
   709
ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
moel@348
   710
ko.exportSymbol('utils.setHtml', ko.utils.setHtml);
moel@348
   711
moel@348
   712
ko.memoization = (function () {
moel@348
   713
    var memos = {};
moel@348
   714
moel@348
   715
    function randomMax8HexChars() {
moel@348
   716
        return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
moel@348
   717
    }
moel@348
   718
    function generateRandomId() {
moel@348
   719
        return randomMax8HexChars() + randomMax8HexChars();
moel@348
   720
    }
moel@348
   721
    function findMemoNodes(rootNode, appendToArray) {
moel@348
   722
        if (!rootNode)
moel@348
   723
            return;
moel@348
   724
        if (rootNode.nodeType == 8) {
moel@348
   725
            var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
moel@348
   726
            if (memoId != null)
moel@348
   727
                appendToArray.push({ domNode: rootNode, memoId: memoId });
moel@348
   728
        } else if (rootNode.nodeType == 1) {
moel@348
   729
            for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)
moel@348
   730
                findMemoNodes(childNodes[i], appendToArray);
moel@348
   731
        }
moel@348
   732
    }
moel@348
   733
moel@348
   734
    return {
moel@348
   735
        memoize: function (callback) {
moel@348
   736
            if (typeof callback != "function")
moel@348
   737
                throw new Error("You can only pass a function to ko.memoization.memoize()");
moel@348
   738
            var memoId = generateRandomId();
moel@348
   739
            memos[memoId] = callback;
moel@348
   740
            return "<!--[ko_memo:" + memoId + "]-->";
moel@348
   741
        },
moel@348
   742
moel@348
   743
        unmemoize: function (memoId, callbackParams) {
moel@348
   744
            var callback = memos[memoId];
moel@348
   745
            if (callback === undefined)
moel@348
   746
                throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");
moel@348
   747
            try {
moel@348
   748
                callback.apply(null, callbackParams || []);
moel@348
   749
                return true;
moel@348
   750
            }
moel@348
   751
            finally { delete memos[memoId]; }
moel@348
   752
        },
moel@348
   753
moel@348
   754
        unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {
moel@348
   755
            var memos = [];
moel@348
   756
            findMemoNodes(domNode, memos);
moel@348
   757
            for (var i = 0, j = memos.length; i < j; i++) {
moel@348
   758
                var node = memos[i].domNode;
moel@348
   759
                var combinedParams = [node];
moel@348
   760
                if (extraCallbackParamsArray)
moel@348
   761
                    ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray);
moel@348
   762
                ko.memoization.unmemoize(memos[i].memoId, combinedParams);
moel@348
   763
                node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
moel@348
   764
                if (node.parentNode)
moel@348
   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)
moel@348
   766
            }
moel@348
   767
        },
moel@348
   768
moel@348
   769
        parseMemoText: function (memoText) {
moel@348
   770
            var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
moel@348
   771
            return match ? match[1] : null;
moel@348
   772
        }
moel@348
   773
    };
moel@348
   774
})();
moel@348
   775
moel@348
   776
ko.exportSymbol('memoization', ko.memoization);
moel@348
   777
ko.exportSymbol('memoization.memoize', ko.memoization.memoize);
moel@348
   778
ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize);
moel@348
   779
ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText);
moel@348
   780
ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);
moel@348
   781
ko.extenders = {
moel@348
   782
    'throttle': function(target, timeout) {
moel@348
   783
        // Throttling means two things:
moel@348
   784
moel@348
   785
        // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies
moel@348
   786
        //     notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate
moel@348
   787
        target['throttleEvaluation'] = timeout;
moel@348
   788
moel@348
   789
        // (2) For writable targets (observables, or writable dependent observables), we throttle *writes*
moel@348
   790
        //     so the target cannot change value synchronously or faster than a certain rate
moel@348
   791
        var writeTimeoutInstance = null;
moel@348
   792
        return ko.dependentObservable({
moel@348
   793
            'read': target,
moel@348
   794
            'write': function(value) {
moel@348
   795
                clearTimeout(writeTimeoutInstance);
moel@348
   796
                writeTimeoutInstance = setTimeout(function() {
moel@348
   797
                    target(value);
moel@348
   798
                }, timeout);
moel@348
   799
            }
moel@348
   800
        });
moel@348
   801
    },
moel@348
   802
moel@348
   803
    'notify': function(target, notifyWhen) {
moel@348
   804
        target["equalityComparer"] = notifyWhen == "always"
moel@348
   805
            ? function() { return false } // Treat all values as not equal
moel@348
   806
            : ko.observable["fn"]["equalityComparer"];
moel@348
   807
        return target;
moel@348
   808
    }
moel@348
   809
};
moel@348
   810
moel@348
   811
function applyExtenders(requestedExtenders) {
moel@348
   812
    var target = this;
moel@348
   813
    if (requestedExtenders) {
moel@348
   814
        for (var key in requestedExtenders) {
moel@348
   815
            var extenderHandler = ko.extenders[key];
moel@348
   816
            if (typeof extenderHandler == 'function') {
moel@348
   817
                target = extenderHandler(target, requestedExtenders[key]);
moel@348
   818
            }
moel@348
   819
        }
moel@348
   820
    }
moel@348
   821
    return target;
moel@348
   822
}
moel@348
   823
moel@348
   824
ko.exportSymbol('extenders', ko.extenders);
moel@348
   825
moel@348
   826
ko.subscription = function (target, callback, disposeCallback) {
moel@348
   827
    this.target = target;
moel@348
   828
    this.callback = callback;
moel@348
   829
    this.disposeCallback = disposeCallback;
moel@348
   830
    ko.exportProperty(this, 'dispose', this.dispose);
moel@348
   831
};
moel@348
   832
ko.subscription.prototype.dispose = function () {
moel@348
   833
    this.isDisposed = true;
moel@348
   834
    this.disposeCallback();
moel@348
   835
};
moel@348
   836
moel@348
   837
ko.subscribable = function () {
moel@348
   838
    this._subscriptions = {};
moel@348
   839
moel@348
   840
    ko.utils.extend(this, ko.subscribable['fn']);
moel@348
   841
    ko.exportProperty(this, 'subscribe', this.subscribe);
moel@348
   842
    ko.exportProperty(this, 'extend', this.extend);
moel@348
   843
    ko.exportProperty(this, 'getSubscriptionsCount', this.getSubscriptionsCount);
moel@348
   844
}
moel@348
   845
moel@348
   846
var defaultEvent = "change";
moel@348
   847
moel@348
   848
ko.subscribable['fn'] = {
moel@348
   849
    subscribe: function (callback, callbackTarget, event) {
moel@348
   850
        event = event || defaultEvent;
moel@348
   851
        var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
moel@348
   852
moel@348
   853
        var subscription = new ko.subscription(this, boundCallback, function () {
moel@348
   854
            ko.utils.arrayRemoveItem(this._subscriptions[event], subscription);
moel@348
   855
        }.bind(this));
moel@348
   856
moel@348
   857
        if (!this._subscriptions[event])
moel@348
   858
            this._subscriptions[event] = [];
moel@348
   859
        this._subscriptions[event].push(subscription);
moel@348
   860
        return subscription;
moel@348
   861
    },
moel@348
   862
moel@348
   863
    "notifySubscribers": function (valueToNotify, event) {
moel@348
   864
        event = event || defaultEvent;
moel@348
   865
        if (this._subscriptions[event]) {
moel@348
   866
            ko.utils.arrayForEach(this._subscriptions[event].slice(0), function (subscription) {
moel@348
   867
                // In case a subscription was disposed during the arrayForEach cycle, check
moel@348
   868
                // for isDisposed on each subscription before invoking its callback
moel@348
   869
                if (subscription && (subscription.isDisposed !== true))
moel@348
   870
                    subscription.callback(valueToNotify);
moel@348
   871
            });
moel@348
   872
        }
moel@348
   873
    },
moel@348
   874
moel@348
   875
    getSubscriptionsCount: function () {
moel@348
   876
        var total = 0;
moel@348
   877
        for (var eventName in this._subscriptions) {
moel@348
   878
            if (this._subscriptions.hasOwnProperty(eventName))
moel@348
   879
                total += this._subscriptions[eventName].length;
moel@348
   880
        }
moel@348
   881
        return total;
moel@348
   882
    },
moel@348
   883
moel@348
   884
    extend: applyExtenders
moel@348
   885
};
moel@348
   886
moel@348
   887
moel@348
   888
ko.isSubscribable = function (instance) {
moel@348
   889
    return typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
moel@348
   890
};
moel@348
   891
moel@348
   892
ko.exportSymbol('subscribable', ko.subscribable);
moel@348
   893
ko.exportSymbol('isSubscribable', ko.isSubscribable);
moel@348
   894
moel@348
   895
ko.dependencyDetection = (function () {
moel@348
   896
    var _frames = [];
moel@348
   897
moel@348
   898
    return {
moel@348
   899
        begin: function (callback) {
moel@348
   900
            _frames.push({ callback: callback, distinctDependencies:[] });
moel@348
   901
        },
moel@348
   902
moel@348
   903
        end: function () {
moel@348
   904
            _frames.pop();
moel@348
   905
        },
moel@348
   906
moel@348
   907
        registerDependency: function (subscribable) {
moel@348
   908
            if (!ko.isSubscribable(subscribable))
moel@348
   909
                throw new Error("Only subscribable things can act as dependencies");
moel@348
   910
            if (_frames.length > 0) {
moel@348
   911
                var topFrame = _frames[_frames.length - 1];
moel@348
   912
                if (ko.utils.arrayIndexOf(topFrame.distinctDependencies, subscribable) >= 0)
moel@348
   913
                    return;
moel@348
   914
                topFrame.distinctDependencies.push(subscribable);
moel@348
   915
                topFrame.callback(subscribable);
moel@348
   916
            }
moel@348
   917
        }
moel@348
   918
    };
moel@348
   919
})();
moel@348
   920
var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true };
moel@348
   921
moel@348
   922
ko.observable = function (initialValue) {
moel@348
   923
    var _latestValue = initialValue;
moel@348
   924
moel@348
   925
    function observable() {
moel@348
   926
        if (arguments.length > 0) {
moel@348
   927
            // Write
moel@348
   928
moel@348
   929
            // Ignore writes if the value hasn't changed
moel@348
   930
            if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
moel@348
   931
                observable.valueWillMutate();
moel@348
   932
                _latestValue = arguments[0];
moel@348
   933
                if (DEBUG) observable._latestValue = _latestValue;
moel@348
   934
                observable.valueHasMutated();
moel@348
   935
            }
moel@348
   936
            return this; // Permits chained assignments
moel@348
   937
        }
moel@348
   938
        else {
moel@348
   939
            // Read
moel@348
   940
            ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
moel@348
   941
            return _latestValue;
moel@348
   942
        }
moel@348
   943
    }
moel@348
   944
    if (DEBUG) observable._latestValue = _latestValue;
moel@348
   945
    ko.subscribable.call(observable);
moel@348
   946
    observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }
moel@348
   947
    observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }
moel@348
   948
    ko.utils.extend(observable, ko.observable['fn']);
moel@348
   949
moel@348
   950
    ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
moel@348
   951
    ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
moel@348
   952
moel@348
   953
    return observable;
moel@348
   954
}
moel@348
   955
moel@348
   956
ko.observable['fn'] = {
moel@348
   957
    "equalityComparer": function valuesArePrimitiveAndEqual(a, b) {
moel@348
   958
        var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
moel@348
   959
        return oldValueIsPrimitive ? (a === b) : false;
moel@348
   960
    }
moel@348
   961
};
moel@348
   962
moel@348
   963
var protoProperty = ko.observable.protoProperty = "__ko_proto__";
moel@348
   964
ko.observable['fn'][protoProperty] = ko.observable;
moel@348
   965
moel@348
   966
ko.hasPrototype = function(instance, prototype) {
moel@348
   967
    if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
moel@348
   968
    if (instance[protoProperty] === prototype) return true;
moel@348
   969
    return ko.hasPrototype(instance[protoProperty], prototype); // Walk the prototype chain
moel@348
   970
};
moel@348
   971
moel@348
   972
ko.isObservable = function (instance) {
moel@348
   973
    return ko.hasPrototype(instance, ko.observable);
moel@348
   974
}
moel@348
   975
ko.isWriteableObservable = function (instance) {
moel@348
   976
    // Observable
moel@348
   977
    if ((typeof instance == "function") && instance[protoProperty] === ko.observable)
moel@348
   978
        return true;
moel@348
   979
    // Writeable dependent observable
moel@348
   980
    if ((typeof instance == "function") && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
moel@348
   981
        return true;
moel@348
   982
    // Anything else
moel@348
   983
    return false;
moel@348
   984
}
moel@348
   985
moel@348
   986
moel@348
   987
ko.exportSymbol('observable', ko.observable);
moel@348
   988
ko.exportSymbol('isObservable', ko.isObservable);
moel@348
   989
ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
moel@348
   990
ko.observableArray = function (initialValues) {
moel@348
   991
    if (arguments.length == 0) {
moel@348
   992
        // Zero-parameter constructor initializes to empty array
moel@348
   993
        initialValues = [];
moel@348
   994
    }
moel@348
   995
    if ((initialValues !== null) && (initialValues !== undefined) && !('length' in initialValues))
moel@348
   996
        throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
moel@348
   997
moel@348
   998
    var result = ko.observable(initialValues);
moel@348
   999
    ko.utils.extend(result, ko.observableArray['fn']);
moel@348
  1000
    return result;
moel@348
  1001
}
moel@348
  1002
moel@348
  1003
ko.observableArray['fn'] = {
moel@348
  1004
    'remove': function (valueOrPredicate) {
moel@348
  1005
        var underlyingArray = this();
moel@348
  1006
        var removedValues = [];
moel@348
  1007
        var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
moel@348
  1008
        for (var i = 0; i < underlyingArray.length; i++) {
moel@348
  1009
            var value = underlyingArray[i];
moel@348
  1010
            if (predicate(value)) {
moel@348
  1011
                if (removedValues.length === 0) {
moel@348
  1012
                    this.valueWillMutate();
moel@348
  1013
                }
moel@348
  1014
                removedValues.push(value);
moel@348
  1015
                underlyingArray.splice(i, 1);
moel@348
  1016
                i--;
moel@348
  1017
            }
moel@348
  1018
        }
moel@348
  1019
        if (removedValues.length) {
moel@348
  1020
            this.valueHasMutated();
moel@348
  1021
        }
moel@348
  1022
        return removedValues;
moel@348
  1023
    },
moel@348
  1024
moel@348
  1025
    'removeAll': function (arrayOfValues) {
moel@348
  1026
        // If you passed zero args, we remove everything
moel@348
  1027
        if (arrayOfValues === undefined) {
moel@348
  1028
            var underlyingArray = this();
moel@348
  1029
            var allValues = underlyingArray.slice(0);
moel@348
  1030
            this.valueWillMutate();
moel@348
  1031
            underlyingArray.splice(0, underlyingArray.length);
moel@348
  1032
            this.valueHasMutated();
moel@348
  1033
            return allValues;
moel@348
  1034
        }
moel@348
  1035
        // If you passed an arg, we interpret it as an array of entries to remove
moel@348
  1036
        if (!arrayOfValues)
moel@348
  1037
            return [];
moel@348
  1038
        return this['remove'](function (value) {
moel@348
  1039
            return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
moel@348
  1040
        });
moel@348
  1041
    },
moel@348
  1042
moel@348
  1043
    'destroy': function (valueOrPredicate) {
moel@348
  1044
        var underlyingArray = this();
moel@348
  1045
        var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
moel@348
  1046
        this.valueWillMutate();
moel@348
  1047
        for (var i = underlyingArray.length - 1; i >= 0; i--) {
moel@348
  1048
            var value = underlyingArray[i];
moel@348
  1049
            if (predicate(value))
moel@348
  1050
                underlyingArray[i]["_destroy"] = true;
moel@348
  1051
        }
moel@348
  1052
        this.valueHasMutated();
moel@348
  1053
    },
moel@348
  1054
moel@348
  1055
    'destroyAll': function (arrayOfValues) {
moel@348
  1056
        // If you passed zero args, we destroy everything
moel@348
  1057
        if (arrayOfValues === undefined)
moel@348
  1058
            return this['destroy'](function() { return true });
moel@348
  1059
moel@348
  1060
        // If you passed an arg, we interpret it as an array of entries to destroy
moel@348
  1061
        if (!arrayOfValues)
moel@348
  1062
            return [];
moel@348
  1063
        return this['destroy'](function (value) {
moel@348
  1064
            return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
moel@348
  1065
        });
moel@348
  1066
    },
moel@348
  1067
moel@348
  1068
    'indexOf': function (item) {
moel@348
  1069
        var underlyingArray = this();
moel@348
  1070
        return ko.utils.arrayIndexOf(underlyingArray, item);
moel@348
  1071
    },
moel@348
  1072
moel@348
  1073
    'replace': function(oldItem, newItem) {
moel@348
  1074
        var index = this['indexOf'](oldItem);
moel@348
  1075
        if (index >= 0) {
moel@348
  1076
            this.valueWillMutate();
moel@348
  1077
            this()[index] = newItem;
moel@348
  1078
            this.valueHasMutated();
moel@348
  1079
        }
moel@348
  1080
    }
moel@348
  1081
}
moel@348
  1082
moel@348
  1083
// Populate ko.observableArray.fn with read/write functions from native arrays
moel@348
  1084
ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
moel@348
  1085
    ko.observableArray['fn'][methodName] = function () {
moel@348
  1086
        var underlyingArray = this();
moel@348
  1087
        this.valueWillMutate();
moel@348
  1088
        var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
moel@348
  1089
        this.valueHasMutated();
moel@348
  1090
        return methodCallResult;
moel@348
  1091
    };
moel@348
  1092
});
moel@348
  1093
moel@348
  1094
// Populate ko.observableArray.fn with read-only functions from native arrays
moel@348
  1095
ko.utils.arrayForEach(["slice"], function (methodName) {
moel@348
  1096
    ko.observableArray['fn'][methodName] = function () {
moel@348
  1097
        var underlyingArray = this();
moel@348
  1098
        return underlyingArray[methodName].apply(underlyingArray, arguments);
moel@348
  1099
    };
moel@348
  1100
});
moel@348
  1101
moel@348
  1102
ko.exportSymbol('observableArray', ko.observableArray);
moel@348
  1103
ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
moel@348
  1104
    var _latestValue,
moel@348
  1105
        _hasBeenEvaluated = false,
moel@348
  1106
        _isBeingEvaluated = false,
moel@348
  1107
        readFunction = evaluatorFunctionOrOptions;
moel@348
  1108
moel@348
  1109
    if (readFunction && typeof readFunction == "object") {
moel@348
  1110
        // Single-parameter syntax - everything is on this "options" param
moel@348
  1111
        options = readFunction;
moel@348
  1112
        readFunction = options["read"];
moel@348
  1113
    } else {
moel@348
  1114
        // Multi-parameter syntax - construct the options according to the params passed
moel@348
  1115
        options = options || {};
moel@348
  1116
        if (!readFunction)
moel@348
  1117
            readFunction = options["read"];
moel@348
  1118
    }
moel@348
  1119
    // By here, "options" is always non-null
moel@348
  1120
    if (typeof readFunction != "function")
moel@348
  1121
        throw new Error("Pass a function that returns the value of the ko.computed");
moel@348
  1122
moel@348
  1123
    var writeFunction = options["write"];
moel@348
  1124
    if (!evaluatorFunctionTarget)
moel@348
  1125
        evaluatorFunctionTarget = options["owner"];
moel@348
  1126
moel@348
  1127
    var _subscriptionsToDependencies = [];
moel@348
  1128
    function disposeAllSubscriptionsToDependencies() {
moel@348
  1129
        ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) {
moel@348
  1130
            subscription.dispose();
moel@348
  1131
        });
moel@348
  1132
        _subscriptionsToDependencies = [];
moel@348
  1133
    }
moel@348
  1134
    var dispose = disposeAllSubscriptionsToDependencies;
moel@348
  1135
moel@348
  1136
    // Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values
moel@348
  1137
    // (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(),
moel@348
  1138
    // plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
moel@348
  1139
    var disposeWhenNodeIsRemoved = (typeof options["disposeWhenNodeIsRemoved"] == "object") ? options["disposeWhenNodeIsRemoved"] : null;
moel@348
  1140
    var disposeWhen = options["disposeWhen"] || function() { return false; };
moel@348
  1141
    if (disposeWhenNodeIsRemoved) {
moel@348
  1142
        dispose = function() {
moel@348
  1143
            ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, arguments.callee);
moel@348
  1144
            disposeAllSubscriptionsToDependencies();
moel@348
  1145
        };
moel@348
  1146
        ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
moel@348
  1147
        var existingDisposeWhenFunction = disposeWhen;
moel@348
  1148
        disposeWhen = function () {
moel@348
  1149
            return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || existingDisposeWhenFunction();
moel@348
  1150
        }
moel@348
  1151
    }
moel@348
  1152
moel@348
  1153
    var evaluationTimeoutInstance = null;
moel@348
  1154
    function evaluatePossiblyAsync() {
moel@348
  1155
        var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
moel@348
  1156
        if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
moel@348
  1157
            clearTimeout(evaluationTimeoutInstance);
moel@348
  1158
            evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
moel@348
  1159
        } else
moel@348
  1160
            evaluateImmediate();
moel@348
  1161
    }
moel@348
  1162
moel@348
  1163
    function evaluateImmediate() {
moel@348
  1164
        if (_isBeingEvaluated) {
moel@348
  1165
            // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
moel@348
  1166
            // This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
moel@348
  1167
            // certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
moel@348
  1168
            // their own re-evaluation. Further discussion at https://github.com/SteveSanderson/knockout/pull/387
moel@348
  1169
            return;
moel@348
  1170
        }
moel@348
  1171
moel@348
  1172
        // Don't dispose on first evaluation, because the "disposeWhen" callback might
moel@348
  1173
        // e.g., dispose when the associated DOM element isn't in the doc, and it's not
moel@348
  1174
        // going to be in the doc until *after* the first evaluation
moel@348
  1175
        if (_hasBeenEvaluated && disposeWhen()) {
moel@348
  1176
            dispose();
moel@348
  1177
            return;
moel@348
  1178
        }
moel@348
  1179
moel@348
  1180
        _isBeingEvaluated = true;
moel@348
  1181
        try {
moel@348
  1182
            // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
moel@348
  1183
            // Then, during evaluation, we cross off any that are in fact still being used.
moel@348
  1184
            var disposalCandidates = ko.utils.arrayMap(_subscriptionsToDependencies, function(item) {return item.target;});
moel@348
  1185
moel@348
  1186
            ko.dependencyDetection.begin(function(subscribable) {
moel@348
  1187
                var inOld;
moel@348
  1188
                if ((inOld = ko.utils.arrayIndexOf(disposalCandidates, subscribable)) >= 0)
moel@348
  1189
                    disposalCandidates[inOld] = undefined; // Don't want to dispose this subscription, as it's still being used
moel@348
  1190
                else
moel@348
  1191
                    _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync)); // Brand new subscription - add it
moel@348
  1192
            });
moel@348
  1193
moel@348
  1194
            var newValue = readFunction.call(evaluatorFunctionTarget);
moel@348
  1195
moel@348
  1196
            // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
moel@348
  1197
            for (var i = disposalCandidates.length - 1; i >= 0; i--) {
moel@348
  1198
                if (disposalCandidates[i])
moel@348
  1199
                    _subscriptionsToDependencies.splice(i, 1)[0].dispose();
moel@348
  1200
            }
moel@348
  1201
            _hasBeenEvaluated = true;
moel@348
  1202
moel@348
  1203
            dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
moel@348
  1204
            _latestValue = newValue;
moel@348
  1205
            if (DEBUG) dependentObservable._latestValue = _latestValue;
moel@348
  1206
        } finally {
moel@348
  1207
            ko.dependencyDetection.end();
moel@348
  1208
        }
moel@348
  1209
moel@348
  1210
        dependentObservable["notifySubscribers"](_latestValue);
moel@348
  1211
        _isBeingEvaluated = false;
moel@348
  1212
moel@348
  1213
    }
moel@348
  1214
moel@348
  1215
    function dependentObservable() {
moel@348
  1216
        if (arguments.length > 0) {
moel@348
  1217
            set.apply(dependentObservable, arguments);
moel@348
  1218
        } else {
moel@348
  1219
            return get();
moel@348
  1220
        }
moel@348
  1221
    }
moel@348
  1222
moel@348
  1223
    function set() {
moel@348
  1224
        if (typeof writeFunction === "function") {
moel@348
  1225
            // Writing a value
moel@348
  1226
            writeFunction.apply(evaluatorFunctionTarget, arguments);
moel@348
  1227
        } else {
moel@348
  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.");
moel@348
  1229
        }
moel@348
  1230
    }
moel@348
  1231
moel@348
  1232
    function get() {
moel@348
  1233
        // Reading the value
moel@348
  1234
        if (!_hasBeenEvaluated)
moel@348
  1235
            evaluateImmediate();
moel@348
  1236
        ko.dependencyDetection.registerDependency(dependentObservable);
moel@348
  1237
        return _latestValue;
moel@348
  1238
    }
moel@348
  1239
moel@348
  1240
    dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; };
moel@348
  1241
    dependentObservable.hasWriteFunction = typeof options["write"] === "function";
moel@348
  1242
    dependentObservable.dispose = function () { dispose(); };
moel@348
  1243
moel@348
  1244
    ko.subscribable.call(dependentObservable);
moel@348
  1245
    ko.utils.extend(dependentObservable, ko.dependentObservable['fn']);
moel@348
  1246
moel@348
  1247
    if (options['deferEvaluation'] !== true)
moel@348
  1248
        evaluateImmediate();
moel@348
  1249
moel@348
  1250
    ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
moel@348
  1251
    ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount);
moel@348
  1252
moel@348
  1253
    return dependentObservable;
moel@348
  1254
};
moel@348
  1255
moel@348
  1256
ko.isComputed = function(instance) {
moel@348
  1257
    return ko.hasPrototype(instance, ko.dependentObservable);
moel@348
  1258
};
moel@348
  1259
moel@348
  1260
var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
moel@348
  1261
ko.dependentObservable[protoProp] = ko.observable;
moel@348
  1262
moel@348
  1263
ko.dependentObservable['fn'] = {};
moel@348
  1264
ko.dependentObservable['fn'][protoProp] = ko.dependentObservable;
moel@348
  1265
moel@348
  1266
ko.exportSymbol('dependentObservable', ko.dependentObservable);
moel@348
  1267
ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
moel@348
  1268
ko.exportSymbol('isComputed', ko.isComputed);
moel@348
  1269
moel@348
  1270
(function() {
moel@348
  1271
    var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
moel@348
  1272
moel@348
  1273
    ko.toJS = function(rootObject) {
moel@348
  1274
        if (arguments.length == 0)
moel@348
  1275
            throw new Error("When calling ko.toJS, pass the object you want to convert.");
moel@348
  1276
moel@348
  1277
        // We just unwrap everything at every level in the object graph
moel@348
  1278
        return mapJsObjectGraph(rootObject, function(valueToMap) {
moel@348
  1279
            // Loop because an observable's value might in turn be another observable wrapper
moel@348
  1280
            for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++)
moel@348
  1281
                valueToMap = valueToMap();
moel@348
  1282
            return valueToMap;
moel@348
  1283
        });
moel@348
  1284
    };
moel@348
  1285
moel@348
  1286
    ko.toJSON = function(rootObject, replacer, space) {     // replacer and space are optional
moel@348
  1287
        var plainJavaScriptObject = ko.toJS(rootObject);
moel@348
  1288
        return ko.utils.stringifyJson(plainJavaScriptObject, replacer, space);
moel@348
  1289
    };
moel@348
  1290
moel@348
  1291
    function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
moel@348
  1292
        visitedObjects = visitedObjects || new objectLookup();
moel@348
  1293
moel@348
  1294
        rootObject = mapInputCallback(rootObject);
moel@348
  1295
        var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date));
moel@348
  1296
        if (!canHaveProperties)
moel@348
  1297
            return rootObject;
moel@348
  1298
moel@348
  1299
        var outputProperties = rootObject instanceof Array ? [] : {};
moel@348
  1300
        visitedObjects.save(rootObject, outputProperties);
moel@348
  1301
moel@348
  1302
        visitPropertiesOrArrayEntries(rootObject, function(indexer) {
moel@348
  1303
            var propertyValue = mapInputCallback(rootObject[indexer]);
moel@348
  1304
moel@348
  1305
            switch (typeof propertyValue) {
moel@348
  1306
                case "boolean":
moel@348
  1307
                case "number":
moel@348
  1308
                case "string":
moel@348
  1309
                case "function":
moel@348
  1310
                    outputProperties[indexer] = propertyValue;
moel@348
  1311
                    break;
moel@348
  1312
                case "object":
moel@348
  1313
                case "undefined":
moel@348
  1314
                    var previouslyMappedValue = visitedObjects.get(propertyValue);
moel@348
  1315
                    outputProperties[indexer] = (previouslyMappedValue !== undefined)
moel@348
  1316
                        ? previouslyMappedValue
moel@348
  1317
                        : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects);
moel@348
  1318
                    break;
moel@348
  1319
            }
moel@348
  1320
        });
moel@348
  1321
moel@348
  1322
        return outputProperties;
moel@348
  1323
    }
moel@348
  1324
moel@348
  1325
    function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
moel@348
  1326
        if (rootObject instanceof Array) {
moel@348
  1327
            for (var i = 0; i < rootObject.length; i++)
moel@348
  1328
                visitorCallback(i);
moel@348
  1329
moel@348
  1330
            // For arrays, also respect toJSON property for custom mappings (fixes #278)
moel@348
  1331
            if (typeof rootObject['toJSON'] == 'function')
moel@348
  1332
                visitorCallback('toJSON');
moel@348
  1333
        } else {
moel@348
  1334
            for (var propertyName in rootObject)
moel@348
  1335
                visitorCallback(propertyName);
moel@348
  1336
        }
moel@348
  1337
    };
moel@348
  1338
moel@348
  1339
    function objectLookup() {
moel@348
  1340
        var keys = [];
moel@348
  1341
        var values = [];
moel@348
  1342
        this.save = function(key, value) {
moel@348
  1343
            var existingIndex = ko.utils.arrayIndexOf(keys, key);
moel@348
  1344
            if (existingIndex >= 0)
moel@348
  1345
                values[existingIndex] = value;
moel@348
  1346
            else {
moel@348
  1347
                keys.push(key);
moel@348
  1348
                values.push(value);
moel@348
  1349
            }
moel@348
  1350
        };
moel@348
  1351
        this.get = function(key) {
moel@348
  1352
            var existingIndex = ko.utils.arrayIndexOf(keys, key);
moel@348
  1353
            return (existingIndex >= 0) ? values[existingIndex] : undefined;
moel@348
  1354
        };
moel@348
  1355
    };
moel@348
  1356
})();
moel@348
  1357
moel@348
  1358
ko.exportSymbol('toJS', ko.toJS);
moel@348
  1359
ko.exportSymbol('toJSON', ko.toJSON);
moel@348
  1360
(function () {
moel@348
  1361
    var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
moel@348
  1362
moel@348
  1363
    // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
moel@348
  1364
    // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
moel@348
  1365
    // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
moel@348
  1366
    ko.selectExtensions = {
moel@348
  1367
        readValue : function(element) {
moel@348
  1368
            switch (ko.utils.tagNameLower(element)) {
moel@348
  1369
                case 'option':
moel@348
  1370
                    if (element[hasDomDataExpandoProperty] === true)
moel@348
  1371
                        return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
moel@348
  1372
                    return element.getAttribute("value");
moel@348
  1373
                case 'select':
moel@348
  1374
                    return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
moel@348
  1375
                default:
moel@348
  1376
                    return element.value;
moel@348
  1377
            }
moel@348
  1378
        },
moel@348
  1379
moel@348
  1380
        writeValue: function(element, value) {
moel@348
  1381
            switch (ko.utils.tagNameLower(element)) {
moel@348
  1382
                case 'option':
moel@348
  1383
                    switch(typeof value) {
moel@348
  1384
                        case "string":
moel@348
  1385
                            ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
moel@348
  1386
                            if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
moel@348
  1387
                                delete element[hasDomDataExpandoProperty];
moel@348
  1388
                            }
moel@348
  1389
                            element.value = value;
moel@348
  1390
                            break;
moel@348
  1391
                        default:
moel@348
  1392
                            // Store arbitrary object using DomData
moel@348
  1393
                            ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
moel@348
  1394
                            element[hasDomDataExpandoProperty] = true;
moel@348
  1395
moel@348
  1396
                            // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
moel@348
  1397
                            element.value = typeof value === "number" ? value : "";
moel@348
  1398
                            break;
moel@348
  1399
                    }
moel@348
  1400
                    break;
moel@348
  1401
                case 'select':
moel@348
  1402
                    for (var i = element.options.length - 1; i >= 0; i--) {
moel@348
  1403
                        if (ko.selectExtensions.readValue(element.options[i]) == value) {
moel@348
  1404
                            element.selectedIndex = i;
moel@348
  1405
                            break;
moel@348
  1406
                        }
moel@348
  1407
                    }
moel@348
  1408
                    break;
moel@348
  1409
                default:
moel@348
  1410
                    if ((value === null) || (value === undefined))
moel@348
  1411
                        value = "";
moel@348
  1412
                    element.value = value;
moel@348
  1413
                    break;
moel@348
  1414
            }
moel@348
  1415
        }
moel@348
  1416
    };
moel@348
  1417
})();
moel@348
  1418
moel@348
  1419
ko.exportSymbol('selectExtensions', ko.selectExtensions);
moel@348
  1420
ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
moel@348
  1421
ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
moel@348
  1422
moel@348
  1423
ko.jsonExpressionRewriting = (function () {
moel@348
  1424
    var restoreCapturedTokensRegex = /\@ko_token_(\d+)\@/g;
moel@348
  1425
    var javaScriptAssignmentTarget = /^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i;
moel@348
  1426
    var javaScriptReservedWords = ["true", "false"];
moel@348
  1427
moel@348
  1428
    function restoreTokens(string, tokens) {
moel@348
  1429
        var prevValue = null;
moel@348
  1430
        while (string != prevValue) { // Keep restoring tokens until it no longer makes a difference (they may be nested)
moel@348
  1431
            prevValue = string;
moel@348
  1432
            string = string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) {
moel@348
  1433
                return tokens[tokenIndex];
moel@348
  1434
            });
moel@348
  1435
        }
moel@348
  1436
        return string;
moel@348
  1437
    }
moel@348
  1438
moel@348
  1439
    function isWriteableValue(expression) {
moel@348
  1440
        if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0)
moel@348
  1441
            return false;
moel@348
  1442
        return expression.match(javaScriptAssignmentTarget) !== null;
moel@348
  1443
    }
moel@348
  1444
moel@348
  1445
    function ensureQuoted(key) {
moel@348
  1446
        var trimmedKey = ko.utils.stringTrim(key);
moel@348
  1447
        switch (trimmedKey.length && trimmedKey.charAt(0)) {
moel@348
  1448
            case "'":
moel@348
  1449
            case '"':
moel@348
  1450
                return key;
moel@348
  1451
            default:
moel@348
  1452
                return "'" + trimmedKey + "'";
moel@348
  1453
        }
moel@348
  1454
    }
moel@348
  1455
moel@348
  1456
    return {
moel@348
  1457
        bindingRewriteValidators: [],
moel@348
  1458
moel@348
  1459
        parseObjectLiteral: function(objectLiteralString) {
moel@348
  1460
            // A full tokeniser+lexer would add too much weight to this library, so here's a simple parser
moel@348
  1461
            // that is sufficient just to split an object literal string into a set of top-level key-value pairs
moel@348
  1462
moel@348
  1463
            var str = ko.utils.stringTrim(objectLiteralString);
moel@348
  1464
            if (str.length < 3)
moel@348
  1465
                return [];
moel@348
  1466
            if (str.charAt(0) === "{")// Ignore any braces surrounding the whole object literal
moel@348
  1467
                str = str.substring(1, str.length - 1);
moel@348
  1468
moel@348
  1469
            // Pull out any string literals and regex literals
moel@348
  1470
            var tokens = [];
moel@348
  1471
            var tokenStart = null, tokenEndChar;
moel@348
  1472
            for (var position = 0; position < str.length; position++) {
moel@348
  1473
                var c = str.charAt(position);
moel@348
  1474
                if (tokenStart === null) {
moel@348
  1475
                    switch (c) {
moel@348
  1476
                        case '"':
moel@348
  1477
                        case "'":
moel@348
  1478
                        case "/":
moel@348
  1479
                            tokenStart = position;
moel@348
  1480
                            tokenEndChar = c;
moel@348
  1481
                            break;
moel@348
  1482
                    }
moel@348
  1483
                } else if ((c == tokenEndChar) && (str.charAt(position - 1) !== "\\")) {
moel@348
  1484
                    var token = str.substring(tokenStart, position + 1);
moel@348
  1485
                    tokens.push(token);
moel@348
  1486
                    var replacement = "@ko_token_" + (tokens.length - 1) + "@";
moel@348
  1487
                    str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
moel@348
  1488
                    position -= (token.length - replacement.length);
moel@348
  1489
                    tokenStart = null;
moel@348
  1490
                }
moel@348
  1491
            }
moel@348
  1492
moel@348
  1493
            // Next pull out balanced paren, brace, and bracket blocks
moel@348
  1494
            tokenStart = null;
moel@348
  1495
            tokenEndChar = null;
moel@348
  1496
            var tokenDepth = 0, tokenStartChar = null;
moel@348
  1497
            for (var position = 0; position < str.length; position++) {
moel@348
  1498
                var c = str.charAt(position);
moel@348
  1499
                if (tokenStart === null) {
moel@348
  1500
                    switch (c) {
moel@348
  1501
                        case "{": tokenStart = position; tokenStartChar = c;
moel@348
  1502
                                  tokenEndChar = "}";
moel@348
  1503
                                  break;
moel@348
  1504
                        case "(": tokenStart = position; tokenStartChar = c;
moel@348
  1505
                                  tokenEndChar = ")";
moel@348
  1506
                                  break;
moel@348
  1507
                        case "[": tokenStart = position; tokenStartChar = c;
moel@348
  1508
                                  tokenEndChar = "]";
moel@348
  1509
                                  break;
moel@348
  1510
                    }
moel@348
  1511
                }
moel@348
  1512
moel@348
  1513
                if (c === tokenStartChar)
moel@348
  1514
                    tokenDepth++;
moel@348
  1515
                else if (c === tokenEndChar) {
moel@348
  1516
                    tokenDepth--;
moel@348
  1517
                    if (tokenDepth === 0) {
moel@348
  1518
                        var token = str.substring(tokenStart, position + 1);
moel@348
  1519
                        tokens.push(token);
moel@348
  1520
                        var replacement = "@ko_token_" + (tokens.length - 1) + "@";
moel@348
  1521
                        str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
moel@348
  1522
                        position -= (token.length - replacement.length);
moel@348
  1523
                        tokenStart = null;
moel@348
  1524
                    }
moel@348
  1525
                }
moel@348
  1526
            }
moel@348
  1527
moel@348
  1528
            // Now we can safely split on commas to get the key/value pairs
moel@348
  1529
            var result = [];
moel@348
  1530
            var keyValuePairs = str.split(",");
moel@348
  1531
            for (var i = 0, j = keyValuePairs.length; i < j; i++) {
moel@348
  1532
                var pair = keyValuePairs[i];
moel@348
  1533
                var colonPos = pair.indexOf(":");
moel@348
  1534
                if ((colonPos > 0) && (colonPos < pair.length - 1)) {
moel@348
  1535
                    var key = pair.substring(0, colonPos);
moel@348
  1536
                    var value = pair.substring(colonPos + 1);
moel@348
  1537
                    result.push({ 'key': restoreTokens(key, tokens), 'value': restoreTokens(value, tokens) });
moel@348
  1538
                } else {
moel@348
  1539
                    result.push({ 'unknown': restoreTokens(pair, tokens) });
moel@348
  1540
                }
moel@348
  1541
            }
moel@348
  1542
            return result;
moel@348
  1543
        },
moel@348
  1544
moel@348
  1545
        insertPropertyAccessorsIntoJson: function (objectLiteralStringOrKeyValueArray) {
moel@348
  1546
            var keyValueArray = typeof objectLiteralStringOrKeyValueArray === "string"
moel@348
  1547
                ? ko.jsonExpressionRewriting.parseObjectLiteral(objectLiteralStringOrKeyValueArray)
moel@348
  1548
                : objectLiteralStringOrKeyValueArray;
moel@348
  1549
            var resultStrings = [], propertyAccessorResultStrings = [];
moel@348
  1550
moel@348
  1551
            var keyValueEntry;
moel@348
  1552
            for (var i = 0; keyValueEntry = keyValueArray[i]; i++) {
moel@348
  1553
                if (resultStrings.length > 0)
moel@348
  1554
                    resultStrings.push(",");
moel@348
  1555
moel@348
  1556
                if (keyValueEntry['key']) {
moel@348
  1557
                    var quotedKey = ensureQuoted(keyValueEntry['key']), val = keyValueEntry['value'];
moel@348
  1558
                    resultStrings.push(quotedKey);
moel@348
  1559
                    resultStrings.push(":");
moel@348
  1560
                    resultStrings.push(val);
moel@348
  1561
moel@348
  1562
                    if (isWriteableValue(ko.utils.stringTrim(val))) {
moel@348
  1563
                        if (propertyAccessorResultStrings.length > 0)
moel@348
  1564
                            propertyAccessorResultStrings.push(", ");
moel@348
  1565
                        propertyAccessorResultStrings.push(quotedKey + " : function(__ko_value) { " + val + " = __ko_value; }");
moel@348
  1566
                    }
moel@348
  1567
                } else if (keyValueEntry['unknown']) {
moel@348
  1568
                    resultStrings.push(keyValueEntry['unknown']);
moel@348
  1569
                }
moel@348
  1570
            }
moel@348
  1571
moel@348
  1572
            var combinedResult = resultStrings.join("");
moel@348
  1573
            if (propertyAccessorResultStrings.length > 0) {
moel@348
  1574
                var allPropertyAccessors = propertyAccessorResultStrings.join("");
moel@348
  1575
                combinedResult = combinedResult + ", '_ko_property_writers' : { " + allPropertyAccessors + " } ";
moel@348
  1576
            }
moel@348
  1577
moel@348
  1578
            return combinedResult;
moel@348
  1579
        },
moel@348
  1580
moel@348
  1581
        keyValueArrayContainsKey: function(keyValueArray, key) {
moel@348
  1582
            for (var i = 0; i < keyValueArray.length; i++)
moel@348
  1583
                if (ko.utils.stringTrim(keyValueArray[i]['key']) == key)
moel@348
  1584
                    return true;
moel@348
  1585
            return false;
moel@348
  1586
        },
moel@348
  1587
moel@348
  1588
        // Internal, private KO utility for updating model properties from within bindings
moel@348
  1589
        // property:            If the property being updated is (or might be) an observable, pass it here
moel@348
  1590
        //                      If it turns out to be a writable observable, it will be written to directly
moel@348
  1591
        // allBindingsAccessor: All bindings in the current execution context.
moel@348
  1592
        //                      This will be searched for a '_ko_property_writers' property in case you're writing to a non-observable
moel@348
  1593
        // key:                 The key identifying the property to be written. Example: for { hasFocus: myValue }, write to 'myValue' by specifying the key 'hasFocus'
moel@348
  1594
        // value:               The value to be written
moel@348
  1595
        // checkIfDifferent:    If true, and if the property being written is a writable observable, the value will only be written if
moel@348
  1596
        //                      it is !== existing value on that writable observable
moel@348
  1597
        writeValueToProperty: function(property, allBindingsAccessor, key, value, checkIfDifferent) {
moel@348
  1598
            if (!property || !ko.isWriteableObservable(property)) {
moel@348
  1599
                var propWriters = allBindingsAccessor()['_ko_property_writers'];
moel@348
  1600
                if (propWriters && propWriters[key])
moel@348
  1601
                    propWriters[key](value);
moel@348
  1602
            } else if (!checkIfDifferent || property() !== value) {
moel@348
  1603
                property(value);
moel@348
  1604
            }
moel@348
  1605
        }
moel@348
  1606
    };
moel@348
  1607
})();
moel@348
  1608
moel@348
  1609
ko.exportSymbol('jsonExpressionRewriting', ko.jsonExpressionRewriting);
moel@348
  1610
ko.exportSymbol('jsonExpressionRewriting.bindingRewriteValidators', ko.jsonExpressionRewriting.bindingRewriteValidators);
moel@348
  1611
ko.exportSymbol('jsonExpressionRewriting.parseObjectLiteral', ko.jsonExpressionRewriting.parseObjectLiteral);
moel@348
  1612
ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson);
moel@348
  1613
(function() {
moel@348
  1614
    // "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
moel@348
  1615
    // may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
moel@348
  1616
    // If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
moel@348
  1617
    // of that virtual hierarchy
moel@348
  1618
    //
moel@348
  1619
    // The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
moel@348
  1620
    // without having to scatter special cases all over the binding and templating code.
moel@348
  1621
moel@348
  1622
    // IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
moel@348
  1623
    // but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
moel@348
  1624
    // So, use node.text where available, and node.nodeValue elsewhere
moel@348
  1625
    var commentNodesHaveTextProperty = document.createComment("test").text === "<!--test-->";
moel@348
  1626
moel@348
  1627
    var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko\s+(.*\:.*)\s*-->$/ : /^\s*ko\s+(.*\:.*)\s*$/;
moel@348
  1628
    var endCommentRegex =   commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
moel@348
  1629
    var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
moel@348
  1630
moel@348
  1631
    function isStartComment(node) {
moel@348
  1632
        return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
moel@348
  1633
    }
moel@348
  1634
moel@348
  1635
    function isEndComment(node) {
moel@348
  1636
        return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(endCommentRegex);
moel@348
  1637
    }
moel@348
  1638
moel@348
  1639
    function getVirtualChildren(startComment, allowUnbalanced) {
moel@348
  1640
        var currentNode = startComment;
moel@348
  1641
        var depth = 1;
moel@348
  1642
        var children = [];
moel@348
  1643
        while (currentNode = currentNode.nextSibling) {
moel@348
  1644
            if (isEndComment(currentNode)) {
moel@348
  1645
                depth--;
moel@348
  1646
                if (depth === 0)
moel@348
  1647
                    return children;
moel@348
  1648
            }
moel@348
  1649
moel@348
  1650
            children.push(currentNode);
moel@348
  1651
moel@348
  1652
            if (isStartComment(currentNode))
moel@348
  1653
                depth++;
moel@348
  1654
        }
moel@348
  1655
        if (!allowUnbalanced)
moel@348
  1656
            throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
moel@348
  1657
        return null;
moel@348
  1658
    }
moel@348
  1659
moel@348
  1660
    function getMatchingEndComment(startComment, allowUnbalanced) {
moel@348
  1661
        var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
moel@348
  1662
        if (allVirtualChildren) {
moel@348
  1663
            if (allVirtualChildren.length > 0)
moel@348
  1664
                return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
moel@348
  1665
            return startComment.nextSibling;
moel@348
  1666
        } else
moel@348
  1667
            return null; // Must have no matching end comment, and allowUnbalanced is true
moel@348
  1668
    }
moel@348
  1669
moel@348
  1670
    function getUnbalancedChildTags(node) {
moel@348
  1671
        // e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span>
moel@348
  1672
        //       from <div>OK</div><!-- /ko --><!-- /ko -->,             returns: <!-- /ko --><!-- /ko -->
moel@348
  1673
        var childNode = node.firstChild, captureRemaining = null;
moel@348
  1674
        if (childNode) {
moel@348
  1675
            do {
moel@348
  1676
                if (captureRemaining)                   // We already hit an unbalanced node and are now just scooping up all subsequent nodes
moel@348
  1677
                    captureRemaining.push(childNode);
moel@348
  1678
                else if (isStartComment(childNode)) {
moel@348
  1679
                    var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true);
moel@348
  1680
                    if (matchingEndComment)             // It's a balanced tag, so skip immediately to the end of this virtual set
moel@348
  1681
                        childNode = matchingEndComment;
moel@348
  1682
                    else
moel@348
  1683
                        captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point
moel@348
  1684
                } else if (isEndComment(childNode)) {
moel@348
  1685
                    captureRemaining = [childNode];     // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing
moel@348
  1686
                }
moel@348
  1687
            } while (childNode = childNode.nextSibling);
moel@348
  1688
        }
moel@348
  1689
        return captureRemaining;
moel@348
  1690
    }
moel@348
  1691
moel@348
  1692
    ko.virtualElements = {
moel@348
  1693
        allowedBindings: {},
moel@348
  1694
moel@348
  1695
        childNodes: function(node) {
moel@348
  1696
            return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
moel@348
  1697
        },
moel@348
  1698
moel@348
  1699
        emptyNode: function(node) {
moel@348
  1700
            if (!isStartComment(node))
moel@348
  1701
                ko.utils.emptyDomNode(node);
moel@348
  1702
            else {
moel@348
  1703
                var virtualChildren = ko.virtualElements.childNodes(node);
moel@348
  1704
                for (var i = 0, j = virtualChildren.length; i < j; i++)
moel@348
  1705
                    ko.removeNode(virtualChildren[i]);
moel@348
  1706
            }
moel@348
  1707
        },
moel@348
  1708
moel@348
  1709
        setDomNodeChildren: function(node, childNodes) {
moel@348
  1710
            if (!isStartComment(node))
moel@348
  1711
                ko.utils.setDomNodeChildren(node, childNodes);
moel@348
  1712
            else {
moel@348
  1713
                ko.virtualElements.emptyNode(node);
moel@348
  1714
                var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
moel@348
  1715
                for (var i = 0, j = childNodes.length; i < j; i++)
moel@348
  1716
                    endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
moel@348
  1717
            }
moel@348
  1718
        },
moel@348
  1719
moel@348
  1720
        prepend: function(containerNode, nodeToPrepend) {
moel@348
  1721
            if (!isStartComment(containerNode)) {
moel@348
  1722
                if (containerNode.firstChild)
moel@348
  1723
                    containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
moel@348
  1724
                else
moel@348
  1725
                    containerNode.appendChild(nodeToPrepend);
moel@348
  1726
            } else {
moel@348
  1727
                // Start comments must always have a parent and at least one following sibling (the end comment)
moel@348
  1728
                containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
moel@348
  1729
            }
moel@348
  1730
        },
moel@348
  1731
moel@348
  1732
        insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
moel@348
  1733
            if (!isStartComment(containerNode)) {
moel@348
  1734
                // Insert after insertion point
moel@348
  1735
                if (insertAfterNode.nextSibling)
moel@348
  1736
                    containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
moel@348
  1737
                else
moel@348
  1738
                    containerNode.appendChild(nodeToInsert);
moel@348
  1739
            } else {
moel@348
  1740
                // Children of start comments must always have a parent and at least one following sibling (the end comment)
moel@348
  1741
                containerNode.parentNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
moel@348
  1742
            }
moel@348
  1743
        },
moel@348
  1744
moel@348
  1745
        firstChild: function(node) {
moel@348
  1746
            if (!isStartComment(node))
moel@348
  1747
                return node.firstChild;
moel@348
  1748
            if (!node.nextSibling || isEndComment(node.nextSibling))
moel@348
  1749
                return null;
moel@348
  1750
            return node.nextSibling;
moel@348
  1751
        },
moel@348
  1752
moel@348
  1753
        nextSibling: function(node) {
moel@348
  1754
            if (isStartComment(node))
moel@348
  1755
                node = getMatchingEndComment(node);
moel@348
  1756
            if (node.nextSibling && isEndComment(node.nextSibling))
moel@348
  1757
                return null;
moel@348
  1758
            return node.nextSibling;
moel@348
  1759
        },
moel@348
  1760
moel@348
  1761
        virtualNodeBindingValue: function(node) {
moel@348
  1762
            var regexMatch = isStartComment(node);
moel@348
  1763
            return regexMatch ? regexMatch[1] : null;
moel@348
  1764
        },
moel@348
  1765
moel@348
  1766
        normaliseVirtualElementDomStructure: function(elementVerified) {
moel@348
  1767
            // Workaround for https://github.com/SteveSanderson/knockout/issues/155
moel@348
  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
moel@348
  1769
            // that are direct descendants of <ul> into the preceding <li>)
moel@348
  1770
            if (!htmlTagsWithOptionallyClosingChildren[ko.utils.tagNameLower(elementVerified)])
moel@348
  1771
                return;
moel@348
  1772
moel@348
  1773
            // Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags
moel@348
  1774
            // must be intended to appear *after* that child, so move them there.
moel@348
  1775
            var childNode = elementVerified.firstChild;
moel@348
  1776
            if (childNode) {
moel@348
  1777
                do {
moel@348
  1778
                    if (childNode.nodeType === 1) {
moel@348
  1779
                        var unbalancedTags = getUnbalancedChildTags(childNode);
moel@348
  1780
                        if (unbalancedTags) {
moel@348
  1781
                            // Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child
moel@348
  1782
                            var nodeToInsertBefore = childNode.nextSibling;
moel@348
  1783
                            for (var i = 0; i < unbalancedTags.length; i++) {
moel@348
  1784
                                if (nodeToInsertBefore)
moel@348
  1785
                                    elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore);
moel@348
  1786
                                else
moel@348
  1787
                                    elementVerified.appendChild(unbalancedTags[i]);
moel@348
  1788
                            }
moel@348
  1789
                        }
moel@348
  1790
                    }
moel@348
  1791
                } while (childNode = childNode.nextSibling);
moel@348
  1792
            }
moel@348
  1793
        }
moel@348
  1794
    };
moel@348
  1795
})();
moel@348
  1796
ko.exportSymbol('virtualElements', ko.virtualElements);
moel@348
  1797
ko.exportSymbol('virtualElements.allowedBindings', ko.virtualElements.allowedBindings);
moel@348
  1798
ko.exportSymbol('virtualElements.emptyNode', ko.virtualElements.emptyNode);
moel@348
  1799
//ko.exportSymbol('virtualElements.firstChild', ko.virtualElements.firstChild);     // firstChild is not minified
moel@348
  1800
ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter);
moel@348
  1801
//ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling);   // nextSibling is not minified
moel@348
  1802
ko.exportSymbol('virtualElements.prepend', ko.virtualElements.prepend);
moel@348
  1803
ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomNodeChildren);
moel@348
  1804
(function() {
moel@348
  1805
    var defaultBindingAttributeName = "data-bind";
moel@348
  1806
moel@348
  1807
    ko.bindingProvider = function() {
moel@348
  1808
        this.bindingCache = {};
moel@348
  1809
    };
moel@348
  1810
moel@348
  1811
    ko.utils.extend(ko.bindingProvider.prototype, {
moel@348
  1812
        'nodeHasBindings': function(node) {
moel@348
  1813
            switch (node.nodeType) {
moel@348
  1814
                case 1: return node.getAttribute(defaultBindingAttributeName) != null;   // Element
moel@348
  1815
                case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node
moel@348
  1816
                default: return false;
moel@348
  1817
            }
moel@348
  1818
        },
moel@348
  1819
moel@348
  1820
        'getBindings': function(node, bindingContext) {
moel@348
  1821
            var bindingsString = this['getBindingsString'](node, bindingContext);
moel@348
  1822
            return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext) : null;
moel@348
  1823
        },
moel@348
  1824
moel@348
  1825
        // The following function is only used internally by this default provider.
moel@348
  1826
        // It's not part of the interface definition for a general binding provider.
moel@348
  1827
        'getBindingsString': function(node, bindingContext) {
moel@348
  1828
            switch (node.nodeType) {
moel@348
  1829
                case 1: return node.getAttribute(defaultBindingAttributeName);   // Element
moel@348
  1830
                case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
moel@348
  1831
                default: return null;
moel@348
  1832
            }
moel@348
  1833
        },
moel@348
  1834
moel@348
  1835
        // The following function is only used internally by this default provider.
moel@348
  1836
        // It's not part of the interface definition for a general binding provider.
moel@348
  1837
        'parseBindingsString': function(bindingsString, bindingContext) {
moel@348
  1838
            try {
moel@348
  1839
                var viewModel = bindingContext['$data'],
moel@348
  1840
                    scopes = (typeof viewModel == 'object' && viewModel != null) ? [viewModel, bindingContext] : [bindingContext],
moel@348
  1841
                    bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, scopes.length, this.bindingCache);
moel@348
  1842
                return bindingFunction(scopes);
moel@348
  1843
            } catch (ex) {
moel@348
  1844
                throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
moel@348
  1845
            }
moel@348
  1846
        }
moel@348
  1847
    });
moel@348
  1848
moel@348
  1849
    ko.bindingProvider['instance'] = new ko.bindingProvider();
moel@348
  1850
moel@348
  1851
    function createBindingsStringEvaluatorViaCache(bindingsString, scopesCount, cache) {
moel@348
  1852
        var cacheKey = scopesCount + '_' + bindingsString;
moel@348
  1853
        return cache[cacheKey]
moel@348
  1854
            || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, scopesCount));
moel@348
  1855
    }
moel@348
  1856
moel@348
  1857
    function createBindingsStringEvaluator(bindingsString, scopesCount) {
moel@348
  1858
        var rewrittenBindings = " { " + ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(bindingsString) + " } ";
moel@348
  1859
        return ko.utils.buildEvalWithinScopeFunction(rewrittenBindings, scopesCount);
moel@348
  1860
    }
moel@348
  1861
})();
moel@348
  1862
moel@348
  1863
ko.exportSymbol('bindingProvider', ko.bindingProvider);
moel@348
  1864
(function () {
moel@348
  1865
    ko.bindingHandlers = {};
moel@348
  1866
moel@348
  1867
    ko.bindingContext = function(dataItem, parentBindingContext) {
moel@348
  1868
        if (parentBindingContext) {
moel@348
  1869
            ko.utils.extend(this, parentBindingContext); // Inherit $root and any custom properties
moel@348
  1870
            this['$parentContext'] = parentBindingContext;
moel@348
  1871
            this['$parent'] = parentBindingContext['$data'];
moel@348
  1872
            this['$parents'] = (parentBindingContext['$parents'] || []).slice(0);
moel@348
  1873
            this['$parents'].unshift(this['$parent']);
moel@348
  1874
        } else {
moel@348
  1875
            this['$parents'] = [];
moel@348
  1876
            this['$root'] = dataItem;
moel@348
  1877
        }
moel@348
  1878
        this['$data'] = dataItem;
moel@348
  1879
    }
moel@348
  1880
    ko.bindingContext.prototype['createChildContext'] = function (dataItem) {
moel@348
  1881
        return new ko.bindingContext(dataItem, this);
moel@348
  1882
    };
moel@348
  1883
    ko.bindingContext.prototype['extend'] = function(properties) {
moel@348
  1884
        var clone = ko.utils.extend(new ko.bindingContext(), this);
moel@348
  1885
        return ko.utils.extend(clone, properties);
moel@348
  1886
    };
moel@348
  1887
moel@348
  1888
    function validateThatBindingIsAllowedForVirtualElements(bindingName) {
moel@348
  1889
        var validator = ko.virtualElements.allowedBindings[bindingName];
moel@348
  1890
        if (!validator)
moel@348
  1891
            throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
moel@348
  1892
    }
moel@348
  1893
moel@348
  1894
    function applyBindingsToDescendantsInternal (viewModel, elementOrVirtualElement, bindingContextsMayDifferFromDomParentElement) {
moel@348
  1895
        var currentChild, nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
moel@348
  1896
        while (currentChild = nextInQueue) {
moel@348
  1897
            // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
moel@348
  1898
            nextInQueue = ko.virtualElements.nextSibling(currentChild);
moel@348
  1899
            applyBindingsToNodeAndDescendantsInternal(viewModel, currentChild, bindingContextsMayDifferFromDomParentElement);
moel@348
  1900
        }
moel@348
  1901
    }
moel@348
  1902
moel@348
  1903
    function applyBindingsToNodeAndDescendantsInternal (viewModel, nodeVerified, bindingContextMayDifferFromDomParentElement) {
moel@348
  1904
        var shouldBindDescendants = true;
moel@348
  1905
moel@348
  1906
        // Perf optimisation: Apply bindings only if...
moel@348
  1907
        // (1) We need to store the binding context on this node (because it may differ from the DOM parent node's binding context)
moel@348
  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
moel@348
  1909
        // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
moel@348
  1910
        var isElement = (nodeVerified.nodeType === 1);
moel@348
  1911
        if (isElement) // Workaround IE <= 8 HTML parsing weirdness
moel@348
  1912
            ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
moel@348
  1913
moel@348
  1914
        var shouldApplyBindings = (isElement && bindingContextMayDifferFromDomParentElement)             // Case (1)
moel@348
  1915
                               || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified);       // Case (2)
moel@348
  1916
        if (shouldApplyBindings)
moel@348
  1917
            shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, viewModel, bindingContextMayDifferFromDomParentElement).shouldBindDescendants;
moel@348
  1918
moel@348
  1919
        if (shouldBindDescendants) {
moel@348
  1920
            // We're recursing automatically into (real or virtual) child nodes without changing binding contexts. So,
moel@348
  1921
            //  * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
moel@348
  1922
            //    hence bindingContextsMayDifferFromDomParentElement is false
moel@348
  1923
            //  * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
moel@348
  1924
            //    skip over any number of intermediate virtual elements, any of which might define a custom binding context,
moel@348
  1925
            //    hence bindingContextsMayDifferFromDomParentElement is true
moel@348
  1926
            applyBindingsToDescendantsInternal(viewModel, nodeVerified, /* bindingContextsMayDifferFromDomParentElement: */ !isElement);
moel@348
  1927
        }
moel@348
  1928
    }
moel@348
  1929
moel@348
  1930
    function applyBindingsToNodeInternal (node, bindings, viewModelOrBindingContext, bindingContextMayDifferFromDomParentElement) {
moel@348
  1931
        // Need to be sure that inits are only run once, and updates never run until all the inits have been run
moel@348
  1932
        var initPhase = 0; // 0 = before all inits, 1 = during inits, 2 = after all inits
moel@348
  1933
moel@348
  1934
        // Each time the dependentObservable is evaluated (after data changes),
moel@348
  1935
        // the binding attribute is reparsed so that it can pick out the correct
moel@348
  1936
        // model properties in the context of the changed data.
moel@348
  1937
        // DOM event callbacks need to be able to access this changed data,
moel@348
  1938
        // so we need a single parsedBindings variable (shared by all callbacks
moel@348
  1939
        // associated with this node's bindings) that all the closures can access.
moel@348
  1940
        var parsedBindings;
moel@348
  1941
        function makeValueAccessor(bindingKey) {
moel@348
  1942
            return function () { return parsedBindings[bindingKey] }
moel@348
  1943
        }
moel@348
  1944
        function parsedBindingsAccessor() {
moel@348
  1945
            return parsedBindings;
moel@348
  1946
        }
moel@348
  1947
moel@348
  1948
        var bindingHandlerThatControlsDescendantBindings;
moel@348
  1949
        ko.dependentObservable(
moel@348
  1950
            function () {
moel@348
  1951
                // Ensure we have a nonnull binding context to work with
moel@348
  1952
                var bindingContextInstance = viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
moel@348
  1953
                    ? viewModelOrBindingContext
moel@348
  1954
                    : new ko.bindingContext(ko.utils.unwrapObservable(viewModelOrBindingContext));
moel@348
  1955
                var viewModel = bindingContextInstance['$data'];
moel@348
  1956
moel@348
  1957
                // Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
moel@348
  1958
                // we can easily recover it just by scanning up the node's ancestors in the DOM
moel@348
  1959
                // (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
moel@348
  1960
                if (bindingContextMayDifferFromDomParentElement)
moel@348
  1961
                    ko.storedBindingContextForNode(node, bindingContextInstance);
moel@348
  1962
moel@348
  1963
                // Use evaluatedBindings if given, otherwise fall back on asking the bindings provider to give us some bindings
moel@348
  1964
                var evaluatedBindings = (typeof bindings == "function") ? bindings() : bindings;
moel@348
  1965
                parsedBindings = evaluatedBindings || ko.bindingProvider['instance']['getBindings'](node, bindingContextInstance);
moel@348
  1966
moel@348
  1967
                if (parsedBindings) {
moel@348
  1968
                    // First run all the inits, so bindings can register for notification on changes
moel@348
  1969
                    if (initPhase === 0) {
moel@348
  1970
                        initPhase = 1;
moel@348
  1971
                        for (var bindingKey in parsedBindings) {
moel@348
  1972
                            var binding = ko.bindingHandlers[bindingKey];
moel@348
  1973
                            if (binding && node.nodeType === 8)
moel@348
  1974
                                validateThatBindingIsAllowedForVirtualElements(bindingKey);
moel@348
  1975
moel@348
  1976
                            if (binding && typeof binding["init"] == "function") {
moel@348
  1977
                                var handlerInitFn = binding["init"];
moel@348
  1978
                                var initResult = handlerInitFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
moel@348
  1979
moel@348
  1980
                                // If this binding handler claims to control descendant bindings, make a note of this
moel@348
  1981
                                if (initResult && initResult['controlsDescendantBindings']) {
moel@348
  1982
                                    if (bindingHandlerThatControlsDescendantBindings !== undefined)
moel@348
  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.");
moel@348
  1984
                                    bindingHandlerThatControlsDescendantBindings = bindingKey;
moel@348
  1985
                                }
moel@348
  1986
                            }
moel@348
  1987
                        }
moel@348
  1988
                        initPhase = 2;
moel@348
  1989
                    }
moel@348
  1990
moel@348
  1991
                    // ... then run all the updates, which might trigger changes even on the first evaluation
moel@348
  1992
                    if (initPhase === 2) {
moel@348
  1993
                        for (var bindingKey in parsedBindings) {
moel@348
  1994
                            var binding = ko.bindingHandlers[bindingKey];
moel@348
  1995
                            if (binding && typeof binding["update"] == "function") {
moel@348
  1996
                                var handlerUpdateFn = binding["update"];
moel@348
  1997
                                handlerUpdateFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
moel@348
  1998
                            }
moel@348
  1999
                        }
moel@348
  2000
                    }
moel@348
  2001
                }
moel@348
  2002
            },
moel@348
  2003
            null,
moel@348
  2004
            { 'disposeWhenNodeIsRemoved' : node }
moel@348
  2005
        );
moel@348
  2006
moel@348
  2007
        return {
moel@348
  2008
            shouldBindDescendants: bindingHandlerThatControlsDescendantBindings === undefined
moel@348
  2009
        };
moel@348
  2010
    };
moel@348
  2011
moel@348
  2012
    var storedBindingContextDomDataKey = "__ko_bindingContext__";
moel@348
  2013
    ko.storedBindingContextForNode = function (node, bindingContext) {
moel@348
  2014
        if (arguments.length == 2)
moel@348
  2015
            ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
moel@348
  2016
        else
moel@348
  2017
            return ko.utils.domData.get(node, storedBindingContextDomDataKey);
moel@348
  2018
    }
moel@348
  2019
moel@348
  2020
    ko.applyBindingsToNode = function (node, bindings, viewModel) {
moel@348
  2021
        if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
moel@348
  2022
            ko.virtualElements.normaliseVirtualElementDomStructure(node);
moel@348
  2023
        return applyBindingsToNodeInternal(node, bindings, viewModel, true);
moel@348
  2024
    };
moel@348
  2025
moel@348
  2026
    ko.applyBindingsToDescendants = function(viewModel, rootNode) {
moel@348
  2027
        if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
moel@348
  2028
            applyBindingsToDescendantsInternal(viewModel, rootNode, true);
moel@348
  2029
    };
moel@348
  2030
moel@348
  2031
    ko.applyBindings = function (viewModel, rootNode) {
moel@348
  2032
        if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
moel@348
  2033
            throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
moel@348
  2034
        rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
moel@348
  2035
moel@348
  2036
        applyBindingsToNodeAndDescendantsInternal(viewModel, rootNode, true);
moel@348
  2037
    };
moel@348
  2038
moel@348
  2039
    // Retrieving binding context from arbitrary nodes
moel@348
  2040
    ko.contextFor = function(node) {
moel@348
  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)
moel@348
  2042
        switch (node.nodeType) {
moel@348
  2043
            case 1:
moel@348
  2044
            case 8:
moel@348
  2045
                var context = ko.storedBindingContextForNode(node);
moel@348
  2046
                if (context) return context;
moel@348
  2047
                if (node.parentNode) return ko.contextFor(node.parentNode);
moel@348
  2048
                break;
moel@348
  2049
        }
moel@348
  2050
        return undefined;
moel@348
  2051
    };
moel@348
  2052
    ko.dataFor = function(node) {
moel@348
  2053
        var context = ko.contextFor(node);
moel@348
  2054
        return context ? context['$data'] : undefined;
moel@348
  2055
    };
moel@348
  2056
moel@348
  2057
    ko.exportSymbol('bindingHandlers', ko.bindingHandlers);
moel@348
  2058
    ko.exportSymbol('applyBindings', ko.applyBindings);
moel@348
  2059
    ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants);
moel@348
  2060
    ko.exportSymbol('applyBindingsToNode', ko.applyBindingsToNode);
moel@348
  2061
    ko.exportSymbol('contextFor', ko.contextFor);
moel@348
  2062
    ko.exportSymbol('dataFor', ko.dataFor);
moel@348
  2063
})();
moel@348
  2064
// For certain common events (currently just 'click'), allow a simplified data-binding syntax
moel@348
  2065
// e.g. click:handler instead of the usual full-length event:{click:handler}
moel@348
  2066
var eventHandlersWithShortcuts = ['click'];
moel@348
  2067
ko.utils.arrayForEach(eventHandlersWithShortcuts, function(eventName) {
moel@348
  2068
    ko.bindingHandlers[eventName] = {
moel@348
  2069
        'init': function(element, valueAccessor, allBindingsAccessor, viewModel) {
moel@348
  2070
            var newValueAccessor = function () {
moel@348
  2071
                var result = {};
moel@348
  2072
                result[eventName] = valueAccessor();
moel@348
  2073
                return result;
moel@348
  2074
            };
moel@348
  2075
            return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindingsAccessor, viewModel);
moel@348
  2076
        }
moel@348
  2077
    }
moel@348
  2078
});
moel@348
  2079
moel@348
  2080
moel@348
  2081
ko.bindingHandlers['event'] = {
moel@348
  2082
    'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) {
moel@348
  2083
        var eventsToHandle = valueAccessor() || {};
moel@348
  2084
        for(var eventNameOutsideClosure in eventsToHandle) {
moel@348
  2085
            (function() {
moel@348
  2086
                var eventName = eventNameOutsideClosure; // Separate variable to be captured by event handler closure
moel@348
  2087
                if (typeof eventName == "string") {
moel@348
  2088
                    ko.utils.registerEventHandler(element, eventName, function (event) {
moel@348
  2089
                        var handlerReturnValue;
moel@348
  2090
                        var handlerFunction = valueAccessor()[eventName];
moel@348
  2091
                        if (!handlerFunction)
moel@348
  2092
                            return;
moel@348
  2093
                        var allBindings = allBindingsAccessor();
moel@348
  2094
moel@348
  2095
                        try {
moel@348
  2096
                            // Take all the event args, and prefix with the viewmodel
moel@348
  2097
                            var argsForHandler = ko.utils.makeArray(arguments);
moel@348
  2098
                            argsForHandler.unshift(viewModel);
moel@348
  2099
                            handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);
moel@348
  2100
                        } finally {
moel@348
  2101
                            if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
moel@348
  2102
                                if (event.preventDefault)
moel@348
  2103
                                    event.preventDefault();
moel@348
  2104
                                else
moel@348
  2105
                                    event.returnValue = false;
moel@348
  2106
                            }
moel@348
  2107
                        }
moel@348
  2108
moel@348
  2109
                        var bubble = allBindings[eventName + 'Bubble'] !== false;
moel@348
  2110
                        if (!bubble) {
moel@348
  2111
                            event.cancelBubble = true;
moel@348
  2112
                            if (event.stopPropagation)
moel@348
  2113
                                event.stopPropagation();
moel@348
  2114
                        }
moel@348
  2115
                    });
moel@348
  2116
                }
moel@348
  2117
            })();
moel@348
  2118
        }
moel@348
  2119
    }
moel@348
  2120
};
moel@348
  2121
moel@348
  2122
ko.bindingHandlers['submit'] = {
moel@348
  2123
    'init': function (element, valueAccessor, allBindingsAccessor, viewModel) {
moel@348
  2124
        if (typeof valueAccessor() != "function")
moel@348
  2125
            throw new Error("The value for a submit binding must be a function");
moel@348
  2126
        ko.utils.registerEventHandler(element, "submit", function (event) {
moel@348
  2127
            var handlerReturnValue;
moel@348
  2128
            var value = valueAccessor();
moel@348
  2129
            try { handlerReturnValue = value.call(viewModel, element); }
moel@348
  2130
            finally {
moel@348
  2131
                if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
moel@348
  2132
                    if (event.preventDefault)
moel@348
  2133
                        event.preventDefault();
moel@348
  2134
                    else
moel@348
  2135
                        event.returnValue = false;
moel@348
  2136
                }
moel@348
  2137
            }
moel@348
  2138
        });
moel@348
  2139
    }
moel@348
  2140
};
moel@348
  2141
moel@348
  2142
ko.bindingHandlers['visible'] = {
moel@348
  2143
    'update': function (element, valueAccessor) {
moel@348
  2144
        var value = ko.utils.unwrapObservable(valueAccessor());
moel@348
  2145
        var isCurrentlyVisible = !(element.style.display == "none");
moel@348
  2146
        if (value && !isCurrentlyVisible)
moel@348
  2147
            element.style.display = "";
moel@348
  2148
        else if ((!value) && isCurrentlyVisible)
moel@348
  2149
            element.style.display = "none";
moel@348
  2150
    }
moel@348
  2151
}
moel@348
  2152
moel@348
  2153
ko.bindingHandlers['enable'] = {
moel@348
  2154
    'update': function (element, valueAccessor) {
moel@348
  2155
        var value = ko.utils.unwrapObservable(valueAccessor());
moel@348
  2156
        if (value && element.disabled)
moel@348
  2157
            element.removeAttribute("disabled");
moel@348
  2158
        else if ((!value) && (!element.disabled))
moel@348
  2159
            element.disabled = true;
moel@348
  2160
    }
moel@348
  2161
};
moel@348
  2162
moel@348
  2163
ko.bindingHandlers['disable'] = {
moel@348
  2164
    'update': function (element, valueAccessor) {
moel@348
  2165
        ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
moel@348
  2166
    }
moel@348
  2167
};
moel@348
  2168
moel@348
  2169
function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) {
moel@348
  2170
    if (preferModelValue) {
moel@348
  2171
        if (modelValue !== ko.selectExtensions.readValue(element))
moel@348
  2172
            ko.selectExtensions.writeValue(element, modelValue);
moel@348
  2173
    }
moel@348
  2174
moel@348
  2175
    // No matter which direction we're syncing in, we want the end result to be equality between dropdown value and model value.
moel@348
  2176
    // If they aren't equal, either we prefer the dropdown value, or the model value couldn't be represented, so either way,
moel@348
  2177
    // change the model value to match the dropdown.
moel@348
  2178
    if (modelValue !== ko.selectExtensions.readValue(element))
moel@348
  2179
        ko.utils.triggerEvent(element, "change");
moel@348
  2180
};
moel@348
  2181
moel@348
  2182
ko.bindingHandlers['value'] = {
moel@348
  2183
    'init': function (element, valueAccessor, allBindingsAccessor) {
moel@348
  2184
        // Always catch "change" event; possibly other events too if asked
moel@348
  2185
        var eventsToCatch = ["change"];
moel@348
  2186
        var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
moel@348
  2187
        if (requestedEventsToCatch) {
moel@348
  2188
            if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
moel@348
  2189
                requestedEventsToCatch = [requestedEventsToCatch];
moel@348
  2190
            ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
moel@348
  2191
            eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
moel@348
  2192
        }
moel@348
  2193
moel@348
  2194
        var valueUpdateHandler = function() {
moel@348
  2195
            var modelValue = valueAccessor();
moel@348
  2196
            var elementValue = ko.selectExtensions.readValue(element);
moel@348
  2197
            ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue, /* checkIfDifferent: */ true);
moel@348
  2198
        }
moel@348
  2199
moel@348
  2200
        // Workaround for https://github.com/SteveSanderson/knockout/issues/122
moel@348
  2201
        // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
moel@348
  2202
        var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
moel@348
  2203
                                       && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
moel@348
  2204
        if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
moel@348
  2205
            var propertyChangedFired = false;
moel@348
  2206
            ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
moel@348
  2207
            ko.utils.registerEventHandler(element, "blur", function() {
moel@348
  2208
                if (propertyChangedFired) {
moel@348
  2209
                    propertyChangedFired = false;
moel@348
  2210
                    valueUpdateHandler();
moel@348
  2211
                }
moel@348
  2212
            });
moel@348
  2213
        }
moel@348
  2214
moel@348
  2215
        ko.utils.arrayForEach(eventsToCatch, function(eventName) {
moel@348
  2216
            // The syntax "after<eventname>" means "run the handler asynchronously after the event"
moel@348
  2217
            // This is useful, for example, to catch "keydown" events after the browser has updated the control
moel@348
  2218
            // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
moel@348
  2219
            var handler = valueUpdateHandler;
moel@348
  2220
            if (ko.utils.stringStartsWith(eventName, "after")) {
moel@348
  2221
                handler = function() { setTimeout(valueUpdateHandler, 0) };
moel@348
  2222
                eventName = eventName.substring("after".length);
moel@348
  2223
            }
moel@348
  2224
            ko.utils.registerEventHandler(element, eventName, handler);
moel@348
  2225
        });
moel@348
  2226
    },
moel@348
  2227
    'update': function (element, valueAccessor) {
moel@348
  2228
        var valueIsSelectOption = ko.utils.tagNameLower(element) === "select";
moel@348
  2229
        var newValue = ko.utils.unwrapObservable(valueAccessor());
moel@348
  2230
        var elementValue = ko.selectExtensions.readValue(element);
moel@348
  2231
        var valueHasChanged = (newValue != elementValue);
moel@348
  2232
moel@348
  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).
moel@348
  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.
moel@348
  2235
        if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0"))
moel@348
  2236
            valueHasChanged = true;
moel@348
  2237
moel@348
  2238
        if (valueHasChanged) {
moel@348
  2239
            var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
moel@348
  2240
            applyValueAction();
moel@348
  2241
moel@348
  2242
            // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
moel@348
  2243
            // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
moel@348
  2244
            // to apply the value as well.
moel@348
  2245
            var alsoApplyAsynchronously = valueIsSelectOption;
moel@348
  2246
            if (alsoApplyAsynchronously)
moel@348
  2247
                setTimeout(applyValueAction, 0);
moel@348
  2248
        }
moel@348
  2249
moel@348
  2250
        // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
moel@348
  2251
        // because you're not allowed to have a model value that disagrees with a visible UI selection.
moel@348
  2252
        if (valueIsSelectOption && (element.length > 0))
moel@348
  2253
            ensureDropdownSelectionIsConsistentWithModelValue(element, newValue, /* preferModelValue */ false);
moel@348
  2254
    }
moel@348
  2255
};
moel@348
  2256
moel@348
  2257
ko.bindingHandlers['options'] = {
moel@348
  2258
    'update': function (element, valueAccessor, allBindingsAccessor) {
moel@348
  2259
        if (ko.utils.tagNameLower(element) !== "select")
moel@348
  2260
            throw new Error("options binding applies only to SELECT elements");
moel@348
  2261
moel@348
  2262
        var selectWasPreviouslyEmpty = element.length == 0;
moel@348
  2263
        var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
moel@348
  2264
            return node.tagName && (ko.utils.tagNameLower(node) === "option") && node.selected;
moel@348
  2265
        }), function (node) {
moel@348
  2266
            return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
moel@348
  2267
        });
moel@348
  2268
        var previousScrollTop = element.scrollTop;
moel@348
  2269
moel@348
  2270
        var value = ko.utils.unwrapObservable(valueAccessor());
moel@348
  2271
        var selectedValue = element.value;
moel@348
  2272
moel@348
  2273
        // Remove all existing <option>s.
moel@348
  2274
        // Need to use .remove() rather than .removeChild() for <option>s otherwise IE behaves oddly (https://github.com/SteveSanderson/knockout/issues/134)
moel@348
  2275
        while (element.length > 0) {
moel@348
  2276
            ko.cleanNode(element.options[0]);
moel@348
  2277
            element.remove(0);
moel@348
  2278
        }
moel@348
  2279
moel@348
  2280
        if (value) {
moel@348
  2281
            var allBindings = allBindingsAccessor();
moel@348
  2282
            if (typeof value.length != "number")
moel@348
  2283
                value = [value];
moel@348
  2284
            if (allBindings['optionsCaption']) {
moel@348
  2285
                var option = document.createElement("option");
moel@348
  2286
                ko.utils.setHtml(option, allBindings['optionsCaption']);
moel@348
  2287
                ko.selectExtensions.writeValue(option, undefined);
moel@348
  2288
                element.appendChild(option);
moel@348
  2289
            }
moel@348
  2290
            for (var i = 0, j = value.length; i < j; i++) {
moel@348
  2291
                var option = document.createElement("option");
moel@348
  2292
moel@348
  2293
                // Apply a value to the option element
moel@348
  2294
                var optionValue = typeof allBindings['optionsValue'] == "string" ? value[i][allBindings['optionsValue']] : value[i];
moel@348
  2295
                optionValue = ko.utils.unwrapObservable(optionValue);
moel@348
  2296
                ko.selectExtensions.writeValue(option, optionValue);
moel@348
  2297
moel@348
  2298
                // Apply some text to the option element
moel@348
  2299
                var optionsTextValue = allBindings['optionsText'];
moel@348
  2300
                var optionText;
moel@348
  2301
                if (typeof optionsTextValue == "function")
moel@348
  2302
                    optionText = optionsTextValue(value[i]); // Given a function; run it against the data value
moel@348
  2303
                else if (typeof optionsTextValue == "string")
moel@348
  2304
                    optionText = value[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
moel@348
  2305
                else
moel@348
  2306
                    optionText = optionValue;				 // Given no optionsText arg; use the data value itself
moel@348
  2307
                if ((optionText === null) || (optionText === undefined))
moel@348
  2308
                    optionText = "";
moel@348
  2309
moel@348
  2310
                ko.utils.setTextContent(option, optionText);
moel@348
  2311
moel@348
  2312
                element.appendChild(option);
moel@348
  2313
            }
moel@348
  2314
moel@348
  2315
            // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
moel@348
  2316
            // That's why we first added them without selection. Now it's time to set the selection.
moel@348
  2317
            var newOptions = element.getElementsByTagName("option");
moel@348
  2318
            var countSelectionsRetained = 0;
moel@348
  2319
            for (var i = 0, j = newOptions.length; i < j; i++) {
moel@348
  2320
                if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
moel@348
  2321
                    ko.utils.setOptionNodeSelectionState(newOptions[i], true);
moel@348
  2322
                    countSelectionsRetained++;
moel@348
  2323
                }
moel@348
  2324
            }
moel@348
  2325
moel@348
  2326
            element.scrollTop = previousScrollTop;
moel@348
  2327
moel@348
  2328
            if (selectWasPreviouslyEmpty && ('value' in allBindings)) {
moel@348
  2329
                // Ensure consistency between model value and selected option.
moel@348
  2330
                // If the dropdown is being populated for the first time here (or was otherwise previously empty),
moel@348
  2331
                // the dropdown selection state is meaningless, so we preserve the model value.
moel@348
  2332
                ensureDropdownSelectionIsConsistentWithModelValue(element, ko.utils.unwrapObservable(allBindings['value']), /* preferModelValue */ true);
moel@348
  2333
            }
moel@348
  2334
moel@348
  2335
            // Workaround for IE9 bug
moel@348
  2336
            ko.utils.ensureSelectElementIsRenderedCorrectly(element);
moel@348
  2337
        }
moel@348
  2338
    }
moel@348
  2339
};
moel@348
  2340
ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.optionValueDomData__';
moel@348
  2341
moel@348
  2342
ko.bindingHandlers['selectedOptions'] = {
moel@348
  2343
    getSelectedValuesFromSelectNode: function (selectNode) {
moel@348
  2344
        var result = [];
moel@348
  2345
        var nodes = selectNode.childNodes;
moel@348
  2346
        for (var i = 0, j = nodes.length; i < j; i++) {
moel@348
  2347
            var node = nodes[i], tagName = ko.utils.tagNameLower(node);
moel@348
  2348
            if (tagName == "option" && node.selected)
moel@348
  2349
                result.push(ko.selectExtensions.readValue(node));
moel@348
  2350
            else if (tagName == "optgroup") {
moel@348
  2351
                var selectedValuesFromOptGroup = ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(node);
moel@348
  2352
                Array.prototype.splice.apply(result, [result.length, 0].concat(selectedValuesFromOptGroup)); // Add new entries to existing 'result' instance
moel@348
  2353
            }
moel@348
  2354
        }
moel@348
  2355
        return result;
moel@348
  2356
    },
moel@348
  2357
    'init': function (element, valueAccessor, allBindingsAccessor) {
moel@348
  2358
        ko.utils.registerEventHandler(element, "change", function () {
moel@348
  2359
            var value = valueAccessor();
moel@348
  2360
            var valueToWrite = ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this);
moel@348
  2361
            ko.jsonExpressionRewriting.writeValueToProperty(value, allBindingsAccessor, 'value', valueToWrite);
moel@348
  2362
        });
moel@348
  2363
    },
moel@348
  2364
    'update': function (element, valueAccessor) {
moel@348
  2365
        if (ko.utils.tagNameLower(element) != "select")
moel@348
  2366
            throw new Error("values binding applies only to SELECT elements");
moel@348
  2367
moel@348
  2368
        var newValue = ko.utils.unwrapObservable(valueAccessor());
moel@348
  2369
        if (newValue && typeof newValue.length == "number") {
moel@348
  2370
            var nodes = element.childNodes;
moel@348
  2371
            for (var i = 0, j = nodes.length; i < j; i++) {
moel@348
  2372
                var node = nodes[i];
moel@348
  2373
                if (ko.utils.tagNameLower(node) === "option")
moel@348
  2374
                    ko.utils.setOptionNodeSelectionState(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0);
moel@348
  2375
            }
moel@348
  2376
        }
moel@348
  2377
    }
moel@348
  2378
};
moel@348
  2379
moel@348
  2380
ko.bindingHandlers['text'] = {
moel@348
  2381
    'update': function (element, valueAccessor) {
moel@348
  2382
        ko.utils.setTextContent(element, valueAccessor());
moel@348
  2383
    }
moel@348
  2384
};
moel@348
  2385
moel@348
  2386
ko.bindingHandlers['html'] = {
moel@348
  2387
    'init': function() {
moel@348
  2388
        // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
moel@348
  2389
        return { 'controlsDescendantBindings': true };
moel@348
  2390
    },
moel@348
  2391
    'update': function (element, valueAccessor) {
moel@348
  2392
        var value = ko.utils.unwrapObservable(valueAccessor());
moel@348
  2393
        ko.utils.setHtml(element, value);
moel@348
  2394
    }
moel@348
  2395
};
moel@348
  2396
moel@348
  2397
ko.bindingHandlers['css'] = {
moel@348
  2398
    'update': function (element, valueAccessor) {
moel@348
  2399
        var value = ko.utils.unwrapObservable(valueAccessor() || {});
moel@348
  2400
        for (var className in value) {
moel@348
  2401
            if (typeof className == "string") {
moel@348
  2402
                var shouldHaveClass = ko.utils.unwrapObservable(value[className]);
moel@348
  2403
                ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
moel@348
  2404
            }
moel@348
  2405
        }
moel@348
  2406
    }
moel@348
  2407
};
moel@348
  2408
moel@348
  2409
ko.bindingHandlers['style'] = {
moel@348
  2410
    'update': function (element, valueAccessor) {
moel@348
  2411
        var value = ko.utils.unwrapObservable(valueAccessor() || {});
moel@348
  2412
        for (var styleName in value) {
moel@348
  2413
            if (typeof styleName == "string") {
moel@348
  2414
                var styleValue = ko.utils.unwrapObservable(value[styleName]);
moel@348
  2415
                element.style[styleName] = styleValue || ""; // Empty string removes the value, whereas null/undefined have no effect
moel@348
  2416
            }
moel@348
  2417
        }
moel@348
  2418
    }
moel@348
  2419
};
moel@348
  2420
moel@348
  2421
ko.bindingHandlers['uniqueName'] = {
moel@348
  2422
    'init': function (element, valueAccessor) {
moel@348
  2423
        if (valueAccessor()) {
moel@348
  2424
            element.name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
moel@348
  2425
moel@348
  2426
            // Workaround IE 6/7 issue
moel@348
  2427
            // - https://github.com/SteveSanderson/knockout/issues/197
moel@348
  2428
            // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
moel@348
  2429
            if (ko.utils.isIe6 || ko.utils.isIe7)
moel@348
  2430
                element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
moel@348
  2431
        }
moel@348
  2432
    }
moel@348
  2433
};
moel@348
  2434
ko.bindingHandlers['uniqueName'].currentIndex = 0;
moel@348
  2435
moel@348
  2436
ko.bindingHandlers['checked'] = {
moel@348
  2437
    'init': function (element, valueAccessor, allBindingsAccessor) {
moel@348
  2438
        var updateHandler = function() {
moel@348
  2439
            var valueToWrite;
moel@348
  2440
            if (element.type == "checkbox") {
moel@348
  2441
                valueToWrite = element.checked;
moel@348
  2442
            } else if ((element.type == "radio") && (element.checked)) {
moel@348
  2443
                valueToWrite = element.value;
moel@348
  2444
            } else {
moel@348
  2445
                return; // "checked" binding only responds to checkboxes and selected radio buttons
moel@348
  2446
            }
moel@348
  2447
moel@348
  2448
            var modelValue = valueAccessor();
moel@348
  2449
            if ((element.type == "checkbox") && (ko.utils.unwrapObservable(modelValue) instanceof Array)) {
moel@348
  2450
                // For checkboxes bound to an array, we add/remove the checkbox value to that array
moel@348
  2451
                // This works for both observable and non-observable arrays
moel@348
  2452
                var existingEntryIndex = ko.utils.arrayIndexOf(ko.utils.unwrapObservable(modelValue), element.value);
moel@348
  2453
                if (element.checked && (existingEntryIndex < 0))
moel@348
  2454
                    modelValue.push(element.value);
moel@348
  2455
                else if ((!element.checked) && (existingEntryIndex >= 0))
moel@348
  2456
                    modelValue.splice(existingEntryIndex, 1);
moel@348
  2457
            } else {
moel@348
  2458
                ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
moel@348
  2459
            }
moel@348
  2460
        };
moel@348
  2461
        ko.utils.registerEventHandler(element, "click", updateHandler);
moel@348
  2462
moel@348
  2463
        // IE 6 won't allow radio buttons to be selected unless they have a name
moel@348
  2464
        if ((element.type == "radio") && !element.name)
moel@348
  2465
            ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
moel@348
  2466
    },
moel@348
  2467
    'update': function (element, valueAccessor) {
moel@348
  2468
        var value = ko.utils.unwrapObservable(valueAccessor());
moel@348
  2469
moel@348
  2470
        if (element.type == "checkbox") {
moel@348
  2471
            if (value instanceof Array) {
moel@348
  2472
                // When bound to an array, the checkbox being checked represents its value being present in that array
moel@348
  2473
                element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0;
moel@348
  2474
            } else {
moel@348
  2475
                // When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
moel@348
  2476
                element.checked = value;
moel@348
  2477
            }
moel@348
  2478
        } else if (element.type == "radio") {
moel@348
  2479
            element.checked = (element.value == value);
moel@348
  2480
        }
moel@348
  2481
    }
moel@348
  2482
};
moel@348
  2483
moel@348
  2484
var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
moel@348
  2485
ko.bindingHandlers['attr'] = {
moel@348
  2486
    'update': function(element, valueAccessor, allBindingsAccessor) {
moel@348
  2487
        var value = ko.utils.unwrapObservable(valueAccessor()) || {};
moel@348
  2488
        for (var attrName in value) {
moel@348
  2489
            if (typeof attrName == "string") {
moel@348
  2490
                var attrValue = ko.utils.unwrapObservable(value[attrName]);
moel@348
  2491
moel@348
  2492
                // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
moel@348
  2493
                // when someProp is a "no value"-like value (strictly null, false, or undefined)
moel@348
  2494
                // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
moel@348
  2495
                var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
moel@348
  2496
                if (toRemove)
moel@348
  2497
                    element.removeAttribute(attrName);
moel@348
  2498
moel@348
  2499
                // In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
moel@348
  2500
                // HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
moel@348
  2501
                // but instead of figuring out the mode, we'll just set the attribute through the Javascript
moel@348
  2502
                // property for IE <= 8.
moel@348
  2503
                if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
moel@348
  2504
                    attrName = attrHtmlToJavascriptMap[attrName];
moel@348
  2505
                    if (toRemove)
moel@348
  2506
                        element.removeAttribute(attrName);
moel@348
  2507
                    else
moel@348
  2508
                        element[attrName] = attrValue;
moel@348
  2509
                } else if (!toRemove) {
moel@348
  2510
                    element.setAttribute(attrName, attrValue.toString());
moel@348
  2511
                }
moel@348
  2512
            }
moel@348
  2513
        }
moel@348
  2514
    }
moel@348
  2515
};
moel@348
  2516
moel@348
  2517
ko.bindingHandlers['hasfocus'] = {
moel@348
  2518
    'init': function(element, valueAccessor, allBindingsAccessor) {
moel@348
  2519
        var writeValue = function(valueToWrite) {
moel@348
  2520
            var modelValue = valueAccessor();
moel@348
  2521
            ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'hasfocus', valueToWrite, true);
moel@348
  2522
        };
moel@348
  2523
        ko.utils.registerEventHandler(element, "focus", function() { writeValue(true) });
moel@348
  2524
        ko.utils.registerEventHandler(element, "focusin", function() { writeValue(true) }); // For IE
moel@348
  2525
        ko.utils.registerEventHandler(element, "blur",  function() { writeValue(false) });
moel@348
  2526
        ko.utils.registerEventHandler(element, "focusout",  function() { writeValue(false) }); // For IE
moel@348
  2527
    },
moel@348
  2528
    'update': function(element, valueAccessor) {
moel@348
  2529
        var value = ko.utils.unwrapObservable(valueAccessor());
moel@348
  2530
        value ? element.focus() : element.blur();
moel@348
  2531
        ko.utils.triggerEvent(element, value ? "focusin" : "focusout"); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
moel@348
  2532
    }
moel@348
  2533
};
moel@348
  2534
moel@348
  2535
// "with: someExpression" is equivalent to "template: { if: someExpression, data: someExpression }"
moel@348
  2536
ko.bindingHandlers['with'] = {
moel@348
  2537
    makeTemplateValueAccessor: function(valueAccessor) {
moel@348
  2538
        return function() { var value = valueAccessor(); return { 'if': value, 'data': value, 'templateEngine': ko.nativeTemplateEngine.instance } };
moel@348
  2539
    },
moel@348
  2540
    'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
moel@348
  2541
        return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor));
moel@348
  2542
    },
moel@348
  2543
    'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
moel@348
  2544
        return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
moel@348
  2545
    }
moel@348
  2546
};
moel@348
  2547
ko.jsonExpressionRewriting.bindingRewriteValidators['with'] = false; // Can't rewrite control flow bindings
moel@348
  2548
ko.virtualElements.allowedBindings['with'] = true;
moel@348
  2549
moel@348
  2550
// "if: someExpression" is equivalent to "template: { if: someExpression }"
moel@348
  2551
ko.bindingHandlers['if'] = {
moel@348
  2552
    makeTemplateValueAccessor: function(valueAccessor) {
moel@348
  2553
        return function() { return { 'if': valueAccessor(), 'templateEngine': ko.nativeTemplateEngine.instance } };
moel@348
  2554
    },
moel@348
  2555
    'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
moel@348
  2556
        return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor));
moel@348
  2557
    },
moel@348
  2558
    'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
moel@348
  2559
        return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
moel@348
  2560
    }
moel@348
  2561
};
moel@348
  2562
ko.jsonExpressionRewriting.bindingRewriteValidators['if'] = false; // Can't rewrite control flow bindings
moel@348
  2563
ko.virtualElements.allowedBindings['if'] = true;
moel@348
  2564
moel@348
  2565
// "ifnot: someExpression" is equivalent to "template: { ifnot: someExpression }"
moel@348
  2566
ko.bindingHandlers['ifnot'] = {
moel@348
  2567
    makeTemplateValueAccessor: function(valueAccessor) {
moel@348
  2568
        return function() { return { 'ifnot': valueAccessor(), 'templateEngine': ko.nativeTemplateEngine.instance } };
moel@348
  2569
    },
moel@348
  2570
    'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
moel@348
  2571
        return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['ifnot'].makeTemplateValueAccessor(valueAccessor));
moel@348
  2572
    },
moel@348
  2573
    'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
moel@348
  2574
        return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['ifnot'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
moel@348
  2575
    }
moel@348
  2576
};
moel@348
  2577
ko.jsonExpressionRewriting.bindingRewriteValidators['ifnot'] = false; // Can't rewrite control flow bindings
moel@348
  2578
ko.virtualElements.allowedBindings['ifnot'] = true;
moel@348
  2579
moel@348
  2580
// "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
moel@348
  2581
// "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
moel@348
  2582
ko.bindingHandlers['foreach'] = {
moel@348
  2583
    makeTemplateValueAccessor: function(valueAccessor) {
moel@348
  2584
        return function() {
moel@348
  2585
            var bindingValue = ko.utils.unwrapObservable(valueAccessor());
moel@348
  2586
moel@348
  2587
            // If bindingValue is the array, just pass it on its own
moel@348
  2588
            if ((!bindingValue) || typeof bindingValue.length == "number")
moel@348
  2589
                return { 'foreach': bindingValue, 'templateEngine': ko.nativeTemplateEngine.instance };
moel@348
  2590
moel@348
  2591
            // If bindingValue.data is the array, preserve all relevant options
moel@348
  2592
            return {
moel@348
  2593
                'foreach': bindingValue['data'],
moel@348
  2594
                'includeDestroyed': bindingValue['includeDestroyed'],
moel@348
  2595
                'afterAdd': bindingValue['afterAdd'],
moel@348
  2596
                'beforeRemove': bindingValue['beforeRemove'],
moel@348
  2597
                'afterRender': bindingValue['afterRender'],
moel@348
  2598
                'templateEngine': ko.nativeTemplateEngine.instance
moel@348
  2599
            };
moel@348
  2600
        };
moel@348
  2601
    },
moel@348
  2602
    'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
moel@348
  2603
        return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
moel@348
  2604
    },
moel@348
  2605
    'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
moel@348
  2606
        return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
moel@348
  2607
    }
moel@348
  2608
};
moel@348
  2609
ko.jsonExpressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
moel@348
  2610
ko.virtualElements.allowedBindings['foreach'] = true;
moel@348
  2611
// If you want to make a custom template engine,
moel@348
  2612
//
moel@348
  2613
// [1] Inherit from this class (like ko.nativeTemplateEngine does)
moel@348
  2614
// [2] Override 'renderTemplateSource', supplying a function with this signature:
moel@348
  2615
//
moel@348
  2616
//        function (templateSource, bindingContext, options) {
moel@348
  2617
//            // - templateSource.text() is the text of the template you should render
moel@348
  2618
//            // - bindingContext.$data is the data you should pass into the template
moel@348
  2619
//            //   - you might also want to make bindingContext.$parent, bindingContext.$parents,
moel@348
  2620
//            //     and bindingContext.$root available in the template too
moel@348
  2621
//            // - options gives you access to any other properties set on "data-bind: { template: options }"
moel@348
  2622
//            //
moel@348
  2623
//            // Return value: an array of DOM nodes
moel@348
  2624
//        }
moel@348
  2625
//
moel@348
  2626
// [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
moel@348
  2627
//
moel@348
  2628
//        function (script) {
moel@348
  2629
//            // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
moel@348
  2630
//            //               For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
moel@348
  2631
//        }
moel@348
  2632
//
moel@348
  2633
//     This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
moel@348
  2634
//     If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
moel@348
  2635
//     and then you don't need to override 'createJavaScriptEvaluatorBlock'.
moel@348
  2636
moel@348
  2637
ko.templateEngine = function () { };
moel@348
  2638
moel@348
  2639
ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
moel@348
  2640
    throw new Error("Override renderTemplateSource");
moel@348
  2641
};
moel@348
  2642
moel@348
  2643
ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
moel@348
  2644
    throw new Error("Override createJavaScriptEvaluatorBlock");
moel@348
  2645
};
moel@348
  2646
moel@348
  2647
ko.templateEngine.prototype['makeTemplateSource'] = function(template, templateDocument) {
moel@348
  2648
    // Named template
moel@348
  2649
    if (typeof template == "string") {
moel@348
  2650
        templateDocument = templateDocument || document;
moel@348
  2651
        var elem = templateDocument.getElementById(template);
moel@348
  2652
        if (!elem)
moel@348
  2653
            throw new Error("Cannot find template with ID " + template);
moel@348
  2654
        return new ko.templateSources.domElement(elem);
moel@348
  2655
    } else if ((template.nodeType == 1) || (template.nodeType == 8)) {
moel@348
  2656
        // Anonymous template
moel@348
  2657
        return new ko.templateSources.anonymousTemplate(template);
moel@348
  2658
    } else
moel@348
  2659
        throw new Error("Unknown template type: " + template);
moel@348
  2660
};
moel@348
  2661
moel@348
  2662
ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) {
moel@348
  2663
    var templateSource = this['makeTemplateSource'](template, templateDocument);
moel@348
  2664
    return this['renderTemplateSource'](templateSource, bindingContext, options);
moel@348
  2665
};
moel@348
  2666
moel@348
  2667
ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) {
moel@348
  2668
    // Skip rewriting if requested
moel@348
  2669
    if (this['allowTemplateRewriting'] === false)
moel@348
  2670
        return true;
moel@348
  2671
moel@348
  2672
    // Perf optimisation - see below
moel@348
  2673
    var templateIsInExternalDocument = templateDocument && templateDocument != document;
moel@348
  2674
    if (!templateIsInExternalDocument && this.knownRewrittenTemplates && this.knownRewrittenTemplates[template])
moel@348
  2675
        return true;
moel@348
  2676
moel@348
  2677
    return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten");
moel@348
  2678
};
moel@348
  2679
moel@348
  2680
ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, templateDocument) {
moel@348
  2681
    var templateSource = this['makeTemplateSource'](template, templateDocument);
moel@348
  2682
    var rewritten = rewriterCallback(templateSource['text']());
moel@348
  2683
    templateSource['text'](rewritten);
moel@348
  2684
    templateSource['data']("isRewritten", true);
moel@348
  2685
moel@348
  2686
    // Perf optimisation - for named templates, track which ones have been rewritten so we can
moel@348
  2687
    // answer 'isTemplateRewritten' *without* having to use getElementById (which is slow on IE < 8)
moel@348
  2688
    //
moel@348
  2689
    // Note that we only cache the status for templates in the main document, because caching on a per-doc
moel@348
  2690
    // basis complicates the implementation excessively. In a future version of KO, we will likely remove
moel@348
  2691
    // this 'isRewritten' cache entirely anyway, because the benefit is extremely minor and only applies
moel@348
  2692
    // to rewritable templates, which are pretty much deprecated since KO 2.0.
moel@348
  2693
    var templateIsInExternalDocument = templateDocument && templateDocument != document;
moel@348
  2694
    if (!templateIsInExternalDocument && typeof template == "string") {
moel@348
  2695
        this.knownRewrittenTemplates = this.knownRewrittenTemplates || {};
moel@348
  2696
        this.knownRewrittenTemplates[template] = true;
moel@348
  2697
    }
moel@348
  2698
};
moel@348
  2699
moel@348
  2700
ko.exportSymbol('templateEngine', ko.templateEngine);
moel@348
  2701
moel@348
  2702
ko.templateRewriting = (function () {
moel@348
  2703
    var memoizeDataBindingAttributeSyntaxRegex = /(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9\-]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi;
moel@348
  2704
    var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
moel@348
  2705
moel@348
  2706
    function validateDataBindValuesForRewriting(keyValueArray) {
moel@348
  2707
        var allValidators = ko.jsonExpressionRewriting.bindingRewriteValidators;
moel@348
  2708
        for (var i = 0; i < keyValueArray.length; i++) {
moel@348
  2709
            var key = keyValueArray[i]['key'];
moel@348
  2710
            if (allValidators.hasOwnProperty(key)) {
moel@348
  2711
                var validator = allValidators[key];
moel@348
  2712
moel@348
  2713
                if (typeof validator === "function") {
moel@348
  2714
                    var possibleErrorMessage = validator(keyValueArray[i]['value']);
moel@348
  2715
                    if (possibleErrorMessage)
moel@348
  2716
                        throw new Error(possibleErrorMessage);
moel@348
  2717
                } else if (!validator) {
moel@348
  2718
                    throw new Error("This template engine does not support the '" + key + "' binding within its templates");
moel@348
  2719
                }
moel@348
  2720
            }
moel@348
  2721
        }
moel@348
  2722
    }
moel@348
  2723
moel@348
  2724
    function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, templateEngine) {
moel@348
  2725
        var dataBindKeyValueArray = ko.jsonExpressionRewriting.parseObjectLiteral(dataBindAttributeValue);
moel@348
  2726
        validateDataBindValuesForRewriting(dataBindKeyValueArray);
moel@348
  2727
        var rewrittenDataBindAttributeValue = ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(dataBindKeyValueArray);
moel@348
  2728
moel@348
  2729
        // For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
moel@348
  2730
        // anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
moel@348
  2731
        // extra indirection.
moel@348
  2732
        var applyBindingsToNextSiblingScript = "ko.templateRewriting.applyMemoizedBindingsToNextSibling(function() { \
moel@348
  2733
            return (function() { return { " + rewrittenDataBindAttributeValue + " } })() \
moel@348
  2734
        })";
moel@348
  2735
        return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
moel@348
  2736
    }
moel@348
  2737
moel@348
  2738
    return {
moel@348
  2739
        ensureTemplateIsRewritten: function (template, templateEngine, templateDocument) {
moel@348
  2740
            if (!templateEngine['isTemplateRewritten'](template, templateDocument))
moel@348
  2741
                templateEngine['rewriteTemplate'](template, function (htmlString) {
moel@348
  2742
                    return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine);
moel@348
  2743
                }, templateDocument);
moel@348
  2744
        },
moel@348
  2745
moel@348
  2746
        memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
moel@348
  2747
            return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
moel@348
  2748
                return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[6], /* tagToRetain: */ arguments[1], templateEngine);
moel@348
  2749
            }).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
moel@348
  2750
                return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", templateEngine);
moel@348
  2751
            });
moel@348
  2752
        },
moel@348
  2753
moel@348
  2754
        applyMemoizedBindingsToNextSibling: function (bindings) {
moel@348
  2755
            return ko.memoization.memoize(function (domNode, bindingContext) {
moel@348
  2756
                if (domNode.nextSibling)
moel@348
  2757
                    ko.applyBindingsToNode(domNode.nextSibling, bindings, bindingContext);
moel@348
  2758
            });
moel@348
  2759
        }
moel@348
  2760
    }
moel@348
  2761
})();
moel@348
  2762
moel@348
  2763
ko.exportSymbol('templateRewriting', ko.templateRewriting);
moel@348
  2764
ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templateRewriting.applyMemoizedBindingsToNextSibling); // Exported only because it has to be referenced by string lookup from within rewritten template
moel@348
  2765
(function() {
moel@348
  2766
    // A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving
moel@348
  2767
    // logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.)
moel@348
  2768
    //
moel@348
  2769
    // Two are provided by default:
moel@348
  2770
    //  1. ko.templateSources.domElement       - reads/writes the text content of an arbitrary DOM element
moel@348
  2771
    //  2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but
moel@348
  2772
    //                                           without reading/writing the actual element text content, since it will be overwritten
moel@348
  2773
    //                                           with the rendered template output.
moel@348
  2774
    // You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
moel@348
  2775
    // Template sources need to have the following functions:
moel@348
  2776
    //   text() 			- returns the template text from your storage location
moel@348
  2777
    //   text(value)		- writes the supplied template text to your storage location
moel@348
  2778
    //   data(key)			- reads values stored using data(key, value) - see below
moel@348
  2779
    //   data(key, value)	- associates "value" with this template and the key "key". Is used to store information like "isRewritten".
moel@348
  2780
    //
moel@348
  2781
    // Optionally, template sources can also have the following functions:
moel@348
  2782
    //   nodes()            - returns a DOM element containing the nodes of this template, where available
moel@348
  2783
    //   nodes(value)       - writes the given DOM element to your storage location
moel@348
  2784
    // If a DOM element is available for a given template source, template engines are encouraged to use it in preference over text()
moel@348
  2785
    // for improved speed. However, all templateSources must supply text() even if they don't supply nodes().
moel@348
  2786
    //
moel@348
  2787
    // Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were
moel@348
  2788
    // using and overriding "makeTemplateSource" to return an instance of your custom template source.
moel@348
  2789
moel@348
  2790
    ko.templateSources = {};
moel@348
  2791
moel@348
  2792
    // ---- ko.templateSources.domElement -----
moel@348
  2793
moel@348
  2794
    ko.templateSources.domElement = function(element) {
moel@348
  2795
        this.domElement = element;
moel@348
  2796
    }
moel@348
  2797
moel@348
  2798
    ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
moel@348
  2799
        var tagNameLower = ko.utils.tagNameLower(this.domElement),
moel@348
  2800
            elemContentsProperty = tagNameLower === "script" ? "text"
moel@348
  2801
                                 : tagNameLower === "textarea" ? "value"
moel@348
  2802
                                 : "innerHTML";
moel@348
  2803
moel@348
  2804
        if (arguments.length == 0) {
moel@348
  2805
            return this.domElement[elemContentsProperty];
moel@348
  2806
        } else {
moel@348
  2807
            var valueToWrite = arguments[0];
moel@348
  2808
            if (elemContentsProperty === "innerHTML")
moel@348
  2809
                ko.utils.setHtml(this.domElement, valueToWrite);
moel@348
  2810
            else
moel@348
  2811
                this.domElement[elemContentsProperty] = valueToWrite;
moel@348
  2812
        }
moel@348
  2813
    };
moel@348
  2814
moel@348
  2815
    ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
moel@348
  2816
        if (arguments.length === 1) {
moel@348
  2817
            return ko.utils.domData.get(this.domElement, "templateSourceData_" + key);
moel@348
  2818
        } else {
moel@348
  2819
            ko.utils.domData.set(this.domElement, "templateSourceData_" + key, arguments[1]);
moel@348
  2820
        }
moel@348
  2821
    };
moel@348
  2822
moel@348
  2823
    // ---- ko.templateSources.anonymousTemplate -----
moel@348
  2824
    // Anonymous templates are normally saved/retrieved as DOM nodes through "nodes".
moel@348
  2825
    // For compatibility, you can also read "text"; it will be serialized from the nodes on demand.
moel@348
  2826
    // Writing to "text" is still supported, but then the template data will not be available as DOM nodes.
moel@348
  2827
moel@348
  2828
    var anonymousTemplatesDomDataKey = "__ko_anon_template__";
moel@348
  2829
    ko.templateSources.anonymousTemplate = function(element) {
moel@348
  2830
        this.domElement = element;
moel@348
  2831
    }
moel@348
  2832
    ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
moel@348
  2833
    ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
moel@348
  2834
        if (arguments.length == 0) {
moel@348
  2835
            var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
moel@348
  2836
            if (templateData.textData === undefined && templateData.containerData)
moel@348
  2837
                templateData.textData = templateData.containerData.innerHTML;
moel@348
  2838
            return templateData.textData;
moel@348
  2839
        } else {
moel@348
  2840
            var valueToWrite = arguments[0];
moel@348
  2841
            ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {textData: valueToWrite});
moel@348
  2842
        }
moel@348
  2843
    };
moel@348
  2844
    ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
moel@348
  2845
        if (arguments.length == 0) {
moel@348
  2846
            var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
moel@348
  2847
            return templateData.containerData;
moel@348
  2848
        } else {
moel@348
  2849
            var valueToWrite = arguments[0];
moel@348
  2850
            ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {containerData: valueToWrite});
moel@348
  2851
        }
moel@348
  2852
    };
moel@348
  2853
moel@348
  2854
    ko.exportSymbol('templateSources', ko.templateSources);
moel@348
  2855
    ko.exportSymbol('templateSources.domElement', ko.templateSources.domElement);
moel@348
  2856
    ko.exportSymbol('templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate);
moel@348
  2857
})();
moel@348
  2858
(function () {
moel@348
  2859
    var _templateEngine;
moel@348
  2860
    ko.setTemplateEngine = function (templateEngine) {
moel@348
  2861
        if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
moel@348
  2862
            throw new Error("templateEngine must inherit from ko.templateEngine");
moel@348
  2863
        _templateEngine = templateEngine;
moel@348
  2864
    }
moel@348
  2865
moel@348
  2866
    function invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, action) {
moel@348
  2867
        var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode);
moel@348
  2868
        while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) {
moel@348
  2869
            nextInQueue = ko.virtualElements.nextSibling(node);
moel@348
  2870
            if (node.nodeType === 1 || node.nodeType === 8)
moel@348
  2871
                action(node);
moel@348
  2872
        }
moel@348
  2873
    }
moel@348
  2874
moel@348
  2875
    function activateBindingsOnContinuousNodeArray(continuousNodeArray, bindingContext) {
moel@348
  2876
        // To be used on any nodes that have been rendered by a template and have been inserted into some parent element
moel@348
  2877
        // Walks through continuousNodeArray (which *must* be continuous, i.e., an uninterrupted sequence of sibling nodes, because
moel@348
  2878
        // the algorithm for walking them relies on this), and for each top-level item in the virtual-element sense,
moel@348
  2879
        // (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings
moel@348
  2880
        // (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting)
moel@348
  2881
moel@348
  2882
        if (continuousNodeArray.length) {
moel@348
  2883
            var firstNode = continuousNodeArray[0], lastNode = continuousNodeArray[continuousNodeArray.length - 1];
moel@348
  2884
moel@348
  2885
            // Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
moel@348
  2886
            // whereas a regular applyBindings won't introduce new memoized nodes
moel@348
  2887
            invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, function(node) {
moel@348
  2888
                ko.applyBindings(bindingContext, node);
moel@348
  2889
            });
moel@348
  2890
            invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, function(node) {
moel@348
  2891
                ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
moel@348
  2892
            });
moel@348
  2893
        }
moel@348
  2894
    }
moel@348
  2895
moel@348
  2896
    function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
moel@348
  2897
        return nodeOrNodeArray.nodeType ? nodeOrNodeArray
moel@348
  2898
                                        : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
moel@348
  2899
                                        : null;
moel@348
  2900
    }
moel@348
  2901
moel@348
  2902
    function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
moel@348
  2903
        options = options || {};
moel@348
  2904
        var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
moel@348
  2905
        var templateDocument = firstTargetNode && firstTargetNode.ownerDocument;
moel@348
  2906
        var templateEngineToUse = (options['templateEngine'] || _templateEngine);
moel@348
  2907
        ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument);
moel@348
  2908
        var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument);
moel@348
  2909
moel@348
  2910
        // Loosely check result is an array of DOM nodes
moel@348
  2911
        if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
moel@348
  2912
            throw new Error("Template engine must return an array of DOM nodes");
moel@348
  2913
moel@348
  2914
        var haveAddedNodesToParent = false;
moel@348
  2915
        switch (renderMode) {
moel@348
  2916
            case "replaceChildren":
moel@348
  2917
                ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
moel@348
  2918
                haveAddedNodesToParent = true;
moel@348
  2919
                break;
moel@348
  2920
            case "replaceNode":
moel@348
  2921
                ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
moel@348
  2922
                haveAddedNodesToParent = true;
moel@348
  2923
                break;
moel@348
  2924
            case "ignoreTargetNode": break;
moel@348
  2925
            default:
moel@348
  2926
                throw new Error("Unknown renderMode: " + renderMode);
moel@348
  2927
        }
moel@348
  2928
moel@348
  2929
        if (haveAddedNodesToParent) {
moel@348
  2930
            activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
moel@348
  2931
            if (options['afterRender'])
moel@348
  2932
                options['afterRender'](renderedNodesArray, bindingContext['$data']);
moel@348
  2933
        }
moel@348
  2934
moel@348
  2935
        return renderedNodesArray;
moel@348
  2936
    }
moel@348
  2937
moel@348
  2938
    ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
moel@348
  2939
        options = options || {};
moel@348
  2940
        if ((options['templateEngine'] || _templateEngine) == undefined)
moel@348
  2941
            throw new Error("Set a template engine before calling renderTemplate");
moel@348
  2942
        renderMode = renderMode || "replaceChildren";
moel@348
  2943
moel@348
  2944
        if (targetNodeOrNodeArray) {
moel@348
  2945
            var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
moel@348
  2946
moel@348
  2947
            var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
moel@348
  2948
            var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
moel@348
  2949
moel@348
  2950
            return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
moel@348
  2951
                function () {
moel@348
  2952
                    // Ensure we've got a proper binding context to work with
moel@348
  2953
                    var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
moel@348
  2954
                        ? dataOrBindingContext
moel@348
  2955
                        : new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
moel@348
  2956
moel@348
  2957
                    // Support selecting template as a function of the data being rendered
moel@348
  2958
                    var templateName = typeof(template) == 'function' ? template(bindingContext['$data']) : template;
moel@348
  2959
moel@348
  2960
                    var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
moel@348
  2961
                    if (renderMode == "replaceNode") {
moel@348
  2962
                        targetNodeOrNodeArray = renderedNodesArray;
moel@348
  2963
                        firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
moel@348
  2964
                    }
moel@348
  2965
                },
moel@348
  2966
                null,
moel@348
  2967
                { 'disposeWhen': whenToDispose, 'disposeWhenNodeIsRemoved': activelyDisposeWhenNodeIsRemoved }
moel@348
  2968
            );
moel@348
  2969
        } else {
moel@348
  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
moel@348
  2971
            return ko.memoization.memoize(function (domNode) {
moel@348
  2972
                ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
moel@348
  2973
            });
moel@348
  2974
        }
moel@348
  2975
    };
moel@348
  2976
moel@348
  2977
    ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
moel@348
  2978
        // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
moel@348
  2979
        // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
moel@348
  2980
        var arrayItemContext;
moel@348
  2981
moel@348
  2982
        // This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
moel@348
  2983
        var executeTemplateForArrayItem = function (arrayValue, index) {
moel@348
  2984
            // Support selecting template as a function of the data being rendered
moel@348
  2985
            var templateName = typeof(template) == 'function' ? template(arrayValue) : template;
moel@348
  2986
            arrayItemContext = parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue));
moel@348
  2987
            arrayItemContext['$index'] = index;
moel@348
  2988
            return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
moel@348
  2989
        }
moel@348
  2990
moel@348
  2991
        // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
moel@348
  2992
        var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
moel@348
  2993
            activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
moel@348
  2994
            if (options['afterRender'])
moel@348
  2995
                options['afterRender'](addedNodesArray, arrayValue);
moel@348
  2996
        };
moel@348
  2997
moel@348
  2998
        return ko.dependentObservable(function () {
moel@348
  2999
            var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
moel@348
  3000
            if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
moel@348
  3001
                unwrappedArray = [unwrappedArray];
moel@348
  3002
moel@348
  3003
            // Filter out any entries marked as destroyed
moel@348
  3004
            var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
moel@348
  3005
                return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
moel@348
  3006
            });
moel@348
  3007
moel@348
  3008
            ko.utils.setDomNodeChildrenFromArrayMapping(targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback);
moel@348
  3009
moel@348
  3010
        }, null, { 'disposeWhenNodeIsRemoved': targetNode });
moel@348
  3011
    };
moel@348
  3012
moel@348
  3013
    var templateSubscriptionDomDataKey = '__ko__templateSubscriptionDomDataKey__';
moel@348
  3014
    function disposeOldSubscriptionAndStoreNewOne(element, newSubscription) {
moel@348
  3015
        var oldSubscription = ko.utils.domData.get(element, templateSubscriptionDomDataKey);
moel@348
  3016
        if (oldSubscription && (typeof(oldSubscription.dispose) == 'function'))
moel@348
  3017
            oldSubscription.dispose();
moel@348
  3018
        ko.utils.domData.set(element, templateSubscriptionDomDataKey, newSubscription);
moel@348
  3019
    }
moel@348
  3020
moel@348
  3021
    ko.bindingHandlers['template'] = {
moel@348
  3022
        'init': function(element, valueAccessor) {
moel@348
  3023
            // Support anonymous templates
moel@348
  3024
            var bindingValue = ko.utils.unwrapObservable(valueAccessor());
moel@348
  3025
            if ((typeof bindingValue != "string") && (!bindingValue['name']) && (element.nodeType == 1 || element.nodeType == 8)) {
moel@348
  3026
                // It's an anonymous template - store the element contents, then clear the element
moel@348
  3027
                var templateNodes = element.nodeType == 1 ? element.childNodes : ko.virtualElements.childNodes(element),
moel@348
  3028
                    container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
moel@348
  3029
                new ko.templateSources.anonymousTemplate(element)['nodes'](container);
moel@348
  3030
            }
moel@348
  3031
            return { 'controlsDescendantBindings': true };
moel@348
  3032
        },
moel@348
  3033
        'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
moel@348
  3034
            var bindingValue = ko.utils.unwrapObservable(valueAccessor());
moel@348
  3035
            var templateName;
moel@348
  3036
            var shouldDisplay = true;
moel@348
  3037
moel@348
  3038
            if (typeof bindingValue == "string") {
moel@348
  3039
                templateName = bindingValue;
moel@348
  3040
            } else {
moel@348
  3041
                templateName = bindingValue['name'];
moel@348
  3042
moel@348
  3043
                // Support "if"/"ifnot" conditions
moel@348
  3044
                if ('if' in bindingValue)
moel@348
  3045
                    shouldDisplay = shouldDisplay && ko.utils.unwrapObservable(bindingValue['if']);
moel@348
  3046
                if ('ifnot' in bindingValue)
moel@348
  3047
                    shouldDisplay = shouldDisplay && !ko.utils.unwrapObservable(bindingValue['ifnot']);
moel@348
  3048
            }
moel@348
  3049
moel@348
  3050
            var templateSubscription = null;
moel@348
  3051
moel@348
  3052
            if ((typeof bindingValue === 'object') && ('foreach' in bindingValue)) { // Note: can't use 'in' operator on strings
moel@348
  3053
                // Render once for each data point (treating data set as empty if shouldDisplay==false)
moel@348
  3054
                var dataArray = (shouldDisplay && bindingValue['foreach']) || [];
moel@348
  3055
                templateSubscription = ko.renderTemplateForEach(templateName || element, dataArray, /* options: */ bindingValue, element, bindingContext);
moel@348
  3056
            } else {
moel@348
  3057
                if (shouldDisplay) {
moel@348
  3058
                    // Render once for this single data point (or use the viewModel if no data was provided)
moel@348
  3059
                    var innerBindingContext = (typeof bindingValue == 'object') && ('data' in bindingValue)
moel@348
  3060
                        ? bindingContext['createChildContext'](ko.utils.unwrapObservable(bindingValue['data'])) // Given an explitit 'data' value, we create a child binding context for it
moel@348
  3061
                        : bindingContext;                                                                       // Given no explicit 'data' value, we retain the same binding context
moel@348
  3062
                    templateSubscription = ko.renderTemplate(templateName || element, innerBindingContext, /* options: */ bindingValue, element);
moel@348
  3063
                } else
moel@348
  3064
                    ko.virtualElements.emptyNode(element);
moel@348
  3065
            }
moel@348
  3066
moel@348
  3067
            // It only makes sense to have a single template subscription per element (otherwise which one should have its output displayed?)
moel@348
  3068
            disposeOldSubscriptionAndStoreNewOne(element, templateSubscription);
moel@348
  3069
        }
moel@348
  3070
    };
moel@348
  3071
moel@348
  3072
    // Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
moel@348
  3073
    ko.jsonExpressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
moel@348
  3074
        var parsedBindingValue = ko.jsonExpressionRewriting.parseObjectLiteral(bindingValue);
moel@348
  3075
moel@348
  3076
        if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
moel@348
  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)
moel@348
  3078
moel@348
  3079
        if (ko.jsonExpressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
moel@348
  3080
            return null; // Named templates can be rewritten, so return "no error"
moel@348
  3081
        return "This template engine does not support anonymous templates nested within its templates";
moel@348
  3082
    };
moel@348
  3083
moel@348
  3084
    ko.virtualElements.allowedBindings['template'] = true;
moel@348
  3085
})();
moel@348
  3086
moel@348
  3087
ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
moel@348
  3088
ko.exportSymbol('renderTemplate', ko.renderTemplate);
moel@348
  3089
moel@348
  3090
(function () {
moel@348
  3091
    // Simple calculation based on Levenshtein distance.
moel@348
  3092
    function calculateEditDistanceMatrix(oldArray, newArray, maxAllowedDistance) {
moel@348
  3093
        var distances = [];
moel@348
  3094
        for (var i = 0; i <= newArray.length; i++)
moel@348
  3095
            distances[i] = [];
moel@348
  3096
moel@348
  3097
        // Top row - transform old array into empty array via deletions
moel@348
  3098
        for (var i = 0, j = Math.min(oldArray.length, maxAllowedDistance); i <= j; i++)
moel@348
  3099
            distances[0][i] = i;
moel@348
  3100
moel@348
  3101
        // Left row - transform empty array into new array via additions
moel@348
  3102
        for (var i = 1, j = Math.min(newArray.length, maxAllowedDistance); i <= j; i++) {
moel@348
  3103
            distances[i][0] = i;
moel@348
  3104
        }
moel@348
  3105
moel@348
  3106
        // Fill out the body of the array
moel@348
  3107
        var oldIndex, oldIndexMax = oldArray.length, newIndex, newIndexMax = newArray.length;
moel@348
  3108
        var distanceViaAddition, distanceViaDeletion;
moel@348
  3109
        for (oldIndex = 1; oldIndex <= oldIndexMax; oldIndex++) {
moel@348
  3110
            var newIndexMinForRow = Math.max(1, oldIndex - maxAllowedDistance);
moel@348
  3111
            var newIndexMaxForRow = Math.min(newIndexMax, oldIndex + maxAllowedDistance);
moel@348
  3112
            for (newIndex = newIndexMinForRow; newIndex <= newIndexMaxForRow; newIndex++) {
moel@348
  3113
                if (oldArray[oldIndex - 1] === newArray[newIndex - 1])
moel@348
  3114
                    distances[newIndex][oldIndex] = distances[newIndex - 1][oldIndex - 1];
moel@348
  3115
                else {
moel@348
  3116
                    var northDistance = distances[newIndex - 1][oldIndex] === undefined ? Number.MAX_VALUE : distances[newIndex - 1][oldIndex] + 1;
moel@348
  3117
                    var westDistance = distances[newIndex][oldIndex - 1] === undefined ? Number.MAX_VALUE : distances[newIndex][oldIndex - 1] + 1;
moel@348
  3118
                    distances[newIndex][oldIndex] = Math.min(northDistance, westDistance);
moel@348
  3119
                }
moel@348
  3120
            }
moel@348
  3121
        }
moel@348
  3122
moel@348
  3123
        return distances;
moel@348
  3124
    }
moel@348
  3125
moel@348
  3126
    function findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray) {
moel@348
  3127
        var oldIndex = oldArray.length;
moel@348
  3128
        var newIndex = newArray.length;
moel@348
  3129
        var editScript = [];
moel@348
  3130
        var maxDistance = editDistanceMatrix[newIndex][oldIndex];
moel@348
  3131
        if (maxDistance === undefined)
moel@348
  3132
            return null; // maxAllowedDistance must be too small
moel@348
  3133
        while ((oldIndex > 0) || (newIndex > 0)) {
moel@348
  3134
            var me = editDistanceMatrix[newIndex][oldIndex];
moel@348
  3135
            var distanceViaAdd = (newIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex] : maxDistance + 1;
moel@348
  3136
            var distanceViaDelete = (oldIndex > 0) ? editDistanceMatrix[newIndex][oldIndex - 1] : maxDistance + 1;
moel@348
  3137
            var distanceViaRetain = (newIndex > 0) && (oldIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex - 1] : maxDistance + 1;
moel@348
  3138
            if ((distanceViaAdd === undefined) || (distanceViaAdd < me - 1)) distanceViaAdd = maxDistance + 1;
moel@348
  3139
            if ((distanceViaDelete === undefined) || (distanceViaDelete < me - 1)) distanceViaDelete = maxDistance + 1;
moel@348
  3140
            if (distanceViaRetain < me - 1) distanceViaRetain = maxDistance + 1;
moel@348
  3141
moel@348
  3142
            if ((distanceViaAdd <= distanceViaDelete) && (distanceViaAdd < distanceViaRetain)) {
moel@348
  3143
                editScript.push({ status: "added", value: newArray[newIndex - 1] });
moel@348
  3144
                newIndex--;
moel@348
  3145
            } else if ((distanceViaDelete < distanceViaAdd) && (distanceViaDelete < distanceViaRetain)) {
moel@348
  3146
                editScript.push({ status: "deleted", value: oldArray[oldIndex - 1] });
moel@348
  3147
                oldIndex--;
moel@348
  3148
            } else {
moel@348
  3149
                editScript.push({ status: "retained", value: oldArray[oldIndex - 1] });
moel@348
  3150
                newIndex--;
moel@348
  3151
                oldIndex--;
moel@348
  3152
            }
moel@348
  3153
        }
moel@348
  3154
        return editScript.reverse();
moel@348
  3155
    }
moel@348
  3156
moel@348
  3157
    ko.utils.compareArrays = function (oldArray, newArray, maxEditsToConsider) {
moel@348
  3158
        if (maxEditsToConsider === undefined) {
moel@348
  3159
            return ko.utils.compareArrays(oldArray, newArray, 1)                 // First consider likely case where there is at most one edit (very fast)
moel@348
  3160
                || ko.utils.compareArrays(oldArray, newArray, 10)                // If that fails, account for a fair number of changes while still being fast
moel@348
  3161
                || ko.utils.compareArrays(oldArray, newArray, Number.MAX_VALUE); // Ultimately give the right answer, even though it may take a long time
moel@348
  3162
        } else {
moel@348
  3163
            oldArray = oldArray || [];
moel@348
  3164
            newArray = newArray || [];
moel@348
  3165
            var editDistanceMatrix = calculateEditDistanceMatrix(oldArray, newArray, maxEditsToConsider);
moel@348
  3166
            return findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray);
moel@348
  3167
        }
moel@348
  3168
    };
moel@348
  3169
})();
moel@348
  3170
moel@348
  3171
ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
moel@348
  3172
moel@348
  3173
(function () {
moel@348
  3174
    // Objective:
moel@348
  3175
    // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
moel@348
  3176
    //   map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
moel@348
  3177
    // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
moel@348
  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
moel@348
  3179
    //   previously mapped - retain those nodes, and just insert/delete other ones
moel@348
  3180
moel@348
  3181
    // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
moel@348
  3182
    // You can use this, for example, to activate bindings on those nodes.
moel@348
  3183
moel@348
  3184
    function fixUpVirtualElements(contiguousNodeArray) {
moel@348
  3185
        // Ensures that contiguousNodeArray really *is* an array of contiguous siblings, even if some of the interior
moel@348
  3186
        // ones have changed since your array was first built (e.g., because your array contains virtual elements, and
moel@348
  3187
        // their virtual children changed when binding was applied to them).
moel@348
  3188
        // This is needed so that we can reliably remove or update the nodes corresponding to a given array item
moel@348
  3189
moel@348
  3190
        if (contiguousNodeArray.length > 2) {
moel@348
  3191
            // Build up the actual new contiguous node set
moel@348
  3192
            var current = contiguousNodeArray[0], last = contiguousNodeArray[contiguousNodeArray.length - 1], newContiguousSet = [current];
moel@348
  3193
            while (current !== last) {
moel@348
  3194
                current = current.nextSibling;
moel@348
  3195
                if (!current) // Won't happen, except if the developer has manually removed some DOM elements (then we're in an undefined scenario)
moel@348
  3196
                    return;
moel@348
  3197
                newContiguousSet.push(current);
moel@348
  3198
            }
moel@348
  3199
moel@348
  3200
            // ... then mutate the input array to match this.
moel@348
  3201
            // (The following line replaces the contents of contiguousNodeArray with newContiguousSet)
moel@348
  3202
            Array.prototype.splice.apply(contiguousNodeArray, [0, contiguousNodeArray.length].concat(newContiguousSet));
moel@348
  3203
        }
moel@348
  3204
    }
moel@348
  3205
moel@348
  3206
    function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
moel@348
  3207
        // Map this array value inside a dependentObservable so we re-map when any dependency changes
moel@348
  3208
        var mappedNodes = [];
moel@348
  3209
        var dependentObservable = ko.dependentObservable(function() {
moel@348
  3210
            var newMappedNodes = mapping(valueToMap, index) || [];
moel@348
  3211
moel@348
  3212
            // On subsequent evaluations, just replace the previously-inserted DOM nodes
moel@348
  3213
            if (mappedNodes.length > 0) {
moel@348
  3214
                fixUpVirtualElements(mappedNodes);
moel@348
  3215
                ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
moel@348
  3216
                if (callbackAfterAddingNodes)
moel@348
  3217
                    callbackAfterAddingNodes(valueToMap, newMappedNodes);
moel@348
  3218
            }
moel@348
  3219
moel@348
  3220
            // Replace the contents of the mappedNodes array, thereby updating the record
moel@348
  3221
            // of which nodes would be deleted if valueToMap was itself later removed
moel@348
  3222
            mappedNodes.splice(0, mappedNodes.length);
moel@348
  3223
            ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
moel@348
  3224
        }, null, { 'disposeWhenNodeIsRemoved': containerNode, 'disposeWhen': function() { return (mappedNodes.length == 0) || !ko.utils.domNodeIsAttachedToDocument(mappedNodes[0]) } });
moel@348
  3225
        return { mappedNodes : mappedNodes, dependentObservable : dependentObservable };
moel@348
  3226
    }
moel@348
  3227
moel@348
  3228
    var lastMappingResultDomDataKey = "setDomNodeChildrenFromArrayMapping_lastMappingResult";
moel@348
  3229
moel@348
  3230
    ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
moel@348
  3231
        // Compare the provided array against the previous one
moel@348
  3232
        array = array || [];
moel@348
  3233
        options = options || {};
moel@348
  3234
        var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
moel@348
  3235
        var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
moel@348
  3236
        var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
moel@348
  3237
        var editScript = ko.utils.compareArrays(lastArray, array);
moel@348
  3238
moel@348
  3239
        // Build the new mapping result
moel@348
  3240
        var newMappingResult = [];
moel@348
  3241
        var lastMappingResultIndex = 0;
moel@348
  3242
        var nodesToDelete = [];
moel@348
  3243
        var newMappingResultIndex = 0;
moel@348
  3244
        var nodesAdded = [];
moel@348
  3245
        var insertAfterNode = null;
moel@348
  3246
        for (var i = 0, j = editScript.length; i < j; i++) {
moel@348
  3247
            switch (editScript[i].status) {
moel@348
  3248
                case "retained":
moel@348
  3249
                    // Just keep the information - don't touch the nodes
moel@348
  3250
                    var dataToRetain = lastMappingResult[lastMappingResultIndex];
moel@348
  3251
                    dataToRetain.indexObservable(newMappingResultIndex);
moel@348
  3252
                    newMappingResultIndex = newMappingResult.push(dataToRetain);
moel@348
  3253
                    if (dataToRetain.domNodes.length > 0)
moel@348
  3254
                        insertAfterNode = dataToRetain.domNodes[dataToRetain.domNodes.length - 1];
moel@348
  3255
                    lastMappingResultIndex++;
moel@348
  3256
                    break;
moel@348
  3257
moel@348
  3258
                case "deleted":
moel@348
  3259
                    // Stop tracking changes to the mapping for these nodes
moel@348
  3260
                    lastMappingResult[lastMappingResultIndex].dependentObservable.dispose();
moel@348
  3261
moel@348
  3262
                    // Queue these nodes for later removal
moel@348
  3263
                    fixUpVirtualElements(lastMappingResult[lastMappingResultIndex].domNodes);
moel@348
  3264
                    ko.utils.arrayForEach(lastMappingResult[lastMappingResultIndex].domNodes, function (node) {
moel@348
  3265
                        nodesToDelete.push({
moel@348
  3266
                          element: node,
moel@348
  3267
                          index: i,
moel@348
  3268
                          value: editScript[i].value
moel@348
  3269
                        });
moel@348
  3270
                        insertAfterNode = node;
moel@348
  3271
                    });
moel@348
  3272
                    lastMappingResultIndex++;
moel@348
  3273
                    break;
moel@348
  3274
moel@348
  3275
                case "added":
moel@348
  3276
                    var valueToMap = editScript[i].value;
moel@348
  3277
                    var indexObservable = ko.observable(newMappingResultIndex);
moel@348
  3278
                    var mapData = mapNodeAndRefreshWhenChanged(domNode, mapping, valueToMap, callbackAfterAddingNodes, indexObservable);
moel@348
  3279
                    var mappedNodes = mapData.mappedNodes;
moel@348
  3280
moel@348
  3281
                    // On the first evaluation, insert the nodes at the current insertion point
moel@348
  3282
                    newMappingResultIndex = newMappingResult.push({
moel@348
  3283
                        arrayEntry: editScript[i].value,
moel@348
  3284
                        domNodes: mappedNodes,
moel@348
  3285
                        dependentObservable: mapData.dependentObservable,
moel@348
  3286
                        indexObservable: indexObservable
moel@348
  3287
                    });
moel@348
  3288
                    for (var nodeIndex = 0, nodeIndexMax = mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) {
moel@348
  3289
                        var node = mappedNodes[nodeIndex];
moel@348
  3290
                        nodesAdded.push({
moel@348
  3291
                          element: node,
moel@348
  3292
                          index: i,
moel@348
  3293
                          value: editScript[i].value
moel@348
  3294
                        });
moel@348
  3295
                        if (insertAfterNode == null) {
moel@348
  3296
                            // Insert "node" (the newly-created node) as domNode's first child
moel@348
  3297
                            ko.virtualElements.prepend(domNode, node);
moel@348
  3298
                        } else {
moel@348
  3299
                            // Insert "node" into "domNode" immediately after "insertAfterNode"
moel@348
  3300
                            ko.virtualElements.insertAfter(domNode, node, insertAfterNode);
moel@348
  3301
                        }
moel@348
  3302
                        insertAfterNode = node;
moel@348
  3303
                    }
moel@348
  3304
                    if (callbackAfterAddingNodes)
moel@348
  3305
                        callbackAfterAddingNodes(valueToMap, mappedNodes, indexObservable);
moel@348
  3306
                    break;
moel@348
  3307
            }
moel@348
  3308
        }
moel@348
  3309
moel@348
  3310
        ko.utils.arrayForEach(nodesToDelete, function (node) { ko.cleanNode(node.element) });
moel@348
  3311
moel@348
  3312
        var invokedBeforeRemoveCallback = false;
moel@348
  3313
        if (!isFirstExecution) {
moel@348
  3314
            if (options['afterAdd']) {
moel@348
  3315
                for (var i = 0; i < nodesAdded.length; i++)
moel@348
  3316
                    options['afterAdd'](nodesAdded[i].element, nodesAdded[i].index, nodesAdded[i].value);
moel@348
  3317
            }
moel@348
  3318
            if (options['beforeRemove']) {
moel@348
  3319
                for (var i = 0; i < nodesToDelete.length; i++)
moel@348
  3320
                    options['beforeRemove'](nodesToDelete[i].element, nodesToDelete[i].index, nodesToDelete[i].value);
moel@348
  3321
                invokedBeforeRemoveCallback = true;
moel@348
  3322
            }
moel@348
  3323
        }
moel@348
  3324
        if (!invokedBeforeRemoveCallback && nodesToDelete.length) {
moel@348
  3325
            for (var i = 0; i < nodesToDelete.length; i++) {
moel@348
  3326
                var element = nodesToDelete[i].element;
moel@348
  3327
                if (element.parentNode)
moel@348
  3328
                    element.parentNode.removeChild(element);
moel@348
  3329
            }
moel@348
  3330
        }
moel@348
  3331
moel@348
  3332
        // Store a copy of the array items we just considered so we can difference it next time
moel@348
  3333
        ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
moel@348
  3334
    }
moel@348
  3335
})();
moel@348
  3336
moel@348
  3337
ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
moel@348
  3338
ko.nativeTemplateEngine = function () {
moel@348
  3339
    this['allowTemplateRewriting'] = false;
moel@348
  3340
}
moel@348
  3341
moel@348
  3342
ko.nativeTemplateEngine.prototype = new ko.templateEngine();
moel@348
  3343
ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
moel@348
  3344
    var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly
moel@348
  3345
        templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null,
moel@348
  3346
        templateNodes = templateNodesFunc ? templateSource['nodes']() : null;
moel@348
  3347
moel@348
  3348
    if (templateNodes) {
moel@348
  3349
        return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes);
moel@348
  3350
    } else {
moel@348
  3351
        var templateText = templateSource['text']();
moel@348
  3352
        return ko.utils.parseHtmlFragment(templateText);
moel@348
  3353
    }
moel@348
  3354
};
moel@348
  3355
moel@348
  3356
ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine();
moel@348
  3357
ko.setTemplateEngine(ko.nativeTemplateEngine.instance);
moel@348
  3358
moel@348
  3359
ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
moel@348
  3360
(function() {
moel@348
  3361
    ko.jqueryTmplTemplateEngine = function () {
moel@348
  3362
        // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
moel@348
  3363
        // doesn't expose a version number, so we have to infer it.
moel@348
  3364
        // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
moel@348
  3365
        // which KO internally refers to as version "2", so older versions are no longer detected.
moel@348
  3366
        var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
moel@348
  3367
            if ((typeof(jQuery) == "undefined") || !(jQuery['tmpl']))
moel@348
  3368
                return 0;
moel@348
  3369
            // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
moel@348
  3370
            try {
moel@348
  3371
                if (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
moel@348
  3372
                    // Since 1.0.0pre, custom tags should append markup to an array called "__"
moel@348
  3373
                    return 2; // Final version of jquery.tmpl
moel@348
  3374
                }
moel@348
  3375
            } catch(ex) { /* Apparently not the version we were looking for */ }
moel@348
  3376
moel@348
  3377
            return 1; // Any older version that we don't support
moel@348
  3378
        })();
moel@348
  3379
moel@348
  3380
        function ensureHasReferencedJQueryTemplates() {
moel@348
  3381
            if (jQueryTmplVersion < 2)
moel@348
  3382
                throw new Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");
moel@348
  3383
        }
moel@348
  3384
moel@348
  3385
        function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
moel@348
  3386
            return jQuery['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
moel@348
  3387
        }
moel@348
  3388
moel@348
  3389
        this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
moel@348
  3390
            options = options || {};
moel@348
  3391
            ensureHasReferencedJQueryTemplates();
moel@348
  3392
moel@348
  3393
            // Ensure we have stored a precompiled version of this template (don't want to reparse on every render)
moel@348
  3394
            var precompiled = templateSource['data']('precompiled');
moel@348
  3395
            if (!precompiled) {
moel@348
  3396
                var templateText = templateSource['text']() || "";
moel@348
  3397
                // Wrap in "with($whatever.koBindingContext) { ... }"
moel@348
  3398
                templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
moel@348
  3399
moel@348
  3400
                precompiled = jQuery['template'](null, templateText);
moel@348
  3401
                templateSource['data']('precompiled', precompiled);
moel@348
  3402
            }
moel@348
  3403
moel@348
  3404
            var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
moel@348
  3405
            var jQueryTemplateOptions = jQuery['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
moel@348
  3406
moel@348
  3407
            var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
moel@348
  3408
            resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
moel@348
  3409
moel@348
  3410
            jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
moel@348
  3411
            return resultNodes;
moel@348
  3412
        };
moel@348
  3413
moel@348
  3414
        this['createJavaScriptEvaluatorBlock'] = function(script) {
moel@348
  3415
            return "{{ko_code ((function() { return " + script + " })()) }}";
moel@348
  3416
        };
moel@348
  3417
moel@348
  3418
        this['addTemplate'] = function(templateName, templateMarkup) {
moel@348
  3419
            document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
moel@348
  3420
        };
moel@348
  3421
moel@348
  3422
        if (jQueryTmplVersion > 0) {
moel@348
  3423
            jQuery['tmpl']['tag']['ko_code'] = {
moel@348
  3424
                open: "__.push($1 || '');"
moel@348
  3425
            };
moel@348
  3426
            jQuery['tmpl']['tag']['ko_with'] = {
moel@348
  3427
                open: "with($1) {",
moel@348
  3428
                close: "} "
moel@348
  3429
            };
moel@348
  3430
        }
moel@348
  3431
    };
moel@348
  3432
moel@348
  3433
    ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
moel@348
  3434
moel@348
  3435
    // Use this one by default *only if jquery.tmpl is referenced*
moel@348
  3436
    var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
moel@348
  3437
    if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
moel@348
  3438
        ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
moel@348
  3439
moel@348
  3440
    ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
moel@348
  3441
})();
moel@348
  3442
});
moel@348
  3443
})(window,document,navigator);