ko4j/src/main/resources/org/netbeans/html/ko4j/knockout-3.2.0.debug.js
author Jaroslav Tulach <jtulach@netbeans.org>
Tue, 26 Aug 2014 18:22:37 +0200
changeset 839 7b0f9b77670a
parent 816 41979bf458e7
child 840 177e6467c0c3
permissions -rw-r--r--
MIT allows sublicensing, so let's re-distribute the knockout.js under our license while keeping Steven Sanderson copyright.
jtulach@839
     1
/*
jtulach@839
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
jtulach@839
     3
 *
jtulach@839
     4
 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
jtulach@839
     5
 *
jtulach@839
     6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
jtulach@839
     7
 * Other names may be trademarks of their respective owners.
jtulach@839
     8
 *
jtulach@839
     9
 * The contents of this file are subject to the terms of either the GNU
jtulach@839
    10
 * General Public License Version 2 only ("GPL") or the Common
jtulach@839
    11
 * Development and Distribution License("CDDL") (collectively, the
jtulach@839
    12
 * "License"). You may not use this file except in compliance with the
jtulach@839
    13
 * License. You can obtain a copy of the License at
jtulach@839
    14
 * http://www.netbeans.org/cddl-gplv2.html
jtulach@839
    15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
jtulach@839
    16
 * specific language governing permissions and limitations under the
jtulach@839
    17
 * License.  When distributing the software, include this License Header
jtulach@839
    18
 * Notice in each file and include the License file at
jtulach@839
    19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
jtulach@839
    20
 * particular file as subject to the "Classpath" exception as provided
jtulach@839
    21
 * by Oracle in the GPL Version 2 section of the License file that
jtulach@839
    22
 * accompanied this code. If applicable, add the following below the
jtulach@839
    23
 * License Header, with the fields enclosed by brackets [] replaced by
jtulach@839
    24
 * your own identifying information:
jtulach@839
    25
 * "Portions Copyrighted [year] [name of copyright owner]"
jtulach@839
    26
 *
jtulach@839
    27
 * Contributor(s):
jtulach@839
    28
 *
jtulach@839
    29
 * The Original Software is NetBeans. The Initial Developer of the Original
jtulach@839
    30
 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
jtulach@839
    31
 *
jtulach@839
    32
 * If you wish your version of this file to be governed by only the CDDL
jtulach@839
    33
 * or only the GPL Version 2, indicate your decision by adding
jtulach@839
    34
 * "[Contributor] elects to include this software in this distribution
jtulach@839
    35
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
jtulach@839
    36
 * single choice of license, a recipient has the option to distribute
jtulach@839
    37
 * your version of this file under either the CDDL, the GPL Version 2 or
jtulach@839
    38
 * to extend the choice of license to its licensees as provided above.
jtulach@839
    39
 * However, if you add GPL Version 2 code and therefore, elected the GPL
jtulach@839
    40
 * Version 2 license, then the option applies only if the new code is
jtulach@839
    41
 * made subject to such option by the copyright holder.
jtulach@839
    42
 */
jtulach@839
    43
jtulach@816
    44
/*!
jtulach@816
    45
 * Knockout JavaScript library v3.2.0
jtulach@816
    46
 * (c) Steven Sanderson - http://knockoutjs.com/
jtulach@816
    47
 * License: MIT (http://www.opensource.org/licenses/mit-license.php)
jtulach@816
    48
 */
jtulach@816
    49
jtulach@816
    50
(function(){
jtulach@816
    51
var DEBUG=true;
jtulach@816
    52
(function(undefined){
jtulach@816
    53
    // (0, eval)('this') is a robust way of getting a reference to the global object
jtulach@816
    54
    // For details, see http://stackoverflow.com/questions/14119988/return-this-0-evalthis/14120023#14120023
jtulach@816
    55
    var window = this || (0, eval)('this'),
jtulach@816
    56
        document = window['document'],
jtulach@816
    57
        navigator = window['navigator'],
jtulach@816
    58
        jQueryInstance = window["jQuery"],
jtulach@816
    59
        JSON = window["JSON"];
jtulach@816
    60
(function(factory) {
jtulach@816
    61
    // Support three module loading scenarios
jtulach@816
    62
    if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
jtulach@816
    63
        // [1] CommonJS/Node.js
jtulach@816
    64
        var target = module['exports'] || exports; // module.exports is for Node.js
jtulach@816
    65
        factory(target, require);
jtulach@816
    66
    } else if (typeof define === 'function' && define['amd']) {
jtulach@816
    67
        // [2] AMD anonymous module
jtulach@816
    68
        define(['exports', 'require'], factory);
jtulach@816
    69
    } else {
jtulach@816
    70
        // [3] No module loader (plain <script> tag) - put directly in global namespace
jtulach@816
    71
        factory(window['ko'] = {});
jtulach@816
    72
    }
jtulach@816
    73
}(function(koExports, require){
jtulach@816
    74
// Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
jtulach@816
    75
// In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
jtulach@816
    76
var ko = typeof koExports !== 'undefined' ? koExports : {};
jtulach@816
    77
// Google Closure Compiler helpers (used only to make the minified file smaller)
jtulach@816
    78
ko.exportSymbol = function(koPath, object) {
jtulach@816
    79
    var tokens = koPath.split(".");
jtulach@816
    80
jtulach@816
    81
    // In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable)
jtulach@816
    82
    // At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko)
jtulach@816
    83
    var target = ko;
jtulach@816
    84
jtulach@816
    85
    for (var i = 0; i < tokens.length - 1; i++)
jtulach@816
    86
        target = target[tokens[i]];
jtulach@816
    87
    target[tokens[tokens.length - 1]] = object;
jtulach@816
    88
};
jtulach@816
    89
ko.exportProperty = function(owner, publicName, object) {
jtulach@816
    90
    owner[publicName] = object;
jtulach@816
    91
};
jtulach@816
    92
ko.version = "3.2.0";
jtulach@816
    93
jtulach@816
    94
ko.exportSymbol('version', ko.version);
jtulach@816
    95
ko.utils = (function () {
jtulach@816
    96
    function objectForEach(obj, action) {
jtulach@816
    97
        for (var prop in obj) {
jtulach@816
    98
            if (obj.hasOwnProperty(prop)) {
jtulach@816
    99
                action(prop, obj[prop]);
jtulach@816
   100
            }
jtulach@816
   101
        }
jtulach@816
   102
    }
jtulach@816
   103
jtulach@816
   104
    function extend(target, source) {
jtulach@816
   105
        if (source) {
jtulach@816
   106
            for(var prop in source) {
jtulach@816
   107
                if(source.hasOwnProperty(prop)) {
jtulach@816
   108
                    target[prop] = source[prop];
jtulach@816
   109
                }
jtulach@816
   110
            }
jtulach@816
   111
        }
jtulach@816
   112
        return target;
jtulach@816
   113
    }
jtulach@816
   114
jtulach@816
   115
    function setPrototypeOf(obj, proto) {
jtulach@816
   116
        obj.__proto__ = proto;
jtulach@816
   117
        return obj;
jtulach@816
   118
    }
jtulach@816
   119
jtulach@816
   120
    var canSetPrototype = ({ __proto__: [] } instanceof Array);
jtulach@816
   121
jtulach@816
   122
    // 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)
jtulach@816
   123
    var knownEvents = {}, knownEventTypesByEventName = {};
jtulach@816
   124
    var keyEventTypeName = (navigator && /Firefox\/2/i.test(navigator.userAgent)) ? 'KeyboardEvent' : 'UIEvents';
jtulach@816
   125
    knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
jtulach@816
   126
    knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
jtulach@816
   127
    objectForEach(knownEvents, function(eventType, knownEventsForType) {
jtulach@816
   128
        if (knownEventsForType.length) {
jtulach@816
   129
            for (var i = 0, j = knownEventsForType.length; i < j; i++)
jtulach@816
   130
                knownEventTypesByEventName[knownEventsForType[i]] = eventType;
jtulach@816
   131
        }
jtulach@816
   132
    });
jtulach@816
   133
    var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
jtulach@816
   134
jtulach@816
   135
    // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
jtulach@816
   136
    // Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
jtulach@816
   137
    // Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
jtulach@816
   138
    // If there is a future need to detect specific versions of IE10+, we will amend this.
jtulach@816
   139
    var ieVersion = document && (function() {
jtulach@816
   140
        var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
jtulach@816
   141
jtulach@816
   142
        // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
jtulach@816
   143
        while (
jtulach@816
   144
            div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
jtulach@816
   145
            iElems[0]
jtulach@816
   146
        ) {}
jtulach@816
   147
        return version > 4 ? version : undefined;
jtulach@816
   148
    }());
jtulach@816
   149
    var isIe6 = ieVersion === 6,
jtulach@816
   150
        isIe7 = ieVersion === 7;
jtulach@816
   151
jtulach@816
   152
    function isClickOnCheckableElement(element, eventType) {
jtulach@816
   153
        if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false;
jtulach@816
   154
        if (eventType.toLowerCase() != "click") return false;
jtulach@816
   155
        var inputType = element.type;
jtulach@816
   156
        return (inputType == "checkbox") || (inputType == "radio");
jtulach@816
   157
    }
jtulach@816
   158
jtulach@816
   159
    return {
jtulach@816
   160
        fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
jtulach@816
   161
jtulach@816
   162
        arrayForEach: function (array, action) {
jtulach@816
   163
            for (var i = 0, j = array.length; i < j; i++)
jtulach@816
   164
                action(array[i], i);
jtulach@816
   165
        },
jtulach@816
   166
jtulach@816
   167
        arrayIndexOf: function (array, item) {
jtulach@816
   168
            if (typeof Array.prototype.indexOf == "function")
jtulach@816
   169
                return Array.prototype.indexOf.call(array, item);
jtulach@816
   170
            for (var i = 0, j = array.length; i < j; i++)
jtulach@816
   171
                if (array[i] === item)
jtulach@816
   172
                    return i;
jtulach@816
   173
            return -1;
jtulach@816
   174
        },
jtulach@816
   175
jtulach@816
   176
        arrayFirst: function (array, predicate, predicateOwner) {
jtulach@816
   177
            for (var i = 0, j = array.length; i < j; i++)
jtulach@816
   178
                if (predicate.call(predicateOwner, array[i], i))
jtulach@816
   179
                    return array[i];
jtulach@816
   180
            return null;
jtulach@816
   181
        },
jtulach@816
   182
jtulach@816
   183
        arrayRemoveItem: function (array, itemToRemove) {
jtulach@816
   184
            var index = ko.utils.arrayIndexOf(array, itemToRemove);
jtulach@816
   185
            if (index > 0) {
jtulach@816
   186
                array.splice(index, 1);
jtulach@816
   187
            }
jtulach@816
   188
            else if (index === 0) {
jtulach@816
   189
                array.shift();
jtulach@816
   190
            }
jtulach@816
   191
        },
jtulach@816
   192
jtulach@816
   193
        arrayGetDistinctValues: function (array) {
jtulach@816
   194
            array = array || [];
jtulach@816
   195
            var result = [];
jtulach@816
   196
            for (var i = 0, j = array.length; i < j; i++) {
jtulach@816
   197
                if (ko.utils.arrayIndexOf(result, array[i]) < 0)
jtulach@816
   198
                    result.push(array[i]);
jtulach@816
   199
            }
jtulach@816
   200
            return result;
jtulach@816
   201
        },
jtulach@816
   202
jtulach@816
   203
        arrayMap: function (array, mapping) {
jtulach@816
   204
            array = array || [];
jtulach@816
   205
            var result = [];
jtulach@816
   206
            for (var i = 0, j = array.length; i < j; i++)
jtulach@816
   207
                result.push(mapping(array[i], i));
jtulach@816
   208
            return result;
jtulach@816
   209
        },
jtulach@816
   210
jtulach@816
   211
        arrayFilter: function (array, predicate) {
jtulach@816
   212
            array = array || [];
jtulach@816
   213
            var result = [];
jtulach@816
   214
            for (var i = 0, j = array.length; i < j; i++)
jtulach@816
   215
                if (predicate(array[i], i))
jtulach@816
   216
                    result.push(array[i]);
jtulach@816
   217
            return result;
jtulach@816
   218
        },
jtulach@816
   219
jtulach@816
   220
        arrayPushAll: function (array, valuesToPush) {
jtulach@816
   221
            if (valuesToPush instanceof Array)
jtulach@816
   222
                array.push.apply(array, valuesToPush);
jtulach@816
   223
            else
jtulach@816
   224
                for (var i = 0, j = valuesToPush.length; i < j; i++)
jtulach@816
   225
                    array.push(valuesToPush[i]);
jtulach@816
   226
            return array;
jtulach@816
   227
        },
jtulach@816
   228
jtulach@816
   229
        addOrRemoveItem: function(array, value, included) {
jtulach@816
   230
            var existingEntryIndex = ko.utils.arrayIndexOf(ko.utils.peekObservable(array), value);
jtulach@816
   231
            if (existingEntryIndex < 0) {
jtulach@816
   232
                if (included)
jtulach@816
   233
                    array.push(value);
jtulach@816
   234
            } else {
jtulach@816
   235
                if (!included)
jtulach@816
   236
                    array.splice(existingEntryIndex, 1);
jtulach@816
   237
            }
jtulach@816
   238
        },
jtulach@816
   239
jtulach@816
   240
        canSetPrototype: canSetPrototype,
jtulach@816
   241
jtulach@816
   242
        extend: extend,
jtulach@816
   243
jtulach@816
   244
        setPrototypeOf: setPrototypeOf,
jtulach@816
   245
jtulach@816
   246
        setPrototypeOfOrExtend: canSetPrototype ? setPrototypeOf : extend,
jtulach@816
   247
jtulach@816
   248
        objectForEach: objectForEach,
jtulach@816
   249
jtulach@816
   250
        objectMap: function(source, mapping) {
jtulach@816
   251
            if (!source)
jtulach@816
   252
                return source;
jtulach@816
   253
            var target = {};
jtulach@816
   254
            for (var prop in source) {
jtulach@816
   255
                if (source.hasOwnProperty(prop)) {
jtulach@816
   256
                    target[prop] = mapping(source[prop], prop, source);
jtulach@816
   257
                }
jtulach@816
   258
            }
jtulach@816
   259
            return target;
jtulach@816
   260
        },
jtulach@816
   261
jtulach@816
   262
        emptyDomNode: function (domNode) {
jtulach@816
   263
            while (domNode.firstChild) {
jtulach@816
   264
                ko.removeNode(domNode.firstChild);
jtulach@816
   265
            }
jtulach@816
   266
        },
jtulach@816
   267
jtulach@816
   268
        moveCleanedNodesToContainerElement: function(nodes) {
jtulach@816
   269
            // Ensure it's a real array, as we're about to reparent the nodes and
jtulach@816
   270
            // we don't want the underlying collection to change while we're doing that.
jtulach@816
   271
            var nodesArray = ko.utils.makeArray(nodes);
jtulach@816
   272
jtulach@816
   273
            var container = document.createElement('div');
jtulach@816
   274
            for (var i = 0, j = nodesArray.length; i < j; i++) {
jtulach@816
   275
                container.appendChild(ko.cleanNode(nodesArray[i]));
jtulach@816
   276
            }
jtulach@816
   277
            return container;
jtulach@816
   278
        },
jtulach@816
   279
jtulach@816
   280
        cloneNodes: function (nodesArray, shouldCleanNodes) {
jtulach@816
   281
            for (var i = 0, j = nodesArray.length, newNodesArray = []; i < j; i++) {
jtulach@816
   282
                var clonedNode = nodesArray[i].cloneNode(true);
jtulach@816
   283
                newNodesArray.push(shouldCleanNodes ? ko.cleanNode(clonedNode) : clonedNode);
jtulach@816
   284
            }
jtulach@816
   285
            return newNodesArray;
jtulach@816
   286
        },
jtulach@816
   287
jtulach@816
   288
        setDomNodeChildren: function (domNode, childNodes) {
jtulach@816
   289
            ko.utils.emptyDomNode(domNode);
jtulach@816
   290
            if (childNodes) {
jtulach@816
   291
                for (var i = 0, j = childNodes.length; i < j; i++)
jtulach@816
   292
                    domNode.appendChild(childNodes[i]);
jtulach@816
   293
            }
jtulach@816
   294
        },
jtulach@816
   295
jtulach@816
   296
        replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
jtulach@816
   297
            var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
jtulach@816
   298
            if (nodesToReplaceArray.length > 0) {
jtulach@816
   299
                var insertionPoint = nodesToReplaceArray[0];
jtulach@816
   300
                var parent = insertionPoint.parentNode;
jtulach@816
   301
                for (var i = 0, j = newNodesArray.length; i < j; i++)
jtulach@816
   302
                    parent.insertBefore(newNodesArray[i], insertionPoint);
jtulach@816
   303
                for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
jtulach@816
   304
                    ko.removeNode(nodesToReplaceArray[i]);
jtulach@816
   305
                }
jtulach@816
   306
            }
jtulach@816
   307
        },
jtulach@816
   308
jtulach@816
   309
        fixUpContinuousNodeArray: function(continuousNodeArray, parentNode) {
jtulach@816
   310
            // Before acting on a set of nodes that were previously outputted by a template function, we have to reconcile
jtulach@816
   311
            // them against what is in the DOM right now. It may be that some of the nodes have already been removed, or that
jtulach@816
   312
            // new nodes might have been inserted in the middle, for example by a binding. Also, there may previously have been
jtulach@816
   313
            // leading comment nodes (created by rewritten string-based templates) that have since been removed during binding.
jtulach@816
   314
            // So, this function translates the old "map" output array into its best guess of the set of current DOM nodes.
jtulach@816
   315
            //
jtulach@816
   316
            // Rules:
jtulach@816
   317
            //   [A] Any leading nodes that have been removed should be ignored
jtulach@816
   318
            //       These most likely correspond to memoization nodes that were already removed during binding
jtulach@816
   319
            //       See https://github.com/SteveSanderson/knockout/pull/440
jtulach@816
   320
            //   [B] We want to output a continuous series of nodes. So, ignore any nodes that have already been removed,
jtulach@816
   321
            //       and include any nodes that have been inserted among the previous collection
jtulach@816
   322
jtulach@816
   323
            if (continuousNodeArray.length) {
jtulach@816
   324
                // The parent node can be a virtual element; so get the real parent node
jtulach@816
   325
                parentNode = (parentNode.nodeType === 8 && parentNode.parentNode) || parentNode;
jtulach@816
   326
jtulach@816
   327
                // Rule [A]
jtulach@816
   328
                while (continuousNodeArray.length && continuousNodeArray[0].parentNode !== parentNode)
jtulach@816
   329
                    continuousNodeArray.shift();
jtulach@816
   330
jtulach@816
   331
                // Rule [B]
jtulach@816
   332
                if (continuousNodeArray.length > 1) {
jtulach@816
   333
                    var current = continuousNodeArray[0], last = continuousNodeArray[continuousNodeArray.length - 1];
jtulach@816
   334
                    // Replace with the actual new continuous node set
jtulach@816
   335
                    continuousNodeArray.length = 0;
jtulach@816
   336
                    while (current !== last) {
jtulach@816
   337
                        continuousNodeArray.push(current);
jtulach@816
   338
                        current = current.nextSibling;
jtulach@816
   339
                        if (!current) // Won't happen, except if the developer has manually removed some DOM elements (then we're in an undefined scenario)
jtulach@816
   340
                            return;
jtulach@816
   341
                    }
jtulach@816
   342
                    continuousNodeArray.push(last);
jtulach@816
   343
                }
jtulach@816
   344
            }
jtulach@816
   345
            return continuousNodeArray;
jtulach@816
   346
        },
jtulach@816
   347
jtulach@816
   348
        setOptionNodeSelectionState: function (optionNode, isSelected) {
jtulach@816
   349
            // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
jtulach@816
   350
            if (ieVersion < 7)
jtulach@816
   351
                optionNode.setAttribute("selected", isSelected);
jtulach@816
   352
            else
jtulach@816
   353
                optionNode.selected = isSelected;
jtulach@816
   354
        },
jtulach@816
   355
jtulach@816
   356
        stringTrim: function (string) {
jtulach@816
   357
            return string === null || string === undefined ? '' :
jtulach@816
   358
                string.trim ?
jtulach@816
   359
                    string.trim() :
jtulach@816
   360
                    string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
jtulach@816
   361
        },
jtulach@816
   362
jtulach@816
   363
        stringStartsWith: function (string, startsWith) {
jtulach@816
   364
            string = string || "";
jtulach@816
   365
            if (startsWith.length > string.length)
jtulach@816
   366
                return false;
jtulach@816
   367
            return string.substring(0, startsWith.length) === startsWith;
jtulach@816
   368
        },
jtulach@816
   369
jtulach@816
   370
        domNodeIsContainedBy: function (node, containedByNode) {
jtulach@816
   371
            if (node === containedByNode)
jtulach@816
   372
                return true;
jtulach@816
   373
            if (node.nodeType === 11)
jtulach@816
   374
                return false; // Fixes issue #1162 - can't use node.contains for document fragments on IE8
jtulach@816
   375
            if (containedByNode.contains)
jtulach@816
   376
                return containedByNode.contains(node.nodeType === 3 ? node.parentNode : node);
jtulach@816
   377
            if (containedByNode.compareDocumentPosition)
jtulach@816
   378
                return (containedByNode.compareDocumentPosition(node) & 16) == 16;
jtulach@816
   379
            while (node && node != containedByNode) {
jtulach@816
   380
                node = node.parentNode;
jtulach@816
   381
            }
jtulach@816
   382
            return !!node;
jtulach@816
   383
        },
jtulach@816
   384
jtulach@816
   385
        domNodeIsAttachedToDocument: function (node) {
jtulach@816
   386
            return ko.utils.domNodeIsContainedBy(node, node.ownerDocument.documentElement);
jtulach@816
   387
        },
jtulach@816
   388
jtulach@816
   389
        anyDomNodeIsAttachedToDocument: function(nodes) {
jtulach@816
   390
            return !!ko.utils.arrayFirst(nodes, ko.utils.domNodeIsAttachedToDocument);
jtulach@816
   391
        },
jtulach@816
   392
jtulach@816
   393
        tagNameLower: function(element) {
jtulach@816
   394
            // For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case.
jtulach@816
   395
            // Possible future optimization: If we know it's an element from an XHTML document (not HTML),
jtulach@816
   396
            // we don't need to do the .toLowerCase() as it will always be lower case anyway.
jtulach@816
   397
            return element && element.tagName && element.tagName.toLowerCase();
jtulach@816
   398
        },
jtulach@816
   399
jtulach@816
   400
        registerEventHandler: function (element, eventType, handler) {
jtulach@816
   401
            var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
jtulach@816
   402
            if (!mustUseAttachEvent && jQueryInstance) {
jtulach@816
   403
                jQueryInstance(element)['bind'](eventType, handler);
jtulach@816
   404
            } else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
jtulach@816
   405
                element.addEventListener(eventType, handler, false);
jtulach@816
   406
            else if (typeof element.attachEvent != "undefined") {
jtulach@816
   407
                var attachEventHandler = function (event) { handler.call(element, event); },
jtulach@816
   408
                    attachEventName = "on" + eventType;
jtulach@816
   409
                element.attachEvent(attachEventName, attachEventHandler);
jtulach@816
   410
jtulach@816
   411
                // IE does not dispose attachEvent handlers automatically (unlike with addEventListener)
jtulach@816
   412
                // so to avoid leaks, we have to remove them manually. See bug #856
jtulach@816
   413
                ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
jtulach@816
   414
                    element.detachEvent(attachEventName, attachEventHandler);
jtulach@816
   415
                });
jtulach@816
   416
            } else
jtulach@816
   417
                throw new Error("Browser doesn't support addEventListener or attachEvent");
jtulach@816
   418
        },
jtulach@816
   419
jtulach@816
   420
        triggerEvent: function (element, eventType) {
jtulach@816
   421
            if (!(element && element.nodeType))
jtulach@816
   422
                throw new Error("element must be a DOM node when calling triggerEvent");
jtulach@816
   423
jtulach@816
   424
            // For click events on checkboxes and radio buttons, jQuery toggles the element checked state *after* the
jtulach@816
   425
            // event handler runs instead of *before*. (This was fixed in 1.9 for checkboxes but not for radio buttons.)
jtulach@816
   426
            // IE doesn't change the checked state when you trigger the click event using "fireEvent".
jtulach@816
   427
            // In both cases, we'll use the click method instead.
jtulach@816
   428
            var useClickWorkaround = isClickOnCheckableElement(element, eventType);
jtulach@816
   429
jtulach@816
   430
            if (jQueryInstance && !useClickWorkaround) {
jtulach@816
   431
                jQueryInstance(element)['trigger'](eventType);
jtulach@816
   432
            } else if (typeof document.createEvent == "function") {
jtulach@816
   433
                if (typeof element.dispatchEvent == "function") {
jtulach@816
   434
                    var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
jtulach@816
   435
                    var event = document.createEvent(eventCategory);
jtulach@816
   436
                    event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
jtulach@816
   437
                    element.dispatchEvent(event);
jtulach@816
   438
                }
jtulach@816
   439
                else
jtulach@816
   440
                    throw new Error("The supplied element doesn't support dispatchEvent");
jtulach@816
   441
            } else if (useClickWorkaround && element.click) {
jtulach@816
   442
                element.click();
jtulach@816
   443
            } else if (typeof element.fireEvent != "undefined") {
jtulach@816
   444
                element.fireEvent("on" + eventType);
jtulach@816
   445
            } else {
jtulach@816
   446
                throw new Error("Browser doesn't support triggering events");
jtulach@816
   447
            }
jtulach@816
   448
        },
jtulach@816
   449
jtulach@816
   450
        unwrapObservable: function (value) {
jtulach@816
   451
            return ko.isObservable(value) ? value() : value;
jtulach@816
   452
        },
jtulach@816
   453
jtulach@816
   454
        peekObservable: function (value) {
jtulach@816
   455
            return ko.isObservable(value) ? value.peek() : value;
jtulach@816
   456
        },
jtulach@816
   457
jtulach@816
   458
        toggleDomNodeCssClass: function (node, classNames, shouldHaveClass) {
jtulach@816
   459
            if (classNames) {
jtulach@816
   460
                var cssClassNameRegex = /\S+/g,
jtulach@816
   461
                    currentClassNames = node.className.match(cssClassNameRegex) || [];
jtulach@816
   462
                ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
jtulach@816
   463
                    ko.utils.addOrRemoveItem(currentClassNames, className, shouldHaveClass);
jtulach@816
   464
                });
jtulach@816
   465
                node.className = currentClassNames.join(" ");
jtulach@816
   466
            }
jtulach@816
   467
        },
jtulach@816
   468
jtulach@816
   469
        setTextContent: function(element, textContent) {
jtulach@816
   470
            var value = ko.utils.unwrapObservable(textContent);
jtulach@816
   471
            if ((value === null) || (value === undefined))
jtulach@816
   472
                value = "";
jtulach@816
   473
jtulach@816
   474
            // We need there to be exactly one child: a text node.
jtulach@816
   475
            // If there are no children, more than one, or if it's not a text node,
jtulach@816
   476
            // we'll clear everything and create a single text node.
jtulach@816
   477
            var innerTextNode = ko.virtualElements.firstChild(element);
jtulach@816
   478
            if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) {
jtulach@816
   479
                ko.virtualElements.setDomNodeChildren(element, [element.ownerDocument.createTextNode(value)]);
jtulach@816
   480
            } else {
jtulach@816
   481
                innerTextNode.data = value;
jtulach@816
   482
            }
jtulach@816
   483
jtulach@816
   484
            ko.utils.forceRefresh(element);
jtulach@816
   485
        },
jtulach@816
   486
jtulach@816
   487
        setElementName: function(element, name) {
jtulach@816
   488
            element.name = name;
jtulach@816
   489
jtulach@816
   490
            // Workaround IE 6/7 issue
jtulach@816
   491
            // - https://github.com/SteveSanderson/knockout/issues/197
jtulach@816
   492
            // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
jtulach@816
   493
            if (ieVersion <= 7) {
jtulach@816
   494
                try {
jtulach@816
   495
                    element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
jtulach@816
   496
                }
jtulach@816
   497
                catch(e) {} // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View"
jtulach@816
   498
            }
jtulach@816
   499
        },
jtulach@816
   500
jtulach@816
   501
        forceRefresh: function(node) {
jtulach@816
   502
            // Workaround for an IE9 rendering bug - https://github.com/SteveSanderson/knockout/issues/209
jtulach@816
   503
            if (ieVersion >= 9) {
jtulach@816
   504
                // For text nodes and comment nodes (most likely virtual elements), we will have to refresh the container
jtulach@816
   505
                var elem = node.nodeType == 1 ? node : node.parentNode;
jtulach@816
   506
                if (elem.style)
jtulach@816
   507
                    elem.style.zoom = elem.style.zoom;
jtulach@816
   508
            }
jtulach@816
   509
        },
jtulach@816
   510
jtulach@816
   511
        ensureSelectElementIsRenderedCorrectly: function(selectElement) {
jtulach@816
   512
            // 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.
jtulach@816
   513
            // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
jtulach@816
   514
            // Also fixes IE7 and IE8 bug that causes selects to be zero width if enclosed by 'if' or 'with'. (See issue #839)
jtulach@816
   515
            if (ieVersion) {
jtulach@816
   516
                var originalWidth = selectElement.style.width;
jtulach@816
   517
                selectElement.style.width = 0;
jtulach@816
   518
                selectElement.style.width = originalWidth;
jtulach@816
   519
            }
jtulach@816
   520
        },
jtulach@816
   521
jtulach@816
   522
        range: function (min, max) {
jtulach@816
   523
            min = ko.utils.unwrapObservable(min);
jtulach@816
   524
            max = ko.utils.unwrapObservable(max);
jtulach@816
   525
            var result = [];
jtulach@816
   526
            for (var i = min; i <= max; i++)
jtulach@816
   527
                result.push(i);
jtulach@816
   528
            return result;
jtulach@816
   529
        },
jtulach@816
   530
jtulach@816
   531
        makeArray: function(arrayLikeObject) {
jtulach@816
   532
            var result = [];
jtulach@816
   533
            for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
jtulach@816
   534
                result.push(arrayLikeObject[i]);
jtulach@816
   535
            };
jtulach@816
   536
            return result;
jtulach@816
   537
        },
jtulach@816
   538
jtulach@816
   539
        isIe6 : isIe6,
jtulach@816
   540
        isIe7 : isIe7,
jtulach@816
   541
        ieVersion : ieVersion,
jtulach@816
   542
jtulach@816
   543
        getFormFields: function(form, fieldName) {
jtulach@816
   544
            var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
jtulach@816
   545
            var isMatchingField = (typeof fieldName == 'string')
jtulach@816
   546
                ? function(field) { return field.name === fieldName }
jtulach@816
   547
                : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
jtulach@816
   548
            var matches = [];
jtulach@816
   549
            for (var i = fields.length - 1; i >= 0; i--) {
jtulach@816
   550
                if (isMatchingField(fields[i]))
jtulach@816
   551
                    matches.push(fields[i]);
jtulach@816
   552
            };
jtulach@816
   553
            return matches;
jtulach@816
   554
        },
jtulach@816
   555
jtulach@816
   556
        parseJson: function (jsonString) {
jtulach@816
   557
            if (typeof jsonString == "string") {
jtulach@816
   558
                jsonString = ko.utils.stringTrim(jsonString);
jtulach@816
   559
                if (jsonString) {
jtulach@816
   560
                    if (JSON && JSON.parse) // Use native parsing where available
jtulach@816
   561
                        return JSON.parse(jsonString);
jtulach@816
   562
                    return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
jtulach@816
   563
                }
jtulach@816
   564
            }
jtulach@816
   565
            return null;
jtulach@816
   566
        },
jtulach@816
   567
jtulach@816
   568
        stringifyJson: function (data, replacer, space) {   // replacer and space are optional
jtulach@816
   569
            if (!JSON || !JSON.stringify)
jtulach@816
   570
                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");
jtulach@816
   571
            return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
jtulach@816
   572
        },
jtulach@816
   573
jtulach@816
   574
        postJson: function (urlOrForm, data, options) {
jtulach@816
   575
            options = options || {};
jtulach@816
   576
            var params = options['params'] || {};
jtulach@816
   577
            var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
jtulach@816
   578
            var url = urlOrForm;
jtulach@816
   579
jtulach@816
   580
            // If we were given a form, use its 'action' URL and pick out any requested field values
jtulach@816
   581
            if((typeof urlOrForm == 'object') && (ko.utils.tagNameLower(urlOrForm) === "form")) {
jtulach@816
   582
                var originalForm = urlOrForm;
jtulach@816
   583
                url = originalForm.action;
jtulach@816
   584
                for (var i = includeFields.length - 1; i >= 0; i--) {
jtulach@816
   585
                    var fields = ko.utils.getFormFields(originalForm, includeFields[i]);
jtulach@816
   586
                    for (var j = fields.length - 1; j >= 0; j--)
jtulach@816
   587
                        params[fields[j].name] = fields[j].value;
jtulach@816
   588
                }
jtulach@816
   589
            }
jtulach@816
   590
jtulach@816
   591
            data = ko.utils.unwrapObservable(data);
jtulach@816
   592
            var form = document.createElement("form");
jtulach@816
   593
            form.style.display = "none";
jtulach@816
   594
            form.action = url;
jtulach@816
   595
            form.method = "post";
jtulach@816
   596
            for (var key in data) {
jtulach@816
   597
                // Since 'data' this is a model object, we include all properties including those inherited from its prototype
jtulach@816
   598
                var input = document.createElement("input");
jtulach@816
   599
                input.type = "hidden";
jtulach@816
   600
                input.name = key;
jtulach@816
   601
                input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
jtulach@816
   602
                form.appendChild(input);
jtulach@816
   603
            }
jtulach@816
   604
            objectForEach(params, function(key, value) {
jtulach@816
   605
                var input = document.createElement("input");
jtulach@816
   606
                input.type = "hidden";
jtulach@816
   607
                input.name = key;
jtulach@816
   608
                input.value = value;
jtulach@816
   609
                form.appendChild(input);
jtulach@816
   610
            });
jtulach@816
   611
            document.body.appendChild(form);
jtulach@816
   612
            options['submitter'] ? options['submitter'](form) : form.submit();
jtulach@816
   613
            setTimeout(function () { form.parentNode.removeChild(form); }, 0);
jtulach@816
   614
        }
jtulach@816
   615
    }
jtulach@816
   616
}());
jtulach@816
   617
jtulach@816
   618
ko.exportSymbol('utils', ko.utils);
jtulach@816
   619
ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach);
jtulach@816
   620
ko.exportSymbol('utils.arrayFirst', ko.utils.arrayFirst);
jtulach@816
   621
ko.exportSymbol('utils.arrayFilter', ko.utils.arrayFilter);
jtulach@816
   622
ko.exportSymbol('utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues);
jtulach@816
   623
ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
jtulach@816
   624
ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
jtulach@816
   625
ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
jtulach@816
   626
ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
jtulach@816
   627
ko.exportSymbol('utils.extend', ko.utils.extend);
jtulach@816
   628
ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
jtulach@816
   629
ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
jtulach@816
   630
ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable);
jtulach@816
   631
ko.exportSymbol('utils.postJson', ko.utils.postJson);
jtulach@816
   632
ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
jtulach@816
   633
ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler);
jtulach@816
   634
ko.exportSymbol('utils.stringifyJson', ko.utils.stringifyJson);
jtulach@816
   635
ko.exportSymbol('utils.range', ko.utils.range);
jtulach@816
   636
ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);
jtulach@816
   637
ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
jtulach@816
   638
ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
jtulach@816
   639
ko.exportSymbol('utils.objectForEach', ko.utils.objectForEach);
jtulach@816
   640
ko.exportSymbol('utils.addOrRemoveItem', ko.utils.addOrRemoveItem);
jtulach@816
   641
ko.exportSymbol('unwrap', ko.utils.unwrapObservable); // Convenient shorthand, because this is used so commonly
jtulach@816
   642
jtulach@816
   643
if (!Function.prototype['bind']) {
jtulach@816
   644
    // 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)
jtulach@816
   645
    // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
jtulach@816
   646
    Function.prototype['bind'] = function (object) {
jtulach@816
   647
        var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift();
jtulach@816
   648
        return function () {
jtulach@816
   649
            return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
jtulach@816
   650
        };
jtulach@816
   651
    };
jtulach@816
   652
}
jtulach@816
   653
jtulach@816
   654
ko.utils.domData = new (function () {
jtulach@816
   655
    var uniqueId = 0;
jtulach@816
   656
    var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
jtulach@816
   657
    var dataStore = {};
jtulach@816
   658
jtulach@816
   659
    function getAll(node, createIfNotFound) {
jtulach@816
   660
        var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
jtulach@816
   661
        var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];
jtulach@816
   662
        if (!hasExistingDataStore) {
jtulach@816
   663
            if (!createIfNotFound)
jtulach@816
   664
                return undefined;
jtulach@816
   665
            dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
jtulach@816
   666
            dataStore[dataStoreKey] = {};
jtulach@816
   667
        }
jtulach@816
   668
        return dataStore[dataStoreKey];
jtulach@816
   669
    }
jtulach@816
   670
jtulach@816
   671
    return {
jtulach@816
   672
        get: function (node, key) {
jtulach@816
   673
            var allDataForNode = getAll(node, false);
jtulach@816
   674
            return allDataForNode === undefined ? undefined : allDataForNode[key];
jtulach@816
   675
        },
jtulach@816
   676
        set: function (node, key, value) {
jtulach@816
   677
            if (value === undefined) {
jtulach@816
   678
                // Make sure we don't actually create a new domData key if we are actually deleting a value
jtulach@816
   679
                if (getAll(node, false) === undefined)
jtulach@816
   680
                    return;
jtulach@816
   681
            }
jtulach@816
   682
            var allDataForNode = getAll(node, true);
jtulach@816
   683
            allDataForNode[key] = value;
jtulach@816
   684
        },
jtulach@816
   685
        clear: function (node) {
jtulach@816
   686
            var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
jtulach@816
   687
            if (dataStoreKey) {
jtulach@816
   688
                delete dataStore[dataStoreKey];
jtulach@816
   689
                node[dataStoreKeyExpandoPropertyName] = null;
jtulach@816
   690
                return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
jtulach@816
   691
            }
jtulach@816
   692
            return false;
jtulach@816
   693
        },
jtulach@816
   694
jtulach@816
   695
        nextKey: function () {
jtulach@816
   696
            return (uniqueId++) + dataStoreKeyExpandoPropertyName;
jtulach@816
   697
        }
jtulach@816
   698
    };
jtulach@816
   699
})();
jtulach@816
   700
jtulach@816
   701
ko.exportSymbol('utils.domData', ko.utils.domData);
jtulach@816
   702
ko.exportSymbol('utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully
jtulach@816
   703
jtulach@816
   704
ko.utils.domNodeDisposal = new (function () {
jtulach@816
   705
    var domDataKey = ko.utils.domData.nextKey();
jtulach@816
   706
    var cleanableNodeTypes = { 1: true, 8: true, 9: true };       // Element, Comment, Document
jtulach@816
   707
    var cleanableNodeTypesWithDescendants = { 1: true, 9: true }; // Element, Document
jtulach@816
   708
jtulach@816
   709
    function getDisposeCallbacksCollection(node, createIfNotFound) {
jtulach@816
   710
        var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);
jtulach@816
   711
        if ((allDisposeCallbacks === undefined) && createIfNotFound) {
jtulach@816
   712
            allDisposeCallbacks = [];
jtulach@816
   713
            ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);
jtulach@816
   714
        }
jtulach@816
   715
        return allDisposeCallbacks;
jtulach@816
   716
    }
jtulach@816
   717
    function destroyCallbacksCollection(node) {
jtulach@816
   718
        ko.utils.domData.set(node, domDataKey, undefined);
jtulach@816
   719
    }
jtulach@816
   720
jtulach@816
   721
    function cleanSingleNode(node) {
jtulach@816
   722
        // Run all the dispose callbacks
jtulach@816
   723
        var callbacks = getDisposeCallbacksCollection(node, false);
jtulach@816
   724
        if (callbacks) {
jtulach@816
   725
            callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
jtulach@816
   726
            for (var i = 0; i < callbacks.length; i++)
jtulach@816
   727
                callbacks[i](node);
jtulach@816
   728
        }
jtulach@816
   729
jtulach@816
   730
        // Erase the DOM data
jtulach@816
   731
        ko.utils.domData.clear(node);
jtulach@816
   732
jtulach@816
   733
        // Perform cleanup needed by external libraries (currently only jQuery, but can be extended)
jtulach@816
   734
        ko.utils.domNodeDisposal["cleanExternalData"](node);
jtulach@816
   735
jtulach@816
   736
        // Clear any immediate-child comment nodes, as these wouldn't have been found by
jtulach@816
   737
        // node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
jtulach@816
   738
        if (cleanableNodeTypesWithDescendants[node.nodeType])
jtulach@816
   739
            cleanImmediateCommentTypeChildren(node);
jtulach@816
   740
    }
jtulach@816
   741
jtulach@816
   742
    function cleanImmediateCommentTypeChildren(nodeWithChildren) {
jtulach@816
   743
        var child, nextChild = nodeWithChildren.firstChild;
jtulach@816
   744
        while (child = nextChild) {
jtulach@816
   745
            nextChild = child.nextSibling;
jtulach@816
   746
            if (child.nodeType === 8)
jtulach@816
   747
                cleanSingleNode(child);
jtulach@816
   748
        }
jtulach@816
   749
    }
jtulach@816
   750
jtulach@816
   751
    return {
jtulach@816
   752
        addDisposeCallback : function(node, callback) {
jtulach@816
   753
            if (typeof callback != "function")
jtulach@816
   754
                throw new Error("Callback must be a function");
jtulach@816
   755
            getDisposeCallbacksCollection(node, true).push(callback);
jtulach@816
   756
        },
jtulach@816
   757
jtulach@816
   758
        removeDisposeCallback : function(node, callback) {
jtulach@816
   759
            var callbacksCollection = getDisposeCallbacksCollection(node, false);
jtulach@816
   760
            if (callbacksCollection) {
jtulach@816
   761
                ko.utils.arrayRemoveItem(callbacksCollection, callback);
jtulach@816
   762
                if (callbacksCollection.length == 0)
jtulach@816
   763
                    destroyCallbacksCollection(node);
jtulach@816
   764
            }
jtulach@816
   765
        },
jtulach@816
   766
jtulach@816
   767
        cleanNode : function(node) {
jtulach@816
   768
            // First clean this node, where applicable
jtulach@816
   769
            if (cleanableNodeTypes[node.nodeType]) {
jtulach@816
   770
                cleanSingleNode(node);
jtulach@816
   771
jtulach@816
   772
                // ... then its descendants, where applicable
jtulach@816
   773
                if (cleanableNodeTypesWithDescendants[node.nodeType]) {
jtulach@816
   774
                    // Clone the descendants list in case it changes during iteration
jtulach@816
   775
                    var descendants = [];
jtulach@816
   776
                    ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
jtulach@816
   777
                    for (var i = 0, j = descendants.length; i < j; i++)
jtulach@816
   778
                        cleanSingleNode(descendants[i]);
jtulach@816
   779
                }
jtulach@816
   780
            }
jtulach@816
   781
            return node;
jtulach@816
   782
        },
jtulach@816
   783
jtulach@816
   784
        removeNode : function(node) {
jtulach@816
   785
            ko.cleanNode(node);
jtulach@816
   786
            if (node.parentNode)
jtulach@816
   787
                node.parentNode.removeChild(node);
jtulach@816
   788
        },
jtulach@816
   789
jtulach@816
   790
        "cleanExternalData" : function (node) {
jtulach@816
   791
            // Special support for jQuery here because it's so commonly used.
jtulach@816
   792
            // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
jtulach@816
   793
            // so notify it to tear down any resources associated with the node & descendants here.
jtulach@816
   794
            if (jQueryInstance && (typeof jQueryInstance['cleanData'] == "function"))
jtulach@816
   795
                jQueryInstance['cleanData']([node]);
jtulach@816
   796
        }
jtulach@816
   797
    }
jtulach@816
   798
})();
jtulach@816
   799
ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience
jtulach@816
   800
ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience
jtulach@816
   801
ko.exportSymbol('cleanNode', ko.cleanNode);
jtulach@816
   802
ko.exportSymbol('removeNode', ko.removeNode);
jtulach@816
   803
ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal);
jtulach@816
   804
ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);
jtulach@816
   805
ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);
jtulach@816
   806
(function () {
jtulach@816
   807
    var leadingCommentRegex = /^(\s*)<!--(.*?)-->/;
jtulach@816
   808
jtulach@816
   809
    function simpleHtmlParse(html) {
jtulach@816
   810
        // Based on jQuery's "clean" function, but only accounting for table-related elements.
jtulach@816
   811
        // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly
jtulach@816
   812
jtulach@816
   813
        // Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of
jtulach@816
   814
        // a descendant node. For example: "<div><!-- mycomment -->abc</div>" will get parsed as "<div>abc</div>"
jtulach@816
   815
        // This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node
jtulach@816
   816
        // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
jtulach@816
   817
jtulach@816
   818
        // Trim whitespace, otherwise indexOf won't work as expected
jtulach@816
   819
        var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div");
jtulach@816
   820
jtulach@816
   821
        // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column
jtulach@816
   822
        var wrap = tags.match(/^<(thead|tbody|tfoot)/)              && [1, "<table>", "</table>"] ||
jtulach@816
   823
                   !tags.indexOf("<tr")                             && [2, "<table><tbody>", "</tbody></table>"] ||
jtulach@816
   824
                   (!tags.indexOf("<td") || !tags.indexOf("<th"))   && [3, "<table><tbody><tr>", "</tr></tbody></table>"] ||
jtulach@816
   825
                   /* anything else */                                 [0, "", ""];
jtulach@816
   826
jtulach@816
   827
        // Go to html and back, then peel off extra wrappers
jtulach@816
   828
        // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
jtulach@816
   829
        var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
jtulach@816
   830
        if (typeof window['innerShiv'] == "function") {
jtulach@816
   831
            div.appendChild(window['innerShiv'](markup));
jtulach@816
   832
        } else {
jtulach@816
   833
            div.innerHTML = markup;
jtulach@816
   834
        }
jtulach@816
   835
jtulach@816
   836
        // Move to the right depth
jtulach@816
   837
        while (wrap[0]--)
jtulach@816
   838
            div = div.lastChild;
jtulach@816
   839
jtulach@816
   840
        return ko.utils.makeArray(div.lastChild.childNodes);
jtulach@816
   841
    }
jtulach@816
   842
jtulach@816
   843
    function jQueryHtmlParse(html) {
jtulach@816
   844
        // jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API.
jtulach@816
   845
        if (jQueryInstance['parseHTML']) {
jtulach@816
   846
            return jQueryInstance['parseHTML'](html) || []; // Ensure we always return an array and never null
jtulach@816
   847
        } else {
jtulach@816
   848
            // For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function.
jtulach@816
   849
            var elems = jQueryInstance['clean']([html]);
jtulach@816
   850
jtulach@816
   851
            // 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.
jtulach@816
   852
            // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
jtulach@816
   853
            // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.
jtulach@816
   854
            if (elems && elems[0]) {
jtulach@816
   855
                // Find the top-most parent element that's a direct child of a document fragment
jtulach@816
   856
                var elem = elems[0];
jtulach@816
   857
                while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)
jtulach@816
   858
                    elem = elem.parentNode;
jtulach@816
   859
                // ... then detach it
jtulach@816
   860
                if (elem.parentNode)
jtulach@816
   861
                    elem.parentNode.removeChild(elem);
jtulach@816
   862
            }
jtulach@816
   863
jtulach@816
   864
            return elems;
jtulach@816
   865
        }
jtulach@816
   866
    }
jtulach@816
   867
jtulach@816
   868
    ko.utils.parseHtmlFragment = function(html) {
jtulach@816
   869
        return jQueryInstance ? jQueryHtmlParse(html)   // As below, benefit from jQuery's optimisations where possible
jtulach@816
   870
                              : simpleHtmlParse(html);  // ... otherwise, this simple logic will do in most common cases.
jtulach@816
   871
    };
jtulach@816
   872
jtulach@816
   873
    ko.utils.setHtml = function(node, html) {
jtulach@816
   874
        ko.utils.emptyDomNode(node);
jtulach@816
   875
jtulach@816
   876
        // There's no legitimate reason to display a stringified observable without unwrapping it, so we'll unwrap it
jtulach@816
   877
        html = ko.utils.unwrapObservable(html);
jtulach@816
   878
jtulach@816
   879
        if ((html !== null) && (html !== undefined)) {
jtulach@816
   880
            if (typeof html != 'string')
jtulach@816
   881
                html = html.toString();
jtulach@816
   882
jtulach@816
   883
            // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
jtulach@816
   884
            // for example <tr> elements which are not normally allowed to exist on their own.
jtulach@816
   885
            // If you've referenced jQuery we'll use that rather than duplicating its code.
jtulach@816
   886
            if (jQueryInstance) {
jtulach@816
   887
                jQueryInstance(node)['html'](html);
jtulach@816
   888
            } else {
jtulach@816
   889
                // ... otherwise, use KO's own parsing logic.
jtulach@816
   890
                var parsedNodes = ko.utils.parseHtmlFragment(html);
jtulach@816
   891
                for (var i = 0; i < parsedNodes.length; i++)
jtulach@816
   892
                    node.appendChild(parsedNodes[i]);
jtulach@816
   893
            }
jtulach@816
   894
        }
jtulach@816
   895
    };
jtulach@816
   896
})();
jtulach@816
   897
jtulach@816
   898
ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
jtulach@816
   899
ko.exportSymbol('utils.setHtml', ko.utils.setHtml);
jtulach@816
   900
jtulach@816
   901
ko.memoization = (function () {
jtulach@816
   902
    var memos = {};
jtulach@816
   903
jtulach@816
   904
    function randomMax8HexChars() {
jtulach@816
   905
        return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
jtulach@816
   906
    }
jtulach@816
   907
    function generateRandomId() {
jtulach@816
   908
        return randomMax8HexChars() + randomMax8HexChars();
jtulach@816
   909
    }
jtulach@816
   910
    function findMemoNodes(rootNode, appendToArray) {
jtulach@816
   911
        if (!rootNode)
jtulach@816
   912
            return;
jtulach@816
   913
        if (rootNode.nodeType == 8) {
jtulach@816
   914
            var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
jtulach@816
   915
            if (memoId != null)
jtulach@816
   916
                appendToArray.push({ domNode: rootNode, memoId: memoId });
jtulach@816
   917
        } else if (rootNode.nodeType == 1) {
jtulach@816
   918
            for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)
jtulach@816
   919
                findMemoNodes(childNodes[i], appendToArray);
jtulach@816
   920
        }
jtulach@816
   921
    }
jtulach@816
   922
jtulach@816
   923
    return {
jtulach@816
   924
        memoize: function (callback) {
jtulach@816
   925
            if (typeof callback != "function")
jtulach@816
   926
                throw new Error("You can only pass a function to ko.memoization.memoize()");
jtulach@816
   927
            var memoId = generateRandomId();
jtulach@816
   928
            memos[memoId] = callback;
jtulach@816
   929
            return "<!--[ko_memo:" + memoId + "]-->";
jtulach@816
   930
        },
jtulach@816
   931
jtulach@816
   932
        unmemoize: function (memoId, callbackParams) {
jtulach@816
   933
            var callback = memos[memoId];
jtulach@816
   934
            if (callback === undefined)
jtulach@816
   935
                throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");
jtulach@816
   936
            try {
jtulach@816
   937
                callback.apply(null, callbackParams || []);
jtulach@816
   938
                return true;
jtulach@816
   939
            }
jtulach@816
   940
            finally { delete memos[memoId]; }
jtulach@816
   941
        },
jtulach@816
   942
jtulach@816
   943
        unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {
jtulach@816
   944
            var memos = [];
jtulach@816
   945
            findMemoNodes(domNode, memos);
jtulach@816
   946
            for (var i = 0, j = memos.length; i < j; i++) {
jtulach@816
   947
                var node = memos[i].domNode;
jtulach@816
   948
                var combinedParams = [node];
jtulach@816
   949
                if (extraCallbackParamsArray)
jtulach@816
   950
                    ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray);
jtulach@816
   951
                ko.memoization.unmemoize(memos[i].memoId, combinedParams);
jtulach@816
   952
                node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
jtulach@816
   953
                if (node.parentNode)
jtulach@816
   954
                    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)
jtulach@816
   955
            }
jtulach@816
   956
        },
jtulach@816
   957
jtulach@816
   958
        parseMemoText: function (memoText) {
jtulach@816
   959
            var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
jtulach@816
   960
            return match ? match[1] : null;
jtulach@816
   961
        }
jtulach@816
   962
    };
jtulach@816
   963
})();
jtulach@816
   964
jtulach@816
   965
ko.exportSymbol('memoization', ko.memoization);
jtulach@816
   966
ko.exportSymbol('memoization.memoize', ko.memoization.memoize);
jtulach@816
   967
ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize);
jtulach@816
   968
ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText);
jtulach@816
   969
ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);
jtulach@816
   970
ko.extenders = {
jtulach@816
   971
    'throttle': function(target, timeout) {
jtulach@816
   972
        // Throttling means two things:
jtulach@816
   973
jtulach@816
   974
        // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies
jtulach@816
   975
        //     notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate
jtulach@816
   976
        target['throttleEvaluation'] = timeout;
jtulach@816
   977
jtulach@816
   978
        // (2) For writable targets (observables, or writable dependent observables), we throttle *writes*
jtulach@816
   979
        //     so the target cannot change value synchronously or faster than a certain rate
jtulach@816
   980
        var writeTimeoutInstance = null;
jtulach@816
   981
        return ko.dependentObservable({
jtulach@816
   982
            'read': target,
jtulach@816
   983
            'write': function(value) {
jtulach@816
   984
                clearTimeout(writeTimeoutInstance);
jtulach@816
   985
                writeTimeoutInstance = setTimeout(function() {
jtulach@816
   986
                    target(value);
jtulach@816
   987
                }, timeout);
jtulach@816
   988
            }
jtulach@816
   989
        });
jtulach@816
   990
    },
jtulach@816
   991
jtulach@816
   992
    'rateLimit': function(target, options) {
jtulach@816
   993
        var timeout, method, limitFunction;
jtulach@816
   994
jtulach@816
   995
        if (typeof options == 'number') {
jtulach@816
   996
            timeout = options;
jtulach@816
   997
        } else {
jtulach@816
   998
            timeout = options['timeout'];
jtulach@816
   999
            method = options['method'];
jtulach@816
  1000
        }
jtulach@816
  1001
jtulach@816
  1002
        limitFunction = method == 'notifyWhenChangesStop' ?  debounce : throttle;
jtulach@816
  1003
        target.limit(function(callback) {
jtulach@816
  1004
            return limitFunction(callback, timeout);
jtulach@816
  1005
        });
jtulach@816
  1006
    },
jtulach@816
  1007
jtulach@816
  1008
    'notify': function(target, notifyWhen) {
jtulach@816
  1009
        target["equalityComparer"] = notifyWhen == "always" ?
jtulach@816
  1010
            null :  // null equalityComparer means to always notify
jtulach@816
  1011
            valuesArePrimitiveAndEqual;
jtulach@816
  1012
    }
jtulach@816
  1013
};
jtulach@816
  1014
jtulach@816
  1015
var primitiveTypes = { 'undefined':1, 'boolean':1, 'number':1, 'string':1 };
jtulach@816
  1016
function valuesArePrimitiveAndEqual(a, b) {
jtulach@816
  1017
    var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
jtulach@816
  1018
    return oldValueIsPrimitive ? (a === b) : false;
jtulach@816
  1019
}
jtulach@816
  1020
jtulach@816
  1021
function throttle(callback, timeout) {
jtulach@816
  1022
    var timeoutInstance;
jtulach@816
  1023
    return function () {
jtulach@816
  1024
        if (!timeoutInstance) {
jtulach@816
  1025
            timeoutInstance = setTimeout(function() {
jtulach@816
  1026
                timeoutInstance = undefined;
jtulach@816
  1027
                callback();
jtulach@816
  1028
            }, timeout);
jtulach@816
  1029
        }
jtulach@816
  1030
    };
jtulach@816
  1031
}
jtulach@816
  1032
jtulach@816
  1033
function debounce(callback, timeout) {
jtulach@816
  1034
    var timeoutInstance;
jtulach@816
  1035
    return function () {
jtulach@816
  1036
        clearTimeout(timeoutInstance);
jtulach@816
  1037
        timeoutInstance = setTimeout(callback, timeout);
jtulach@816
  1038
    };
jtulach@816
  1039
}
jtulach@816
  1040
jtulach@816
  1041
function applyExtenders(requestedExtenders) {
jtulach@816
  1042
    var target = this;
jtulach@816
  1043
    if (requestedExtenders) {
jtulach@816
  1044
        ko.utils.objectForEach(requestedExtenders, function(key, value) {
jtulach@816
  1045
            var extenderHandler = ko.extenders[key];
jtulach@816
  1046
            if (typeof extenderHandler == 'function') {
jtulach@816
  1047
                target = extenderHandler(target, value) || target;
jtulach@816
  1048
            }
jtulach@816
  1049
        });
jtulach@816
  1050
    }
jtulach@816
  1051
    return target;
jtulach@816
  1052
}
jtulach@816
  1053
jtulach@816
  1054
ko.exportSymbol('extenders', ko.extenders);
jtulach@816
  1055
jtulach@816
  1056
ko.subscription = function (target, callback, disposeCallback) {
jtulach@816
  1057
    this.target = target;
jtulach@816
  1058
    this.callback = callback;
jtulach@816
  1059
    this.disposeCallback = disposeCallback;
jtulach@816
  1060
    this.isDisposed = false;
jtulach@816
  1061
    ko.exportProperty(this, 'dispose', this.dispose);
jtulach@816
  1062
};
jtulach@816
  1063
ko.subscription.prototype.dispose = function () {
jtulach@816
  1064
    this.isDisposed = true;
jtulach@816
  1065
    this.disposeCallback();
jtulach@816
  1066
};
jtulach@816
  1067
jtulach@816
  1068
ko.subscribable = function () {
jtulach@816
  1069
    ko.utils.setPrototypeOfOrExtend(this, ko.subscribable['fn']);
jtulach@816
  1070
    this._subscriptions = {};
jtulach@816
  1071
}
jtulach@816
  1072
jtulach@816
  1073
var defaultEvent = "change";
jtulach@816
  1074
jtulach@816
  1075
var ko_subscribable_fn = {
jtulach@816
  1076
    subscribe: function (callback, callbackTarget, event) {
jtulach@816
  1077
        var self = this;
jtulach@816
  1078
jtulach@816
  1079
        event = event || defaultEvent;
jtulach@816
  1080
        var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
jtulach@816
  1081
jtulach@816
  1082
        var subscription = new ko.subscription(self, boundCallback, function () {
jtulach@816
  1083
            ko.utils.arrayRemoveItem(self._subscriptions[event], subscription);
jtulach@816
  1084
            if (self.afterSubscriptionRemove)
jtulach@816
  1085
                self.afterSubscriptionRemove(event);
jtulach@816
  1086
        });
jtulach@816
  1087
jtulach@816
  1088
        if (self.beforeSubscriptionAdd)
jtulach@816
  1089
            self.beforeSubscriptionAdd(event);
jtulach@816
  1090
jtulach@816
  1091
        if (!self._subscriptions[event])
jtulach@816
  1092
            self._subscriptions[event] = [];
jtulach@816
  1093
        self._subscriptions[event].push(subscription);
jtulach@816
  1094
jtulach@816
  1095
        return subscription;
jtulach@816
  1096
    },
jtulach@816
  1097
jtulach@816
  1098
    "notifySubscribers": function (valueToNotify, event) {
jtulach@816
  1099
        event = event || defaultEvent;
jtulach@816
  1100
        if (this.hasSubscriptionsForEvent(event)) {
jtulach@816
  1101
            try {
jtulach@816
  1102
                ko.dependencyDetection.begin(); // Begin suppressing dependency detection (by setting the top frame to undefined)
jtulach@816
  1103
                for (var a = this._subscriptions[event].slice(0), i = 0, subscription; subscription = a[i]; ++i) {
jtulach@816
  1104
                    // In case a subscription was disposed during the arrayForEach cycle, check
jtulach@816
  1105
                    // for isDisposed on each subscription before invoking its callback
jtulach@816
  1106
                    if (!subscription.isDisposed)
jtulach@816
  1107
                        subscription.callback(valueToNotify);
jtulach@816
  1108
                }
jtulach@816
  1109
            } finally {
jtulach@816
  1110
                ko.dependencyDetection.end(); // End suppressing dependency detection
jtulach@816
  1111
            }
jtulach@816
  1112
        }
jtulach@816
  1113
    },
jtulach@816
  1114
jtulach@816
  1115
    limit: function(limitFunction) {
jtulach@816
  1116
        var self = this, selfIsObservable = ko.isObservable(self),
jtulach@816
  1117
            isPending, previousValue, pendingValue, beforeChange = 'beforeChange';
jtulach@816
  1118
jtulach@816
  1119
        if (!self._origNotifySubscribers) {
jtulach@816
  1120
            self._origNotifySubscribers = self["notifySubscribers"];
jtulach@816
  1121
            self["notifySubscribers"] = function(value, event) {
jtulach@816
  1122
                if (!event || event === defaultEvent) {
jtulach@816
  1123
                    self._rateLimitedChange(value);
jtulach@816
  1124
                } else if (event === beforeChange) {
jtulach@816
  1125
                    self._rateLimitedBeforeChange(value);
jtulach@816
  1126
                } else {
jtulach@816
  1127
                    self._origNotifySubscribers(value, event);
jtulach@816
  1128
                }
jtulach@816
  1129
            };
jtulach@816
  1130
        }
jtulach@816
  1131
jtulach@816
  1132
        var finish = limitFunction(function() {
jtulach@816
  1133
            // If an observable provided a reference to itself, access it to get the latest value.
jtulach@816
  1134
            // This allows computed observables to delay calculating their value until needed.
jtulach@816
  1135
            if (selfIsObservable && pendingValue === self) {
jtulach@816
  1136
                pendingValue = self();
jtulach@816
  1137
            }
jtulach@816
  1138
            isPending = false;
jtulach@816
  1139
            if (self.isDifferent(previousValue, pendingValue)) {
jtulach@816
  1140
                self._origNotifySubscribers(previousValue = pendingValue);
jtulach@816
  1141
            }
jtulach@816
  1142
        });
jtulach@816
  1143
jtulach@816
  1144
        self._rateLimitedChange = function(value) {
jtulach@816
  1145
            isPending = true;
jtulach@816
  1146
            pendingValue = value;
jtulach@816
  1147
            finish();
jtulach@816
  1148
        };
jtulach@816
  1149
        self._rateLimitedBeforeChange = function(value) {
jtulach@816
  1150
            if (!isPending) {
jtulach@816
  1151
                previousValue = value;
jtulach@816
  1152
                self._origNotifySubscribers(value, beforeChange);
jtulach@816
  1153
            }
jtulach@816
  1154
        };
jtulach@816
  1155
    },
jtulach@816
  1156
jtulach@816
  1157
    hasSubscriptionsForEvent: function(event) {
jtulach@816
  1158
        return this._subscriptions[event] && this._subscriptions[event].length;
jtulach@816
  1159
    },
jtulach@816
  1160
jtulach@816
  1161
    getSubscriptionsCount: function () {
jtulach@816
  1162
        var total = 0;
jtulach@816
  1163
        ko.utils.objectForEach(this._subscriptions, function(eventName, subscriptions) {
jtulach@816
  1164
            total += subscriptions.length;
jtulach@816
  1165
        });
jtulach@816
  1166
        return total;
jtulach@816
  1167
    },
jtulach@816
  1168
jtulach@816
  1169
    isDifferent: function(oldValue, newValue) {
jtulach@816
  1170
        return !this['equalityComparer'] || !this['equalityComparer'](oldValue, newValue);
jtulach@816
  1171
    },
jtulach@816
  1172
jtulach@816
  1173
    extend: applyExtenders
jtulach@816
  1174
};
jtulach@816
  1175
jtulach@816
  1176
ko.exportProperty(ko_subscribable_fn, 'subscribe', ko_subscribable_fn.subscribe);
jtulach@816
  1177
ko.exportProperty(ko_subscribable_fn, 'extend', ko_subscribable_fn.extend);
jtulach@816
  1178
ko.exportProperty(ko_subscribable_fn, 'getSubscriptionsCount', ko_subscribable_fn.getSubscriptionsCount);
jtulach@816
  1179
jtulach@816
  1180
// For browsers that support proto assignment, we overwrite the prototype of each
jtulach@816
  1181
// observable instance. Since observables are functions, we need Function.prototype
jtulach@816
  1182
// to still be in the prototype chain.
jtulach@816
  1183
if (ko.utils.canSetPrototype) {
jtulach@816
  1184
    ko.utils.setPrototypeOf(ko_subscribable_fn, Function.prototype);
jtulach@816
  1185
}
jtulach@816
  1186
jtulach@816
  1187
ko.subscribable['fn'] = ko_subscribable_fn;
jtulach@816
  1188
jtulach@816
  1189
jtulach@816
  1190
ko.isSubscribable = function (instance) {
jtulach@816
  1191
    return instance != null && typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
jtulach@816
  1192
};
jtulach@816
  1193
jtulach@816
  1194
ko.exportSymbol('subscribable', ko.subscribable);
jtulach@816
  1195
ko.exportSymbol('isSubscribable', ko.isSubscribable);
jtulach@816
  1196
jtulach@816
  1197
ko.computedContext = ko.dependencyDetection = (function () {
jtulach@816
  1198
    var outerFrames = [],
jtulach@816
  1199
        currentFrame,
jtulach@816
  1200
        lastId = 0;
jtulach@816
  1201
jtulach@816
  1202
    // Return a unique ID that can be assigned to an observable for dependency tracking.
jtulach@816
  1203
    // Theoretically, you could eventually overflow the number storage size, resulting
jtulach@816
  1204
    // in duplicate IDs. But in JavaScript, the largest exact integral value is 2^53
jtulach@816
  1205
    // or 9,007,199,254,740,992. If you created 1,000,000 IDs per second, it would
jtulach@816
  1206
    // take over 285 years to reach that number.
jtulach@816
  1207
    // Reference http://blog.vjeux.com/2010/javascript/javascript-max_int-number-limits.html
jtulach@816
  1208
    function getId() {
jtulach@816
  1209
        return ++lastId;
jtulach@816
  1210
    }
jtulach@816
  1211
jtulach@816
  1212
    function begin(options) {
jtulach@816
  1213
        outerFrames.push(currentFrame);
jtulach@816
  1214
        currentFrame = options;
jtulach@816
  1215
    }
jtulach@816
  1216
jtulach@816
  1217
    function end() {
jtulach@816
  1218
        currentFrame = outerFrames.pop();
jtulach@816
  1219
    }
jtulach@816
  1220
jtulach@816
  1221
    return {
jtulach@816
  1222
        begin: begin,
jtulach@816
  1223
jtulach@816
  1224
        end: end,
jtulach@816
  1225
jtulach@816
  1226
        registerDependency: function (subscribable) {
jtulach@816
  1227
            if (currentFrame) {
jtulach@816
  1228
                if (!ko.isSubscribable(subscribable))
jtulach@816
  1229
                    throw new Error("Only subscribable things can act as dependencies");
jtulach@816
  1230
                currentFrame.callback(subscribable, subscribable._id || (subscribable._id = getId()));
jtulach@816
  1231
            }
jtulach@816
  1232
        },
jtulach@816
  1233
jtulach@816
  1234
        ignore: function (callback, callbackTarget, callbackArgs) {
jtulach@816
  1235
            try {
jtulach@816
  1236
                begin();
jtulach@816
  1237
                return callback.apply(callbackTarget, callbackArgs || []);
jtulach@816
  1238
            } finally {
jtulach@816
  1239
                end();
jtulach@816
  1240
            }
jtulach@816
  1241
        },
jtulach@816
  1242
jtulach@816
  1243
        getDependenciesCount: function () {
jtulach@816
  1244
            if (currentFrame)
jtulach@816
  1245
                return currentFrame.computed.getDependenciesCount();
jtulach@816
  1246
        },
jtulach@816
  1247
jtulach@816
  1248
        isInitial: function() {
jtulach@816
  1249
            if (currentFrame)
jtulach@816
  1250
                return currentFrame.isInitial;
jtulach@816
  1251
        }
jtulach@816
  1252
    };
jtulach@816
  1253
})();
jtulach@816
  1254
jtulach@816
  1255
ko.exportSymbol('computedContext', ko.computedContext);
jtulach@816
  1256
ko.exportSymbol('computedContext.getDependenciesCount', ko.computedContext.getDependenciesCount);
jtulach@816
  1257
ko.exportSymbol('computedContext.isInitial', ko.computedContext.isInitial);
jtulach@816
  1258
ko.exportSymbol('computedContext.isSleeping', ko.computedContext.isSleeping);
jtulach@816
  1259
ko.observable = function (initialValue) {
jtulach@816
  1260
    var _latestValue = initialValue;
jtulach@816
  1261
jtulach@816
  1262
    function observable() {
jtulach@816
  1263
        if (arguments.length > 0) {
jtulach@816
  1264
            // Write
jtulach@816
  1265
jtulach@816
  1266
            // Ignore writes if the value hasn't changed
jtulach@816
  1267
            if (observable.isDifferent(_latestValue, arguments[0])) {
jtulach@816
  1268
                observable.valueWillMutate();
jtulach@816
  1269
                _latestValue = arguments[0];
jtulach@816
  1270
                if (DEBUG) observable._latestValue = _latestValue;
jtulach@816
  1271
                observable.valueHasMutated();
jtulach@816
  1272
            }
jtulach@816
  1273
            return this; // Permits chained assignments
jtulach@816
  1274
        }
jtulach@816
  1275
        else {
jtulach@816
  1276
            // Read
jtulach@816
  1277
            ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
jtulach@816
  1278
            return _latestValue;
jtulach@816
  1279
        }
jtulach@816
  1280
    }
jtulach@816
  1281
    ko.subscribable.call(observable);
jtulach@816
  1282
    ko.utils.setPrototypeOfOrExtend(observable, ko.observable['fn']);
jtulach@816
  1283
jtulach@816
  1284
    if (DEBUG) observable._latestValue = _latestValue;
jtulach@816
  1285
    observable.peek = function() { return _latestValue };
jtulach@816
  1286
    observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }
jtulach@816
  1287
    observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }
jtulach@816
  1288
jtulach@816
  1289
    ko.exportProperty(observable, 'peek', observable.peek);
jtulach@816
  1290
    ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
jtulach@816
  1291
    ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
jtulach@816
  1292
jtulach@816
  1293
    return observable;
jtulach@816
  1294
}
jtulach@816
  1295
jtulach@816
  1296
ko.observable['fn'] = {
jtulach@816
  1297
    "equalityComparer": valuesArePrimitiveAndEqual
jtulach@816
  1298
};
jtulach@816
  1299
jtulach@816
  1300
var protoProperty = ko.observable.protoProperty = "__ko_proto__";
jtulach@816
  1301
ko.observable['fn'][protoProperty] = ko.observable;
jtulach@816
  1302
jtulach@816
  1303
// Note that for browsers that don't support proto assignment, the
jtulach@816
  1304
// inheritance chain is created manually in the ko.observable constructor
jtulach@816
  1305
if (ko.utils.canSetPrototype) {
jtulach@816
  1306
    ko.utils.setPrototypeOf(ko.observable['fn'], ko.subscribable['fn']);
jtulach@816
  1307
}
jtulach@816
  1308
jtulach@816
  1309
ko.hasPrototype = function(instance, prototype) {
jtulach@816
  1310
    if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
jtulach@816
  1311
    if (instance[protoProperty] === prototype) return true;
jtulach@816
  1312
    return ko.hasPrototype(instance[protoProperty], prototype); // Walk the prototype chain
jtulach@816
  1313
};
jtulach@816
  1314
jtulach@816
  1315
ko.isObservable = function (instance) {
jtulach@816
  1316
    return ko.hasPrototype(instance, ko.observable);
jtulach@816
  1317
}
jtulach@816
  1318
ko.isWriteableObservable = function (instance) {
jtulach@816
  1319
    // Observable
jtulach@816
  1320
    if ((typeof instance == "function") && instance[protoProperty] === ko.observable)
jtulach@816
  1321
        return true;
jtulach@816
  1322
    // Writeable dependent observable
jtulach@816
  1323
    if ((typeof instance == "function") && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
jtulach@816
  1324
        return true;
jtulach@816
  1325
    // Anything else
jtulach@816
  1326
    return false;
jtulach@816
  1327
}
jtulach@816
  1328
jtulach@816
  1329
jtulach@816
  1330
ko.exportSymbol('observable', ko.observable);
jtulach@816
  1331
ko.exportSymbol('isObservable', ko.isObservable);
jtulach@816
  1332
ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
jtulach@816
  1333
ko.exportSymbol('isWritableObservable', ko.isWriteableObservable);
jtulach@816
  1334
ko.observableArray = function (initialValues) {
jtulach@816
  1335
    initialValues = initialValues || [];
jtulach@816
  1336
jtulach@816
  1337
    if (typeof initialValues != 'object' || !('length' in initialValues))
jtulach@816
  1338
        throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
jtulach@816
  1339
jtulach@816
  1340
    var result = ko.observable(initialValues);
jtulach@816
  1341
    ko.utils.setPrototypeOfOrExtend(result, ko.observableArray['fn']);
jtulach@816
  1342
    return result.extend({'trackArrayChanges':true});
jtulach@816
  1343
};
jtulach@816
  1344
jtulach@816
  1345
ko.observableArray['fn'] = {
jtulach@816
  1346
    'remove': function (valueOrPredicate) {
jtulach@816
  1347
        var underlyingArray = this.peek();
jtulach@816
  1348
        var removedValues = [];
jtulach@816
  1349
        var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
jtulach@816
  1350
        for (var i = 0; i < underlyingArray.length; i++) {
jtulach@816
  1351
            var value = underlyingArray[i];
jtulach@816
  1352
            if (predicate(value)) {
jtulach@816
  1353
                if (removedValues.length === 0) {
jtulach@816
  1354
                    this.valueWillMutate();
jtulach@816
  1355
                }
jtulach@816
  1356
                removedValues.push(value);
jtulach@816
  1357
                underlyingArray.splice(i, 1);
jtulach@816
  1358
                i--;
jtulach@816
  1359
            }
jtulach@816
  1360
        }
jtulach@816
  1361
        if (removedValues.length) {
jtulach@816
  1362
            this.valueHasMutated();
jtulach@816
  1363
        }
jtulach@816
  1364
        return removedValues;
jtulach@816
  1365
    },
jtulach@816
  1366
jtulach@816
  1367
    'removeAll': function (arrayOfValues) {
jtulach@816
  1368
        // If you passed zero args, we remove everything
jtulach@816
  1369
        if (arrayOfValues === undefined) {
jtulach@816
  1370
            var underlyingArray = this.peek();
jtulach@816
  1371
            var allValues = underlyingArray.slice(0);
jtulach@816
  1372
            this.valueWillMutate();
jtulach@816
  1373
            underlyingArray.splice(0, underlyingArray.length);
jtulach@816
  1374
            this.valueHasMutated();
jtulach@816
  1375
            return allValues;
jtulach@816
  1376
        }
jtulach@816
  1377
        // If you passed an arg, we interpret it as an array of entries to remove
jtulach@816
  1378
        if (!arrayOfValues)
jtulach@816
  1379
            return [];
jtulach@816
  1380
        return this['remove'](function (value) {
jtulach@816
  1381
            return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
jtulach@816
  1382
        });
jtulach@816
  1383
    },
jtulach@816
  1384
jtulach@816
  1385
    'destroy': function (valueOrPredicate) {
jtulach@816
  1386
        var underlyingArray = this.peek();
jtulach@816
  1387
        var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
jtulach@816
  1388
        this.valueWillMutate();
jtulach@816
  1389
        for (var i = underlyingArray.length - 1; i >= 0; i--) {
jtulach@816
  1390
            var value = underlyingArray[i];
jtulach@816
  1391
            if (predicate(value))
jtulach@816
  1392
                underlyingArray[i]["_destroy"] = true;
jtulach@816
  1393
        }
jtulach@816
  1394
        this.valueHasMutated();
jtulach@816
  1395
    },
jtulach@816
  1396
jtulach@816
  1397
    'destroyAll': function (arrayOfValues) {
jtulach@816
  1398
        // If you passed zero args, we destroy everything
jtulach@816
  1399
        if (arrayOfValues === undefined)
jtulach@816
  1400
            return this['destroy'](function() { return true });
jtulach@816
  1401
jtulach@816
  1402
        // If you passed an arg, we interpret it as an array of entries to destroy
jtulach@816
  1403
        if (!arrayOfValues)
jtulach@816
  1404
            return [];
jtulach@816
  1405
        return this['destroy'](function (value) {
jtulach@816
  1406
            return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
jtulach@816
  1407
        });
jtulach@816
  1408
    },
jtulach@816
  1409
jtulach@816
  1410
    'indexOf': function (item) {
jtulach@816
  1411
        var underlyingArray = this();
jtulach@816
  1412
        return ko.utils.arrayIndexOf(underlyingArray, item);
jtulach@816
  1413
    },
jtulach@816
  1414
jtulach@816
  1415
    'replace': function(oldItem, newItem) {
jtulach@816
  1416
        var index = this['indexOf'](oldItem);
jtulach@816
  1417
        if (index >= 0) {
jtulach@816
  1418
            this.valueWillMutate();
jtulach@816
  1419
            this.peek()[index] = newItem;
jtulach@816
  1420
            this.valueHasMutated();
jtulach@816
  1421
        }
jtulach@816
  1422
    }
jtulach@816
  1423
};
jtulach@816
  1424
jtulach@816
  1425
// Populate ko.observableArray.fn with read/write functions from native arrays
jtulach@816
  1426
// Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
jtulach@816
  1427
// because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale
jtulach@816
  1428
ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
jtulach@816
  1429
    ko.observableArray['fn'][methodName] = function () {
jtulach@816
  1430
        // Use "peek" to avoid creating a subscription in any computed that we're executing in the context of
jtulach@816
  1431
        // (for consistency with mutating regular observables)
jtulach@816
  1432
        var underlyingArray = this.peek();
jtulach@816
  1433
        this.valueWillMutate();
jtulach@816
  1434
        this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments);
jtulach@816
  1435
        var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
jtulach@816
  1436
        this.valueHasMutated();
jtulach@816
  1437
        return methodCallResult;
jtulach@816
  1438
    };
jtulach@816
  1439
});
jtulach@816
  1440
jtulach@816
  1441
// Populate ko.observableArray.fn with read-only functions from native arrays
jtulach@816
  1442
ko.utils.arrayForEach(["slice"], function (methodName) {
jtulach@816
  1443
    ko.observableArray['fn'][methodName] = function () {
jtulach@816
  1444
        var underlyingArray = this();
jtulach@816
  1445
        return underlyingArray[methodName].apply(underlyingArray, arguments);
jtulach@816
  1446
    };
jtulach@816
  1447
});
jtulach@816
  1448
jtulach@816
  1449
// Note that for browsers that don't support proto assignment, the
jtulach@816
  1450
// inheritance chain is created manually in the ko.observableArray constructor
jtulach@816
  1451
if (ko.utils.canSetPrototype) {
jtulach@816
  1452
    ko.utils.setPrototypeOf(ko.observableArray['fn'], ko.observable['fn']);
jtulach@816
  1453
}
jtulach@816
  1454
jtulach@816
  1455
ko.exportSymbol('observableArray', ko.observableArray);
jtulach@816
  1456
var arrayChangeEventName = 'arrayChange';
jtulach@816
  1457
ko.extenders['trackArrayChanges'] = function(target) {
jtulach@816
  1458
    // Only modify the target observable once
jtulach@816
  1459
    if (target.cacheDiffForKnownOperation) {
jtulach@816
  1460
        return;
jtulach@816
  1461
    }
jtulach@816
  1462
    var trackingChanges = false,
jtulach@816
  1463
        cachedDiff = null,
jtulach@816
  1464
        pendingNotifications = 0,
jtulach@816
  1465
        underlyingSubscribeFunction = target.subscribe;
jtulach@816
  1466
jtulach@816
  1467
    // Intercept "subscribe" calls, and for array change events, ensure change tracking is enabled
jtulach@816
  1468
    target.subscribe = target['subscribe'] = function(callback, callbackTarget, event) {
jtulach@816
  1469
        if (event === arrayChangeEventName) {
jtulach@816
  1470
            trackChanges();
jtulach@816
  1471
        }
jtulach@816
  1472
        return underlyingSubscribeFunction.apply(this, arguments);
jtulach@816
  1473
    };
jtulach@816
  1474
jtulach@816
  1475
    function trackChanges() {
jtulach@816
  1476
        // Calling 'trackChanges' multiple times is the same as calling it once
jtulach@816
  1477
        if (trackingChanges) {
jtulach@816
  1478
            return;
jtulach@816
  1479
        }
jtulach@816
  1480
jtulach@816
  1481
        trackingChanges = true;
jtulach@816
  1482
jtulach@816
  1483
        // Intercept "notifySubscribers" to track how many times it was called.
jtulach@816
  1484
        var underlyingNotifySubscribersFunction = target['notifySubscribers'];
jtulach@816
  1485
        target['notifySubscribers'] = function(valueToNotify, event) {
jtulach@816
  1486
            if (!event || event === defaultEvent) {
jtulach@816
  1487
                ++pendingNotifications;
jtulach@816
  1488
            }
jtulach@816
  1489
            return underlyingNotifySubscribersFunction.apply(this, arguments);
jtulach@816
  1490
        };
jtulach@816
  1491
jtulach@816
  1492
        // Each time the array changes value, capture a clone so that on the next
jtulach@816
  1493
        // change it's possible to produce a diff
jtulach@816
  1494
        var previousContents = [].concat(target.peek() || []);
jtulach@816
  1495
        cachedDiff = null;
jtulach@816
  1496
        target.subscribe(function(currentContents) {
jtulach@816
  1497
            // Make a copy of the current contents and ensure it's an array
jtulach@816
  1498
            currentContents = [].concat(currentContents || []);
jtulach@816
  1499
jtulach@816
  1500
            // Compute the diff and issue notifications, but only if someone is listening
jtulach@816
  1501
            if (target.hasSubscriptionsForEvent(arrayChangeEventName)) {
jtulach@816
  1502
                var changes = getChanges(previousContents, currentContents);
jtulach@816
  1503
                if (changes.length) {
jtulach@816
  1504
                    target['notifySubscribers'](changes, arrayChangeEventName);
jtulach@816
  1505
                }
jtulach@816
  1506
            }
jtulach@816
  1507
jtulach@816
  1508
            // Eliminate references to the old, removed items, so they can be GCed
jtulach@816
  1509
            previousContents = currentContents;
jtulach@816
  1510
            cachedDiff = null;
jtulach@816
  1511
            pendingNotifications = 0;
jtulach@816
  1512
        });
jtulach@816
  1513
    }
jtulach@816
  1514
jtulach@816
  1515
    function getChanges(previousContents, currentContents) {
jtulach@816
  1516
        // We try to re-use cached diffs.
jtulach@816
  1517
        // The scenarios where pendingNotifications > 1 are when using rate-limiting or the Deferred Updates
jtulach@816
  1518
        // plugin, which without this check would not be compatible with arrayChange notifications. Normally,
jtulach@816
  1519
        // notifications are issued immediately so we wouldn't be queueing up more than one.
jtulach@816
  1520
        if (!cachedDiff || pendingNotifications > 1) {
jtulach@816
  1521
            cachedDiff = ko.utils.compareArrays(previousContents, currentContents, { 'sparse': true });
jtulach@816
  1522
        }
jtulach@816
  1523
jtulach@816
  1524
        return cachedDiff;
jtulach@816
  1525
    }
jtulach@816
  1526
jtulach@816
  1527
    target.cacheDiffForKnownOperation = function(rawArray, operationName, args) {
jtulach@816
  1528
        // Only run if we're currently tracking changes for this observable array
jtulach@816
  1529
        // and there aren't any pending deferred notifications.
jtulach@816
  1530
        if (!trackingChanges || pendingNotifications) {
jtulach@816
  1531
            return;
jtulach@816
  1532
        }
jtulach@816
  1533
        var diff = [],
jtulach@816
  1534
            arrayLength = rawArray.length,
jtulach@816
  1535
            argsLength = args.length,
jtulach@816
  1536
            offset = 0;
jtulach@816
  1537
jtulach@816
  1538
        function pushDiff(status, value, index) {
jtulach@816
  1539
            return diff[diff.length] = { 'status': status, 'value': value, 'index': index };
jtulach@816
  1540
        }
jtulach@816
  1541
        switch (operationName) {
jtulach@816
  1542
            case 'push':
jtulach@816
  1543
                offset = arrayLength;
jtulach@816
  1544
            case 'unshift':
jtulach@816
  1545
                for (var index = 0; index < argsLength; index++) {
jtulach@816
  1546
                    pushDiff('added', args[index], offset + index);
jtulach@816
  1547
                }
jtulach@816
  1548
                break;
jtulach@816
  1549
jtulach@816
  1550
            case 'pop':
jtulach@816
  1551
                offset = arrayLength - 1;
jtulach@816
  1552
            case 'shift':
jtulach@816
  1553
                if (arrayLength) {
jtulach@816
  1554
                    pushDiff('deleted', rawArray[offset], offset);
jtulach@816
  1555
                }
jtulach@816
  1556
                break;
jtulach@816
  1557
jtulach@816
  1558
            case 'splice':
jtulach@816
  1559
                // Negative start index means 'from end of array'. After that we clamp to [0...arrayLength].
jtulach@816
  1560
                // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
jtulach@816
  1561
                var startIndex = Math.min(Math.max(0, args[0] < 0 ? arrayLength + args[0] : args[0]), arrayLength),
jtulach@816
  1562
                    endDeleteIndex = argsLength === 1 ? arrayLength : Math.min(startIndex + (args[1] || 0), arrayLength),
jtulach@816
  1563
                    endAddIndex = startIndex + argsLength - 2,
jtulach@816
  1564
                    endIndex = Math.max(endDeleteIndex, endAddIndex),
jtulach@816
  1565
                    additions = [], deletions = [];
jtulach@816
  1566
                for (var index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) {
jtulach@816
  1567
                    if (index < endDeleteIndex)
jtulach@816
  1568
                        deletions.push(pushDiff('deleted', rawArray[index], index));
jtulach@816
  1569
                    if (index < endAddIndex)
jtulach@816
  1570
                        additions.push(pushDiff('added', args[argsIndex], index));
jtulach@816
  1571
                }
jtulach@816
  1572
                ko.utils.findMovesInArrayComparison(deletions, additions);
jtulach@816
  1573
                break;
jtulach@816
  1574
jtulach@816
  1575
            default:
jtulach@816
  1576
                return;
jtulach@816
  1577
        }
jtulach@816
  1578
        cachedDiff = diff;
jtulach@816
  1579
    };
jtulach@816
  1580
};
jtulach@816
  1581
ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
jtulach@816
  1582
    var _latestValue,
jtulach@816
  1583
        _needsEvaluation = true,
jtulach@816
  1584
        _isBeingEvaluated = false,
jtulach@816
  1585
        _suppressDisposalUntilDisposeWhenReturnsFalse = false,
jtulach@816
  1586
        _isDisposed = false,
jtulach@816
  1587
        readFunction = evaluatorFunctionOrOptions,
jtulach@816
  1588
        pure = false,
jtulach@816
  1589
        isSleeping = false;
jtulach@816
  1590
jtulach@816
  1591
    if (readFunction && typeof readFunction == "object") {
jtulach@816
  1592
        // Single-parameter syntax - everything is on this "options" param
jtulach@816
  1593
        options = readFunction;
jtulach@816
  1594
        readFunction = options["read"];
jtulach@816
  1595
    } else {
jtulach@816
  1596
        // Multi-parameter syntax - construct the options according to the params passed
jtulach@816
  1597
        options = options || {};
jtulach@816
  1598
        if (!readFunction)
jtulach@816
  1599
            readFunction = options["read"];
jtulach@816
  1600
    }
jtulach@816
  1601
    if (typeof readFunction != "function")
jtulach@816
  1602
        throw new Error("Pass a function that returns the value of the ko.computed");
jtulach@816
  1603
jtulach@816
  1604
    function addSubscriptionToDependency(subscribable, id) {
jtulach@816
  1605
        if (!_subscriptionsToDependencies[id]) {
jtulach@816
  1606
            _subscriptionsToDependencies[id] = subscribable.subscribe(evaluatePossiblyAsync);
jtulach@816
  1607
            ++_dependenciesCount;
jtulach@816
  1608
        }
jtulach@816
  1609
    }
jtulach@816
  1610
jtulach@816
  1611
    function disposeAllSubscriptionsToDependencies() {
jtulach@816
  1612
        ko.utils.objectForEach(_subscriptionsToDependencies, function (id, subscription) {
jtulach@816
  1613
            subscription.dispose();
jtulach@816
  1614
        });
jtulach@816
  1615
        _subscriptionsToDependencies = {};
jtulach@816
  1616
    }
jtulach@816
  1617
jtulach@816
  1618
    function disposeComputed() {
jtulach@816
  1619
        disposeAllSubscriptionsToDependencies();
jtulach@816
  1620
        _dependenciesCount = 0;
jtulach@816
  1621
        _isDisposed = true;
jtulach@816
  1622
        _needsEvaluation = false;
jtulach@816
  1623
    }
jtulach@816
  1624
jtulach@816
  1625
    function evaluatePossiblyAsync() {
jtulach@816
  1626
        var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
jtulach@816
  1627
        if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
jtulach@816
  1628
            clearTimeout(evaluationTimeoutInstance);
jtulach@816
  1629
            evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
jtulach@816
  1630
        } else if (dependentObservable._evalRateLimited) {
jtulach@816
  1631
            dependentObservable._evalRateLimited();
jtulach@816
  1632
        } else {
jtulach@816
  1633
            evaluateImmediate();
jtulach@816
  1634
        }
jtulach@816
  1635
    }
jtulach@816
  1636
jtulach@816
  1637
    function evaluateImmediate(suppressChangeNotification) {
jtulach@816
  1638
        if (_isBeingEvaluated) {
jtulach@816
  1639
            if (pure) {
jtulach@816
  1640
                throw Error("A 'pure' computed must not be called recursively");
jtulach@816
  1641
            }
jtulach@816
  1642
            // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
jtulach@816
  1643
            // This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
jtulach@816
  1644
            // certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
jtulach@816
  1645
            // their own re-evaluation. Further discussion at https://github.com/SteveSanderson/knockout/pull/387
jtulach@816
  1646
            return;
jtulach@816
  1647
        }
jtulach@816
  1648
jtulach@816
  1649
        // Do not evaluate (and possibly capture new dependencies) if disposed
jtulach@816
  1650
        if (_isDisposed) {
jtulach@816
  1651
            return;
jtulach@816
  1652
        }
jtulach@816
  1653
jtulach@816
  1654
        if (disposeWhen && disposeWhen()) {
jtulach@816
  1655
            // See comment below about _suppressDisposalUntilDisposeWhenReturnsFalse
jtulach@816
  1656
            if (!_suppressDisposalUntilDisposeWhenReturnsFalse) {
jtulach@816
  1657
                dispose();
jtulach@816
  1658
                return;
jtulach@816
  1659
            }
jtulach@816
  1660
        } else {
jtulach@816
  1661
            // It just did return false, so we can stop suppressing now
jtulach@816
  1662
            _suppressDisposalUntilDisposeWhenReturnsFalse = false;
jtulach@816
  1663
        }
jtulach@816
  1664
jtulach@816
  1665
        _isBeingEvaluated = true;
jtulach@816
  1666
jtulach@816
  1667
        // When sleeping, recalculate the value and return.
jtulach@816
  1668
        if (isSleeping) {
jtulach@816
  1669
            try {
jtulach@816
  1670
                var dependencyTracking = {};
jtulach@816
  1671
                ko.dependencyDetection.begin({
jtulach@816
  1672
                    callback: function (subscribable, id) {
jtulach@816
  1673
                        if (!dependencyTracking[id]) {
jtulach@816
  1674
                            dependencyTracking[id] = 1;
jtulach@816
  1675
                            ++_dependenciesCount;
jtulach@816
  1676
                        }
jtulach@816
  1677
                    },
jtulach@816
  1678
                    computed: dependentObservable,
jtulach@816
  1679
                    isInitial: undefined
jtulach@816
  1680
                });
jtulach@816
  1681
                _dependenciesCount = 0;
jtulach@816
  1682
                _latestValue = readFunction.call(evaluatorFunctionTarget);
jtulach@816
  1683
            } finally {
jtulach@816
  1684
                ko.dependencyDetection.end();
jtulach@816
  1685
                _isBeingEvaluated = false;
jtulach@816
  1686
            }
jtulach@816
  1687
        } else {
jtulach@816
  1688
            try {
jtulach@816
  1689
                // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
jtulach@816
  1690
                // Then, during evaluation, we cross off any that are in fact still being used.
jtulach@816
  1691
                var disposalCandidates = _subscriptionsToDependencies, disposalCount = _dependenciesCount;
jtulach@816
  1692
                ko.dependencyDetection.begin({
jtulach@816
  1693
                    callback: function(subscribable, id) {
jtulach@816
  1694
                        if (!_isDisposed) {
jtulach@816
  1695
                            if (disposalCount && disposalCandidates[id]) {
jtulach@816
  1696
                                // Don't want to dispose this subscription, as it's still being used
jtulach@816
  1697
                                _subscriptionsToDependencies[id] = disposalCandidates[id];
jtulach@816
  1698
                                ++_dependenciesCount;
jtulach@816
  1699
                                delete disposalCandidates[id];
jtulach@816
  1700
                                --disposalCount;
jtulach@816
  1701
                            } else {
jtulach@816
  1702
                                // Brand new subscription - add it
jtulach@816
  1703
                                addSubscriptionToDependency(subscribable, id);
jtulach@816
  1704
                            }
jtulach@816
  1705
                        }
jtulach@816
  1706
                    },
jtulach@816
  1707
                    computed: dependentObservable,
jtulach@816
  1708
                    isInitial: pure ? undefined : !_dependenciesCount        // If we're evaluating when there are no previous dependencies, it must be the first time
jtulach@816
  1709
                });
jtulach@816
  1710
jtulach@816
  1711
                _subscriptionsToDependencies = {};
jtulach@816
  1712
                _dependenciesCount = 0;
jtulach@816
  1713
jtulach@816
  1714
                try {
jtulach@816
  1715
                    var newValue = evaluatorFunctionTarget ? readFunction.call(evaluatorFunctionTarget) : readFunction();
jtulach@816
  1716
jtulach@816
  1717
                } finally {
jtulach@816
  1718
                    ko.dependencyDetection.end();
jtulach@816
  1719
jtulach@816
  1720
                    // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
jtulach@816
  1721
                    if (disposalCount) {
jtulach@816
  1722
                        ko.utils.objectForEach(disposalCandidates, function(id, toDispose) {
jtulach@816
  1723
                            toDispose.dispose();
jtulach@816
  1724
                        });
jtulach@816
  1725
                    }
jtulach@816
  1726
jtulach@816
  1727
                    _needsEvaluation = false;
jtulach@816
  1728
                }
jtulach@816
  1729
jtulach@816
  1730
                if (dependentObservable.isDifferent(_latestValue, newValue)) {
jtulach@816
  1731
                    dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
jtulach@816
  1732
jtulach@816
  1733
                    _latestValue = newValue;
jtulach@816
  1734
                    if (DEBUG) dependentObservable._latestValue = _latestValue;
jtulach@816
  1735
jtulach@816
  1736
                    if (suppressChangeNotification !== true) {  // Check for strict true value since setTimeout in Firefox passes a numeric value to the function
jtulach@816
  1737
                        dependentObservable["notifySubscribers"](_latestValue);
jtulach@816
  1738
                    }
jtulach@816
  1739
                }
jtulach@816
  1740
            } finally {
jtulach@816
  1741
                _isBeingEvaluated = false;
jtulach@816
  1742
            }
jtulach@816
  1743
        }
jtulach@816
  1744
jtulach@816
  1745
        if (!_dependenciesCount)
jtulach@816
  1746
            dispose();
jtulach@816
  1747
    }
jtulach@816
  1748
jtulach@816
  1749
    function dependentObservable() {
jtulach@816
  1750
        if (arguments.length > 0) {
jtulach@816
  1751
            if (typeof writeFunction === "function") {
jtulach@816
  1752
                // Writing a value
jtulach@816
  1753
                writeFunction.apply(evaluatorFunctionTarget, arguments);
jtulach@816
  1754
            } else {
jtulach@816
  1755
                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.");
jtulach@816
  1756
            }
jtulach@816
  1757
            return this; // Permits chained assignments
jtulach@816
  1758
        } else {
jtulach@816
  1759
            // Reading the value
jtulach@816
  1760
            ko.dependencyDetection.registerDependency(dependentObservable);
jtulach@816
  1761
            if (_needsEvaluation)
jtulach@816
  1762
                evaluateImmediate(true /* suppressChangeNotification */);
jtulach@816
  1763
            return _latestValue;
jtulach@816
  1764
        }
jtulach@816
  1765
    }
jtulach@816
  1766
jtulach@816
  1767
    function peek() {
jtulach@816
  1768
        // Peek won't re-evaluate, except to get the initial value when "deferEvaluation" is set, or while the computed is sleeping.
jtulach@816
  1769
        // Those are the only times that both of these conditions will be satisfied.
jtulach@816
  1770
        if (_needsEvaluation && !_dependenciesCount)
jtulach@816
  1771
            evaluateImmediate(true /* suppressChangeNotification */);
jtulach@816
  1772
        return _latestValue;
jtulach@816
  1773
    }
jtulach@816
  1774
jtulach@816
  1775
    function isActive() {
jtulach@816
  1776
        return _needsEvaluation || _dependenciesCount > 0;
jtulach@816
  1777
    }
jtulach@816
  1778
jtulach@816
  1779
    // By here, "options" is always non-null
jtulach@816
  1780
    var writeFunction = options["write"],
jtulach@816
  1781
        disposeWhenNodeIsRemoved = options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
jtulach@816
  1782
        disposeWhenOption = options["disposeWhen"] || options.disposeWhen,
jtulach@816
  1783
        disposeWhen = disposeWhenOption,
jtulach@816
  1784
        dispose = disposeComputed,
jtulach@816
  1785
        _subscriptionsToDependencies = {},
jtulach@816
  1786
        _dependenciesCount = 0,
jtulach@816
  1787
        evaluationTimeoutInstance = null;
jtulach@816
  1788
jtulach@816
  1789
    if (!evaluatorFunctionTarget)
jtulach@816
  1790
        evaluatorFunctionTarget = options["owner"];
jtulach@816
  1791
jtulach@816
  1792
    ko.subscribable.call(dependentObservable);
jtulach@816
  1793
    ko.utils.setPrototypeOfOrExtend(dependentObservable, ko.dependentObservable['fn']);
jtulach@816
  1794
jtulach@816
  1795
    dependentObservable.peek = peek;
jtulach@816
  1796
    dependentObservable.getDependenciesCount = function () { return _dependenciesCount; };
jtulach@816
  1797
    dependentObservable.hasWriteFunction = typeof options["write"] === "function";
jtulach@816
  1798
    dependentObservable.dispose = function () { dispose(); };
jtulach@816
  1799
    dependentObservable.isActive = isActive;
jtulach@816
  1800
jtulach@816
  1801
    // Replace the limit function with one that delays evaluation as well.
jtulach@816
  1802
    var originalLimit = dependentObservable.limit;
jtulach@816
  1803
    dependentObservable.limit = function(limitFunction) {
jtulach@816
  1804
        originalLimit.call(dependentObservable, limitFunction);
jtulach@816
  1805
        dependentObservable._evalRateLimited = function() {
jtulach@816
  1806
            dependentObservable._rateLimitedBeforeChange(_latestValue);
jtulach@816
  1807
jtulach@816
  1808
            _needsEvaluation = true;    // Mark as dirty
jtulach@816
  1809
jtulach@816
  1810
            // Pass the observable to the rate-limit code, which will access it when
jtulach@816
  1811
            // it's time to do the notification.
jtulach@816
  1812
            dependentObservable._rateLimitedChange(dependentObservable);
jtulach@816
  1813
        }
jtulach@816
  1814
    };
jtulach@816
  1815
jtulach@816
  1816
    if (options['pure']) {
jtulach@816
  1817
        pure = true;
jtulach@816
  1818
        isSleeping = true;     // Starts off sleeping; will awake on the first subscription
jtulach@816
  1819
        dependentObservable.beforeSubscriptionAdd = function () {
jtulach@816
  1820
            // If asleep, wake up the computed and evaluate to register any dependencies.
jtulach@816
  1821
            if (isSleeping) {
jtulach@816
  1822
                isSleeping = false;
jtulach@816
  1823
                evaluateImmediate(true /* suppressChangeNotification */);
jtulach@816
  1824
            }
jtulach@816
  1825
        }
jtulach@816
  1826
        dependentObservable.afterSubscriptionRemove = function () {
jtulach@816
  1827
            if (!dependentObservable.getSubscriptionsCount()) {
jtulach@816
  1828
                disposeAllSubscriptionsToDependencies();
jtulach@816
  1829
                isSleeping = _needsEvaluation = true;
jtulach@816
  1830
            }
jtulach@816
  1831
        }
jtulach@816
  1832
    } else if (options['deferEvaluation']) {
jtulach@816
  1833
        // This will force a computed with deferEvaluation to evaluate when the first subscriptions is registered.
jtulach@816
  1834
        dependentObservable.beforeSubscriptionAdd = function () {
jtulach@816
  1835
            peek();
jtulach@816
  1836
            delete dependentObservable.beforeSubscriptionAdd;
jtulach@816
  1837
        }
jtulach@816
  1838
    }
jtulach@816
  1839
jtulach@816
  1840
    ko.exportProperty(dependentObservable, 'peek', dependentObservable.peek);
jtulach@816
  1841
    ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
jtulach@816
  1842
    ko.exportProperty(dependentObservable, 'isActive', dependentObservable.isActive);
jtulach@816
  1843
    ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount);
jtulach@816
  1844
jtulach@816
  1845
    // Add a "disposeWhen" callback that, on each evaluation, disposes if the node was removed without using ko.removeNode.
jtulach@816
  1846
    if (disposeWhenNodeIsRemoved) {
jtulach@816
  1847
        // Since this computed is associated with a DOM node, and we don't want to dispose the computed
jtulach@816
  1848
        // until the DOM node is *removed* from the document (as opposed to never having been in the document),
jtulach@816
  1849
        // we'll prevent disposal until "disposeWhen" first returns false.
jtulach@816
  1850
        _suppressDisposalUntilDisposeWhenReturnsFalse = true;
jtulach@816
  1851
jtulach@816
  1852
        // Only watch for the node's disposal if the value really is a node. It might not be,
jtulach@816
  1853
        // e.g., { disposeWhenNodeIsRemoved: true } can be used to opt into the "only dispose
jtulach@816
  1854
        // after first false result" behaviour even if there's no specific node to watch. This
jtulach@816
  1855
        // technique is intended for KO's internal use only and shouldn't be documented or used
jtulach@816
  1856
        // by application code, as it's likely to change in a future version of KO.
jtulach@816
  1857
        if (disposeWhenNodeIsRemoved.nodeType) {
jtulach@816
  1858
            disposeWhen = function () {
jtulach@816
  1859
                return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || (disposeWhenOption && disposeWhenOption());
jtulach@816
  1860
            };
jtulach@816
  1861
        }
jtulach@816
  1862
    }
jtulach@816
  1863
jtulach@816
  1864
    // Evaluate, unless sleeping or deferEvaluation is true
jtulach@816
  1865
    if (!isSleeping && !options['deferEvaluation'])
jtulach@816
  1866
        evaluateImmediate();
jtulach@816
  1867
jtulach@816
  1868
    // Attach a DOM node disposal callback so that the computed will be proactively disposed as soon as the node is
jtulach@816
  1869
    // removed using ko.removeNode. But skip if isActive is false (there will never be any dependencies to dispose).
jtulach@816
  1870
    if (disposeWhenNodeIsRemoved && isActive() && disposeWhenNodeIsRemoved.nodeType) {
jtulach@816
  1871
        dispose = function() {
jtulach@816
  1872
            ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, dispose);
jtulach@816
  1873
            disposeComputed();
jtulach@816
  1874
        };
jtulach@816
  1875
        ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
jtulach@816
  1876
    }
jtulach@816
  1877
jtulach@816
  1878
    return dependentObservable;
jtulach@816
  1879
};
jtulach@816
  1880
jtulach@816
  1881
ko.isComputed = function(instance) {
jtulach@816
  1882
    return ko.hasPrototype(instance, ko.dependentObservable);
jtulach@816
  1883
};
jtulach@816
  1884
jtulach@816
  1885
var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
jtulach@816
  1886
ko.dependentObservable[protoProp] = ko.observable;
jtulach@816
  1887
jtulach@816
  1888
ko.dependentObservable['fn'] = {
jtulach@816
  1889
    "equalityComparer": valuesArePrimitiveAndEqual
jtulach@816
  1890
};
jtulach@816
  1891
ko.dependentObservable['fn'][protoProp] = ko.dependentObservable;
jtulach@816
  1892
jtulach@816
  1893
// Note that for browsers that don't support proto assignment, the
jtulach@816
  1894
// inheritance chain is created manually in the ko.dependentObservable constructor
jtulach@816
  1895
if (ko.utils.canSetPrototype) {
jtulach@816
  1896
    ko.utils.setPrototypeOf(ko.dependentObservable['fn'], ko.subscribable['fn']);
jtulach@816
  1897
}
jtulach@816
  1898
jtulach@816
  1899
ko.exportSymbol('dependentObservable', ko.dependentObservable);
jtulach@816
  1900
ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
jtulach@816
  1901
ko.exportSymbol('isComputed', ko.isComputed);
jtulach@816
  1902
jtulach@816
  1903
ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget) {
jtulach@816
  1904
    if (typeof evaluatorFunctionOrOptions === 'function') {
jtulach@816
  1905
        return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget, {'pure':true});
jtulach@816
  1906
    } else {
jtulach@816
  1907
        evaluatorFunctionOrOptions = ko.utils.extend({}, evaluatorFunctionOrOptions);   // make a copy of the parameter object
jtulach@816
  1908
        evaluatorFunctionOrOptions['pure'] = true;
jtulach@816
  1909
        return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget);
jtulach@816
  1910
    }
jtulach@816
  1911
}
jtulach@816
  1912
ko.exportSymbol('pureComputed', ko.pureComputed);
jtulach@816
  1913
jtulach@816
  1914
(function() {
jtulach@816
  1915
    var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
jtulach@816
  1916
jtulach@816
  1917
    ko.toJS = function(rootObject) {
jtulach@816
  1918
        if (arguments.length == 0)
jtulach@816
  1919
            throw new Error("When calling ko.toJS, pass the object you want to convert.");
jtulach@816
  1920
jtulach@816
  1921
        // We just unwrap everything at every level in the object graph
jtulach@816
  1922
        return mapJsObjectGraph(rootObject, function(valueToMap) {
jtulach@816
  1923
            // Loop because an observable's value might in turn be another observable wrapper
jtulach@816
  1924
            for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++)
jtulach@816
  1925
                valueToMap = valueToMap();
jtulach@816
  1926
            return valueToMap;
jtulach@816
  1927
        });
jtulach@816
  1928
    };
jtulach@816
  1929
jtulach@816
  1930
    ko.toJSON = function(rootObject, replacer, space) {     // replacer and space are optional
jtulach@816
  1931
        var plainJavaScriptObject = ko.toJS(rootObject);
jtulach@816
  1932
        return ko.utils.stringifyJson(plainJavaScriptObject, replacer, space);
jtulach@816
  1933
    };
jtulach@816
  1934
jtulach@816
  1935
    function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
jtulach@816
  1936
        visitedObjects = visitedObjects || new objectLookup();
jtulach@816
  1937
jtulach@816
  1938
        rootObject = mapInputCallback(rootObject);
jtulach@816
  1939
        var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date)) && (!(rootObject instanceof String)) && (!(rootObject instanceof Number)) && (!(rootObject instanceof Boolean));
jtulach@816
  1940
        if (!canHaveProperties)
jtulach@816
  1941
            return rootObject;
jtulach@816
  1942
jtulach@816
  1943
        var outputProperties = rootObject instanceof Array ? [] : {};
jtulach@816
  1944
        visitedObjects.save(rootObject, outputProperties);
jtulach@816
  1945
jtulach@816
  1946
        visitPropertiesOrArrayEntries(rootObject, function(indexer) {
jtulach@816
  1947
            var propertyValue = mapInputCallback(rootObject[indexer]);
jtulach@816
  1948
jtulach@816
  1949
            switch (typeof propertyValue) {
jtulach@816
  1950
                case "boolean":
jtulach@816
  1951
                case "number":
jtulach@816
  1952
                case "string":
jtulach@816
  1953
                case "function":
jtulach@816
  1954
                    outputProperties[indexer] = propertyValue;
jtulach@816
  1955
                    break;
jtulach@816
  1956
                case "object":
jtulach@816
  1957
                case "undefined":
jtulach@816
  1958
                    var previouslyMappedValue = visitedObjects.get(propertyValue);
jtulach@816
  1959
                    outputProperties[indexer] = (previouslyMappedValue !== undefined)
jtulach@816
  1960
                        ? previouslyMappedValue
jtulach@816
  1961
                        : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects);
jtulach@816
  1962
                    break;
jtulach@816
  1963
            }
jtulach@816
  1964
        });
jtulach@816
  1965
jtulach@816
  1966
        return outputProperties;
jtulach@816
  1967
    }
jtulach@816
  1968
jtulach@816
  1969
    function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
jtulach@816
  1970
        if (rootObject instanceof Array) {
jtulach@816
  1971
            for (var i = 0; i < rootObject.length; i++)
jtulach@816
  1972
                visitorCallback(i);
jtulach@816
  1973
jtulach@816
  1974
            // For arrays, also respect toJSON property for custom mappings (fixes #278)
jtulach@816
  1975
            if (typeof rootObject['toJSON'] == 'function')
jtulach@816
  1976
                visitorCallback('toJSON');
jtulach@816
  1977
        } else {
jtulach@816
  1978
            for (var propertyName in rootObject) {
jtulach@816
  1979
                visitorCallback(propertyName);
jtulach@816
  1980
            }
jtulach@816
  1981
        }
jtulach@816
  1982
    };
jtulach@816
  1983
jtulach@816
  1984
    function objectLookup() {
jtulach@816
  1985
        this.keys = [];
jtulach@816
  1986
        this.values = [];
jtulach@816
  1987
    };
jtulach@816
  1988
jtulach@816
  1989
    objectLookup.prototype = {
jtulach@816
  1990
        constructor: objectLookup,
jtulach@816
  1991
        save: function(key, value) {
jtulach@816
  1992
            var existingIndex = ko.utils.arrayIndexOf(this.keys, key);
jtulach@816
  1993
            if (existingIndex >= 0)
jtulach@816
  1994
                this.values[existingIndex] = value;
jtulach@816
  1995
            else {
jtulach@816
  1996
                this.keys.push(key);
jtulach@816
  1997
                this.values.push(value);
jtulach@816
  1998
            }
jtulach@816
  1999
        },
jtulach@816
  2000
        get: function(key) {
jtulach@816
  2001
            var existingIndex = ko.utils.arrayIndexOf(this.keys, key);
jtulach@816
  2002
            return (existingIndex >= 0) ? this.values[existingIndex] : undefined;
jtulach@816
  2003
        }
jtulach@816
  2004
    };
jtulach@816
  2005
})();
jtulach@816
  2006
jtulach@816
  2007
ko.exportSymbol('toJS', ko.toJS);
jtulach@816
  2008
ko.exportSymbol('toJSON', ko.toJSON);
jtulach@816
  2009
(function () {
jtulach@816
  2010
    var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
jtulach@816
  2011
jtulach@816
  2012
    // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
jtulach@816
  2013
    // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
jtulach@816
  2014
    // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
jtulach@816
  2015
    ko.selectExtensions = {
jtulach@816
  2016
        readValue : function(element) {
jtulach@816
  2017
            switch (ko.utils.tagNameLower(element)) {
jtulach@816
  2018
                case 'option':
jtulach@816
  2019
                    if (element[hasDomDataExpandoProperty] === true)
jtulach@816
  2020
                        return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
jtulach@816
  2021
                    return ko.utils.ieVersion <= 7
jtulach@816
  2022
                        ? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text)
jtulach@816
  2023
                        : element.value;
jtulach@816
  2024
                case 'select':
jtulach@816
  2025
                    return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
jtulach@816
  2026
                default:
jtulach@816
  2027
                    return element.value;
jtulach@816
  2028
            }
jtulach@816
  2029
        },
jtulach@816
  2030
jtulach@816
  2031
        writeValue: function(element, value, allowUnset) {
jtulach@816
  2032
            switch (ko.utils.tagNameLower(element)) {
jtulach@816
  2033
                case 'option':
jtulach@816
  2034
                    switch(typeof value) {
jtulach@816
  2035
                        case "string":
jtulach@816
  2036
                            ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
jtulach@816
  2037
                            if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
jtulach@816
  2038
                                delete element[hasDomDataExpandoProperty];
jtulach@816
  2039
                            }
jtulach@816
  2040
                            element.value = value;
jtulach@816
  2041
                            break;
jtulach@816
  2042
                        default:
jtulach@816
  2043
                            // Store arbitrary object using DomData
jtulach@816
  2044
                            ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
jtulach@816
  2045
                            element[hasDomDataExpandoProperty] = true;
jtulach@816
  2046
jtulach@816
  2047
                            // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
jtulach@816
  2048
                            element.value = typeof value === "number" ? value : "";
jtulach@816
  2049
                            break;
jtulach@816
  2050
                    }
jtulach@816
  2051
                    break;
jtulach@816
  2052
                case 'select':
jtulach@816
  2053
                    if (value === "" || value === null)       // A blank string or null value will select the caption
jtulach@816
  2054
                        value = undefined;
jtulach@816
  2055
                    var selection = -1;
jtulach@816
  2056
                    for (var i = 0, n = element.options.length, optionValue; i < n; ++i) {
jtulach@816
  2057
                        optionValue = ko.selectExtensions.readValue(element.options[i]);
jtulach@816
  2058
                        // Include special check to handle selecting a caption with a blank string value
jtulach@816
  2059
                        if (optionValue == value || (optionValue == "" && value === undefined)) {
jtulach@816
  2060
                            selection = i;
jtulach@816
  2061
                            break;
jtulach@816
  2062
                        }
jtulach@816
  2063
                    }
jtulach@816
  2064
                    if (allowUnset || selection >= 0 || (value === undefined && element.size > 1)) {
jtulach@816
  2065
                        element.selectedIndex = selection;
jtulach@816
  2066
                    }
jtulach@816
  2067
                    break;
jtulach@816
  2068
                default:
jtulach@816
  2069
                    if ((value === null) || (value === undefined))
jtulach@816
  2070
                        value = "";
jtulach@816
  2071
                    element.value = value;
jtulach@816
  2072
                    break;
jtulach@816
  2073
            }
jtulach@816
  2074
        }
jtulach@816
  2075
    };
jtulach@816
  2076
})();
jtulach@816
  2077
jtulach@816
  2078
ko.exportSymbol('selectExtensions', ko.selectExtensions);
jtulach@816
  2079
ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
jtulach@816
  2080
ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
jtulach@816
  2081
ko.expressionRewriting = (function () {
jtulach@816
  2082
    var javaScriptReservedWords = ["true", "false", "null", "undefined"];
jtulach@816
  2083
jtulach@816
  2084
    // Matches something that can be assigned to--either an isolated identifier or something ending with a property accessor
jtulach@816
  2085
    // This is designed to be simple and avoid false negatives, but could produce false positives (e.g., a+b.c).
jtulach@816
  2086
    // This also will not properly handle nested brackets (e.g., obj1[obj2['prop']]; see #911).
jtulach@816
  2087
    var javaScriptAssignmentTarget = /^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i;
jtulach@816
  2088
jtulach@816
  2089
    function getWriteableValue(expression) {
jtulach@816
  2090
        if (ko.utils.arrayIndexOf(javaScriptReservedWords, expression) >= 0)
jtulach@816
  2091
            return false;
jtulach@816
  2092
        var match = expression.match(javaScriptAssignmentTarget);
jtulach@816
  2093
        return match === null ? false : match[1] ? ('Object(' + match[1] + ')' + match[2]) : expression;
jtulach@816
  2094
    }
jtulach@816
  2095
jtulach@816
  2096
    // The following regular expressions will be used to split an object-literal string into tokens
jtulach@816
  2097
jtulach@816
  2098
        // These two match strings, either with double quotes or single quotes
jtulach@816
  2099
    var stringDouble = '"(?:[^"\\\\]|\\\\.)*"',
jtulach@816
  2100
        stringSingle = "'(?:[^'\\\\]|\\\\.)*'",
jtulach@816
  2101
        // Matches a regular expression (text enclosed by slashes), but will also match sets of divisions
jtulach@816
  2102
        // as a regular expression (this is handled by the parsing loop below).
jtulach@816
  2103
        stringRegexp = '/(?:[^/\\\\]|\\\\.)*/\w*',
jtulach@816
  2104
        // These characters have special meaning to the parser and must not appear in the middle of a
jtulach@816
  2105
        // token, except as part of a string.
jtulach@816
  2106
        specials = ',"\'{}()/:[\\]',
jtulach@816
  2107
        // Match text (at least two characters) that does not contain any of the above special characters,
jtulach@816
  2108
        // although some of the special characters are allowed to start it (all but the colon and comma).
jtulach@816
  2109
        // The text can contain spaces, but leading or trailing spaces are skipped.
jtulach@816
  2110
        everyThingElse = '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']',
jtulach@816
  2111
        // Match any non-space character not matched already. This will match colons and commas, since they're
jtulach@816
  2112
        // not matched by "everyThingElse", but will also match any other single character that wasn't already
jtulach@816
  2113
        // matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace).
jtulach@816
  2114
        oneNotSpace = '[^\\s]',
jtulach@816
  2115
jtulach@816
  2116
        // Create the actual regular expression by or-ing the above strings. The order is important.
jtulach@816
  2117
        bindingToken = RegExp(stringDouble + '|' + stringSingle + '|' + stringRegexp + '|' + everyThingElse + '|' + oneNotSpace, 'g'),
jtulach@816
  2118
jtulach@816
  2119
        // Match end of previous token to determine whether a slash is a division or regex.
jtulach@816
  2120
        divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/,
jtulach@816
  2121
        keywordRegexLookBehind = {'in':1,'return':1,'typeof':1};
jtulach@816
  2122
jtulach@816
  2123
    function parseObjectLiteral(objectLiteralString) {
jtulach@816
  2124
        // Trim leading and trailing spaces from the string
jtulach@816
  2125
        var str = ko.utils.stringTrim(objectLiteralString);
jtulach@816
  2126
jtulach@816
  2127
        // Trim braces '{' surrounding the whole object literal
jtulach@816
  2128
        if (str.charCodeAt(0) === 123) str = str.slice(1, -1);
jtulach@816
  2129
jtulach@816
  2130
        // Split into tokens
jtulach@816
  2131
        var result = [], toks = str.match(bindingToken), key, values, depth = 0;
jtulach@816
  2132
jtulach@816
  2133
        if (toks) {
jtulach@816
  2134
            // Append a comma so that we don't need a separate code block to deal with the last item
jtulach@816
  2135
            toks.push(',');
jtulach@816
  2136
jtulach@816
  2137
            for (var i = 0, tok; tok = toks[i]; ++i) {
jtulach@816
  2138
                var c = tok.charCodeAt(0);
jtulach@816
  2139
                // A comma signals the end of a key/value pair if depth is zero
jtulach@816
  2140
                if (c === 44) { // ","
jtulach@816
  2141
                    if (depth <= 0) {
jtulach@816
  2142
                        if (key)
jtulach@816
  2143
                            result.push(values ? {key: key, value: values.join('')} : {'unknown': key});
jtulach@816
  2144
                        key = values = depth = 0;
jtulach@816
  2145
                        continue;
jtulach@816
  2146
                    }
jtulach@816
  2147
                // Simply skip the colon that separates the name and value
jtulach@816
  2148
                } else if (c === 58) { // ":"
jtulach@816
  2149
                    if (!values)
jtulach@816
  2150
                        continue;
jtulach@816
  2151
                // A set of slashes is initially matched as a regular expression, but could be division
jtulach@816
  2152
                } else if (c === 47 && i && tok.length > 1) {  // "/"
jtulach@816
  2153
                    // Look at the end of the previous token to determine if the slash is actually division
jtulach@816
  2154
                    var match = toks[i-1].match(divisionLookBehind);
jtulach@816
  2155
                    if (match && !keywordRegexLookBehind[match[0]]) {
jtulach@816
  2156
                        // The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash)
jtulach@816
  2157
                        str = str.substr(str.indexOf(tok) + 1);
jtulach@816
  2158
                        toks = str.match(bindingToken);
jtulach@816
  2159
                        toks.push(',');
jtulach@816
  2160
                        i = -1;
jtulach@816
  2161
                        // Continue with just the slash
jtulach@816
  2162
                        tok = '/';
jtulach@816
  2163
                    }
jtulach@816
  2164
                // Increment depth for parentheses, braces, and brackets so that interior commas are ignored
jtulach@816
  2165
                } else if (c === 40 || c === 123 || c === 91) { // '(', '{', '['
jtulach@816
  2166
                    ++depth;
jtulach@816
  2167
                } else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']'
jtulach@816
  2168
                    --depth;
jtulach@816
  2169
                // The key must be a single token; if it's a string, trim the quotes
jtulach@816
  2170
                } else if (!key && !values) {
jtulach@816
  2171
                    key = (c === 34 || c === 39) /* '"', "'" */ ? tok.slice(1, -1) : tok;
jtulach@816
  2172
                    continue;
jtulach@816
  2173
                }
jtulach@816
  2174
                if (values)
jtulach@816
  2175
                    values.push(tok);
jtulach@816
  2176
                else
jtulach@816
  2177
                    values = [tok];
jtulach@816
  2178
            }
jtulach@816
  2179
        }
jtulach@816
  2180
        return result;
jtulach@816
  2181
    }
jtulach@816
  2182
jtulach@816
  2183
    // Two-way bindings include a write function that allow the handler to update the value even if it's not an observable.
jtulach@816
  2184
    var twoWayBindings = {};
jtulach@816
  2185
jtulach@816
  2186
    function preProcessBindings(bindingsStringOrKeyValueArray, bindingOptions) {
jtulach@816
  2187
        bindingOptions = bindingOptions || {};
jtulach@816
  2188
jtulach@816
  2189
        function processKeyValue(key, val) {
jtulach@816
  2190
            var writableVal;
jtulach@816
  2191
            function callPreprocessHook(obj) {
jtulach@816
  2192
                return (obj && obj['preprocess']) ? (val = obj['preprocess'](val, key, processKeyValue)) : true;
jtulach@816
  2193
            }
jtulach@816
  2194
            if (!bindingParams) {
jtulach@816
  2195
                if (!callPreprocessHook(ko['getBindingHandler'](key)))
jtulach@816
  2196
                    return;
jtulach@816
  2197
jtulach@816
  2198
                if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
jtulach@816
  2199
                    // For two-way bindings, provide a write method in case the value
jtulach@816
  2200
                    // isn't a writable observable.
jtulach@816
  2201
                    propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
jtulach@816
  2202
                }
jtulach@816
  2203
            }
jtulach@816
  2204
            // Values are wrapped in a function so that each value can be accessed independently
jtulach@816
  2205
            if (makeValueAccessors) {
jtulach@816
  2206
                val = 'function(){return ' + val + ' }';
jtulach@816
  2207
            }
jtulach@816
  2208
            resultStrings.push("'" + key + "':" + val);
jtulach@816
  2209
        }
jtulach@816
  2210
jtulach@816
  2211
        var resultStrings = [],
jtulach@816
  2212
            propertyAccessorResultStrings = [],
jtulach@816
  2213
            makeValueAccessors = bindingOptions['valueAccessors'],
jtulach@816
  2214
            bindingParams = bindingOptions['bindingParams'],
jtulach@816
  2215
            keyValueArray = typeof bindingsStringOrKeyValueArray === "string" ?
jtulach@816
  2216
                parseObjectLiteral(bindingsStringOrKeyValueArray) : bindingsStringOrKeyValueArray;
jtulach@816
  2217
jtulach@816
  2218
        ko.utils.arrayForEach(keyValueArray, function(keyValue) {
jtulach@816
  2219
            processKeyValue(keyValue.key || keyValue['unknown'], keyValue.value);
jtulach@816
  2220
        });
jtulach@816
  2221
jtulach@816
  2222
        if (propertyAccessorResultStrings.length)
jtulach@816
  2223
            processKeyValue('_ko_property_writers', "{" + propertyAccessorResultStrings.join(",") + " }");
jtulach@816
  2224
jtulach@816
  2225
        return resultStrings.join(",");
jtulach@816
  2226
    }
jtulach@816
  2227
jtulach@816
  2228
    return {
jtulach@816
  2229
        bindingRewriteValidators: [],
jtulach@816
  2230
jtulach@816
  2231
        twoWayBindings: twoWayBindings,
jtulach@816
  2232
jtulach@816
  2233
        parseObjectLiteral: parseObjectLiteral,
jtulach@816
  2234
jtulach@816
  2235
        preProcessBindings: preProcessBindings,
jtulach@816
  2236
jtulach@816
  2237
        keyValueArrayContainsKey: function(keyValueArray, key) {
jtulach@816
  2238
            for (var i = 0; i < keyValueArray.length; i++)
jtulach@816
  2239
                if (keyValueArray[i]['key'] == key)
jtulach@816
  2240
                    return true;
jtulach@816
  2241
            return false;
jtulach@816
  2242
        },
jtulach@816
  2243
jtulach@816
  2244
        // Internal, private KO utility for updating model properties from within bindings
jtulach@816
  2245
        // property:            If the property being updated is (or might be) an observable, pass it here
jtulach@816
  2246
        //                      If it turns out to be a writable observable, it will be written to directly
jtulach@816
  2247
        // allBindings:         An object with a get method to retrieve bindings in the current execution context.
jtulach@816
  2248
        //                      This will be searched for a '_ko_property_writers' property in case you're writing to a non-observable
jtulach@816
  2249
        // key:                 The key identifying the property to be written. Example: for { hasFocus: myValue }, write to 'myValue' by specifying the key 'hasFocus'
jtulach@816
  2250
        // value:               The value to be written
jtulach@816
  2251
        // checkIfDifferent:    If true, and if the property being written is a writable observable, the value will only be written if
jtulach@816
  2252
        //                      it is !== existing value on that writable observable
jtulach@816
  2253
        writeValueToProperty: function(property, allBindings, key, value, checkIfDifferent) {
jtulach@816
  2254
            if (!property || !ko.isObservable(property)) {
jtulach@816
  2255
                var propWriters = allBindings.get('_ko_property_writers');
jtulach@816
  2256
                if (propWriters && propWriters[key])
jtulach@816
  2257
                    propWriters[key](value);
jtulach@816
  2258
            } else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
jtulach@816
  2259
                property(value);
jtulach@816
  2260
            }
jtulach@816
  2261
        }
jtulach@816
  2262
    };
jtulach@816
  2263
})();
jtulach@816
  2264
jtulach@816
  2265
ko.exportSymbol('expressionRewriting', ko.expressionRewriting);
jtulach@816
  2266
ko.exportSymbol('expressionRewriting.bindingRewriteValidators', ko.expressionRewriting.bindingRewriteValidators);
jtulach@816
  2267
ko.exportSymbol('expressionRewriting.parseObjectLiteral', ko.expressionRewriting.parseObjectLiteral);
jtulach@816
  2268
ko.exportSymbol('expressionRewriting.preProcessBindings', ko.expressionRewriting.preProcessBindings);
jtulach@816
  2269
jtulach@816
  2270
// Making bindings explicitly declare themselves as "two way" isn't ideal in the long term (it would be better if
jtulach@816
  2271
// all bindings could use an official 'property writer' API without needing to declare that they might). However,
jtulach@816
  2272
// since this is not, and has never been, a public API (_ko_property_writers was never documented), it's acceptable
jtulach@816
  2273
// as an internal implementation detail in the short term.
jtulach@816
  2274
// For those developers who rely on _ko_property_writers in their custom bindings, we expose _twoWayBindings as an
jtulach@816
  2275
// undocumented feature that makes it relatively easy to upgrade to KO 3.0. However, this is still not an official
jtulach@816
  2276
// public API, and we reserve the right to remove it at any time if we create a real public property writers API.
jtulach@816
  2277
ko.exportSymbol('expressionRewriting._twoWayBindings', ko.expressionRewriting.twoWayBindings);
jtulach@816
  2278
jtulach@816
  2279
// For backward compatibility, define the following aliases. (Previously, these function names were misleading because
jtulach@816
  2280
// they referred to JSON specifically, even though they actually work with arbitrary JavaScript object literal expressions.)
jtulach@816
  2281
ko.exportSymbol('jsonExpressionRewriting', ko.expressionRewriting);
jtulach@816
  2282
ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.expressionRewriting.preProcessBindings);
jtulach@816
  2283
(function() {
jtulach@816
  2284
    // "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
jtulach@816
  2285
    // may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
jtulach@816
  2286
    // If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
jtulach@816
  2287
    // of that virtual hierarchy
jtulach@816
  2288
    //
jtulach@816
  2289
    // The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
jtulach@816
  2290
    // without having to scatter special cases all over the binding and templating code.
jtulach@816
  2291
jtulach@816
  2292
    // IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
jtulach@816
  2293
    // but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
jtulach@816
  2294
    // So, use node.text where available, and node.nodeValue elsewhere
jtulach@816
  2295
    var commentNodesHaveTextProperty = document && document.createComment("test").text === "<!--test-->";
jtulach@816
  2296
jtulach@816
  2297
    var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+([\s\S]+))?\s*-->$/ : /^\s*ko(?:\s+([\s\S]+))?\s*$/;
jtulach@816
  2298
    var endCommentRegex =   commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
jtulach@816
  2299
    var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
jtulach@816
  2300
jtulach@816
  2301
    function isStartComment(node) {
jtulach@816
  2302
        return (node.nodeType == 8) && startCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue);
jtulach@816
  2303
    }
jtulach@816
  2304
jtulach@816
  2305
    function isEndComment(node) {
jtulach@816
  2306
        return (node.nodeType == 8) && endCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue);
jtulach@816
  2307
    }
jtulach@816
  2308
jtulach@816
  2309
    function getVirtualChildren(startComment, allowUnbalanced) {
jtulach@816
  2310
        var currentNode = startComment;
jtulach@816
  2311
        var depth = 1;
jtulach@816
  2312
        var children = [];
jtulach@816
  2313
        while (currentNode = currentNode.nextSibling) {
jtulach@816
  2314
            if (isEndComment(currentNode)) {
jtulach@816
  2315
                depth--;
jtulach@816
  2316
                if (depth === 0)
jtulach@816
  2317
                    return children;
jtulach@816
  2318
            }
jtulach@816
  2319
jtulach@816
  2320
            children.push(currentNode);
jtulach@816
  2321
jtulach@816
  2322
            if (isStartComment(currentNode))
jtulach@816
  2323
                depth++;
jtulach@816
  2324
        }
jtulach@816
  2325
        if (!allowUnbalanced)
jtulach@816
  2326
            throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
jtulach@816
  2327
        return null;
jtulach@816
  2328
    }
jtulach@816
  2329
jtulach@816
  2330
    function getMatchingEndComment(startComment, allowUnbalanced) {
jtulach@816
  2331
        var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
jtulach@816
  2332
        if (allVirtualChildren) {
jtulach@816
  2333
            if (allVirtualChildren.length > 0)
jtulach@816
  2334
                return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
jtulach@816
  2335
            return startComment.nextSibling;
jtulach@816
  2336
        } else
jtulach@816
  2337
            return null; // Must have no matching end comment, and allowUnbalanced is true
jtulach@816
  2338
    }
jtulach@816
  2339
jtulach@816
  2340
    function getUnbalancedChildTags(node) {
jtulach@816
  2341
        // e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span>
jtulach@816
  2342
        //       from <div>OK</div><!-- /ko --><!-- /ko -->,             returns: <!-- /ko --><!-- /ko -->
jtulach@816
  2343
        var childNode = node.firstChild, captureRemaining = null;
jtulach@816
  2344
        if (childNode) {
jtulach@816
  2345
            do {
jtulach@816
  2346
                if (captureRemaining)                   // We already hit an unbalanced node and are now just scooping up all subsequent nodes
jtulach@816
  2347
                    captureRemaining.push(childNode);
jtulach@816
  2348
                else if (isStartComment(childNode)) {
jtulach@816
  2349
                    var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true);
jtulach@816
  2350
                    if (matchingEndComment)             // It's a balanced tag, so skip immediately to the end of this virtual set
jtulach@816
  2351
                        childNode = matchingEndComment;
jtulach@816
  2352
                    else
jtulach@816
  2353
                        captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point
jtulach@816
  2354
                } else if (isEndComment(childNode)) {
jtulach@816
  2355
                    captureRemaining = [childNode];     // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing
jtulach@816
  2356
                }
jtulach@816
  2357
            } while (childNode = childNode.nextSibling);
jtulach@816
  2358
        }
jtulach@816
  2359
        return captureRemaining;
jtulach@816
  2360
    }
jtulach@816
  2361
jtulach@816
  2362
    ko.virtualElements = {
jtulach@816
  2363
        allowedBindings: {},
jtulach@816
  2364
jtulach@816
  2365
        childNodes: function(node) {
jtulach@816
  2366
            return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
jtulach@816
  2367
        },
jtulach@816
  2368
jtulach@816
  2369
        emptyNode: function(node) {
jtulach@816
  2370
            if (!isStartComment(node))
jtulach@816
  2371
                ko.utils.emptyDomNode(node);
jtulach@816
  2372
            else {
jtulach@816
  2373
                var virtualChildren = ko.virtualElements.childNodes(node);
jtulach@816
  2374
                for (var i = 0, j = virtualChildren.length; i < j; i++)
jtulach@816
  2375
                    ko.removeNode(virtualChildren[i]);
jtulach@816
  2376
            }
jtulach@816
  2377
        },
jtulach@816
  2378
jtulach@816
  2379
        setDomNodeChildren: function(node, childNodes) {
jtulach@816
  2380
            if (!isStartComment(node))
jtulach@816
  2381
                ko.utils.setDomNodeChildren(node, childNodes);
jtulach@816
  2382
            else {
jtulach@816
  2383
                ko.virtualElements.emptyNode(node);
jtulach@816
  2384
                var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
jtulach@816
  2385
                for (var i = 0, j = childNodes.length; i < j; i++)
jtulach@816
  2386
                    endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
jtulach@816
  2387
            }
jtulach@816
  2388
        },
jtulach@816
  2389
jtulach@816
  2390
        prepend: function(containerNode, nodeToPrepend) {
jtulach@816
  2391
            if (!isStartComment(containerNode)) {
jtulach@816
  2392
                if (containerNode.firstChild)
jtulach@816
  2393
                    containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
jtulach@816
  2394
                else
jtulach@816
  2395
                    containerNode.appendChild(nodeToPrepend);
jtulach@816
  2396
            } else {
jtulach@816
  2397
                // Start comments must always have a parent and at least one following sibling (the end comment)
jtulach@816
  2398
                containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
jtulach@816
  2399
            }
jtulach@816
  2400
        },
jtulach@816
  2401
jtulach@816
  2402
        insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
jtulach@816
  2403
            if (!insertAfterNode) {
jtulach@816
  2404
                ko.virtualElements.prepend(containerNode, nodeToInsert);
jtulach@816
  2405
            } else if (!isStartComment(containerNode)) {
jtulach@816
  2406
                // Insert after insertion point
jtulach@816
  2407
                if (insertAfterNode.nextSibling)
jtulach@816
  2408
                    containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
jtulach@816
  2409
                else
jtulach@816
  2410
                    containerNode.appendChild(nodeToInsert);
jtulach@816
  2411
            } else {
jtulach@816
  2412
                // Children of start comments must always have a parent and at least one following sibling (the end comment)
jtulach@816
  2413
                containerNode.parentNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
jtulach@816
  2414
            }
jtulach@816
  2415
        },
jtulach@816
  2416
jtulach@816
  2417
        firstChild: function(node) {
jtulach@816
  2418
            if (!isStartComment(node))
jtulach@816
  2419
                return node.firstChild;
jtulach@816
  2420
            if (!node.nextSibling || isEndComment(node.nextSibling))
jtulach@816
  2421
                return null;
jtulach@816
  2422
            return node.nextSibling;
jtulach@816
  2423
        },
jtulach@816
  2424
jtulach@816
  2425
        nextSibling: function(node) {
jtulach@816
  2426
            if (isStartComment(node))
jtulach@816
  2427
                node = getMatchingEndComment(node);
jtulach@816
  2428
            if (node.nextSibling && isEndComment(node.nextSibling))
jtulach@816
  2429
                return null;
jtulach@816
  2430
            return node.nextSibling;
jtulach@816
  2431
        },
jtulach@816
  2432
jtulach@816
  2433
        hasBindingValue: isStartComment,
jtulach@816
  2434
jtulach@816
  2435
        virtualNodeBindingValue: function(node) {
jtulach@816
  2436
            var regexMatch = (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
jtulach@816
  2437
            return regexMatch ? regexMatch[1] : null;
jtulach@816
  2438
        },
jtulach@816
  2439
jtulach@816
  2440
        normaliseVirtualElementDomStructure: function(elementVerified) {
jtulach@816
  2441
            // Workaround for https://github.com/SteveSanderson/knockout/issues/155
jtulach@816
  2442
            // (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
jtulach@816
  2443
            // that are direct descendants of <ul> into the preceding <li>)
jtulach@816
  2444
            if (!htmlTagsWithOptionallyClosingChildren[ko.utils.tagNameLower(elementVerified)])
jtulach@816
  2445
                return;
jtulach@816
  2446
jtulach@816
  2447
            // Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags
jtulach@816
  2448
            // must be intended to appear *after* that child, so move them there.
jtulach@816
  2449
            var childNode = elementVerified.firstChild;
jtulach@816
  2450
            if (childNode) {
jtulach@816
  2451
                do {
jtulach@816
  2452
                    if (childNode.nodeType === 1) {
jtulach@816
  2453
                        var unbalancedTags = getUnbalancedChildTags(childNode);
jtulach@816
  2454
                        if (unbalancedTags) {
jtulach@816
  2455
                            // Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child
jtulach@816
  2456
                            var nodeToInsertBefore = childNode.nextSibling;
jtulach@816
  2457
                            for (var i = 0; i < unbalancedTags.length; i++) {
jtulach@816
  2458
                                if (nodeToInsertBefore)
jtulach@816
  2459
                                    elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore);
jtulach@816
  2460
                                else
jtulach@816
  2461
                                    elementVerified.appendChild(unbalancedTags[i]);
jtulach@816
  2462
                            }
jtulach@816
  2463
                        }
jtulach@816
  2464
                    }
jtulach@816
  2465
                } while (childNode = childNode.nextSibling);
jtulach@816
  2466
            }
jtulach@816
  2467
        }
jtulach@816
  2468
    };
jtulach@816
  2469
})();
jtulach@816
  2470
ko.exportSymbol('virtualElements', ko.virtualElements);
jtulach@816
  2471
ko.exportSymbol('virtualElements.allowedBindings', ko.virtualElements.allowedBindings);
jtulach@816
  2472
ko.exportSymbol('virtualElements.emptyNode', ko.virtualElements.emptyNode);
jtulach@816
  2473
//ko.exportSymbol('virtualElements.firstChild', ko.virtualElements.firstChild);     // firstChild is not minified
jtulach@816
  2474
ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter);
jtulach@816
  2475
//ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling);   // nextSibling is not minified
jtulach@816
  2476
ko.exportSymbol('virtualElements.prepend', ko.virtualElements.prepend);
jtulach@816
  2477
ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomNodeChildren);
jtulach@816
  2478
(function() {
jtulach@816
  2479
    var defaultBindingAttributeName = "data-bind";
jtulach@816
  2480
jtulach@816
  2481
    ko.bindingProvider = function() {
jtulach@816
  2482
        this.bindingCache = {};
jtulach@816
  2483
    };
jtulach@816
  2484
jtulach@816
  2485
    ko.utils.extend(ko.bindingProvider.prototype, {
jtulach@816
  2486
        'nodeHasBindings': function(node) {
jtulach@816
  2487
            switch (node.nodeType) {
jtulach@816
  2488
                case 1: // Element
jtulach@816
  2489
                    return node.getAttribute(defaultBindingAttributeName) != null
jtulach@816
  2490
                        || ko.components['getComponentNameForNode'](node);
jtulach@816
  2491
                case 8: // Comment node
jtulach@816
  2492
                    return ko.virtualElements.hasBindingValue(node);
jtulach@816
  2493
                default: return false;
jtulach@816
  2494
            }
jtulach@816
  2495
        },
jtulach@816
  2496
jtulach@816
  2497
        'getBindings': function(node, bindingContext) {
jtulach@816
  2498
            var bindingsString = this['getBindingsString'](node, bindingContext),
jtulach@816
  2499
                parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
jtulach@816
  2500
            return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ false);
jtulach@816
  2501
        },
jtulach@816
  2502
jtulach@816
  2503
        'getBindingAccessors': function(node, bindingContext) {
jtulach@816
  2504
            var bindingsString = this['getBindingsString'](node, bindingContext),
jtulach@816
  2505
                parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node, { 'valueAccessors': true }) : null;
jtulach@816
  2506
            return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ true);
jtulach@816
  2507
        },
jtulach@816
  2508
jtulach@816
  2509
        // The following function is only used internally by this default provider.
jtulach@816
  2510
        // It's not part of the interface definition for a general binding provider.
jtulach@816
  2511
        'getBindingsString': function(node, bindingContext) {
jtulach@816
  2512
            switch (node.nodeType) {
jtulach@816
  2513
                case 1: return node.getAttribute(defaultBindingAttributeName);   // Element
jtulach@816
  2514
                case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
jtulach@816
  2515
                default: return null;
jtulach@816
  2516
            }
jtulach@816
  2517
        },
jtulach@816
  2518
jtulach@816
  2519
        // The following function is only used internally by this default provider.
jtulach@816
  2520
        // It's not part of the interface definition for a general binding provider.
jtulach@816
  2521
        'parseBindingsString': function(bindingsString, bindingContext, node, options) {
jtulach@816
  2522
            try {
jtulach@816
  2523
                var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache, options);
jtulach@816
  2524
                return bindingFunction(bindingContext, node);
jtulach@816
  2525
            } catch (ex) {
jtulach@816
  2526
                ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message;
jtulach@816
  2527
                throw ex;
jtulach@816
  2528
            }
jtulach@816
  2529
        }
jtulach@816
  2530
    });
jtulach@816
  2531
jtulach@816
  2532
    ko.bindingProvider['instance'] = new ko.bindingProvider();
jtulach@816
  2533
jtulach@816
  2534
    function createBindingsStringEvaluatorViaCache(bindingsString, cache, options) {
jtulach@816
  2535
        var cacheKey = bindingsString + (options && options['valueAccessors'] || '');
jtulach@816
  2536
        return cache[cacheKey]
jtulach@816
  2537
            || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, options));
jtulach@816
  2538
    }
jtulach@816
  2539
jtulach@816
  2540
    function createBindingsStringEvaluator(bindingsString, options) {
jtulach@816
  2541
        // Build the source for a function that evaluates "expression"
jtulach@816
  2542
        // For each scope variable, add an extra level of "with" nesting
jtulach@816
  2543
        // Example result: with(sc1) { with(sc0) { return (expression) } }
jtulach@816
  2544
        var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options),
jtulach@816
  2545
            functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
jtulach@816
  2546
        return new Function("$context", "$element", functionBody);
jtulach@816
  2547
    }
jtulach@816
  2548
})();
jtulach@816
  2549
jtulach@816
  2550
ko.exportSymbol('bindingProvider', ko.bindingProvider);
jtulach@816
  2551
(function () {
jtulach@816
  2552
    ko.bindingHandlers = {};
jtulach@816
  2553
jtulach@816
  2554
    // The following element types will not be recursed into during binding. In the future, we
jtulach@816
  2555
    // may consider adding <template> to this list, because such elements' contents are always
jtulach@816
  2556
    // intended to be bound in a different context from where they appear in the document.
jtulach@816
  2557
    var bindingDoesNotRecurseIntoElementTypes = {
jtulach@816
  2558
        // Don't want bindings that operate on text nodes to mutate <script> contents,
jtulach@816
  2559
        // because it's unexpected and a potential XSS issue
jtulach@816
  2560
        'script': true
jtulach@816
  2561
    };
jtulach@816
  2562
jtulach@816
  2563
    // Use an overridable method for retrieving binding handlers so that a plugins may support dynamically created handlers
jtulach@816
  2564
    ko['getBindingHandler'] = function(bindingKey) {
jtulach@816
  2565
        return ko.bindingHandlers[bindingKey];
jtulach@816
  2566
    };
jtulach@816
  2567
jtulach@816
  2568
    // The ko.bindingContext constructor is only called directly to create the root context. For child
jtulach@816
  2569
    // contexts, use bindingContext.createChildContext or bindingContext.extend.
jtulach@816
  2570
    ko.bindingContext = function(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback) {
jtulach@816
  2571
jtulach@816
  2572
        // The binding context object includes static properties for the current, parent, and root view models.
jtulach@816
  2573
        // If a view model is actually stored in an observable, the corresponding binding context object, and
jtulach@816
  2574
        // any child contexts, must be updated when the view model is changed.
jtulach@816
  2575
        function updateContext() {
jtulach@816
  2576
            // Most of the time, the context will directly get a view model object, but if a function is given,
jtulach@816
  2577
            // we call the function to retrieve the view model. If the function accesses any obsevables or returns
jtulach@816
  2578
            // an observable, the dependency is tracked, and those observables can later cause the binding
jtulach@816
  2579
            // context to be updated.
jtulach@816
  2580
            var dataItemOrObservable = isFunc ? dataItemOrAccessor() : dataItemOrAccessor,
jtulach@816
  2581
                dataItem = ko.utils.unwrapObservable(dataItemOrObservable);
jtulach@816
  2582
jtulach@816
  2583
            if (parentContext) {
jtulach@816
  2584
                // When a "parent" context is given, register a dependency on the parent context. Thus whenever the
jtulach@816
  2585
                // parent context is updated, this context will also be updated.
jtulach@816
  2586
                if (parentContext._subscribable)
jtulach@816
  2587
                    parentContext._subscribable();
jtulach@816
  2588
jtulach@816
  2589
                // Copy $root and any custom properties from the parent context
jtulach@816
  2590
                ko.utils.extend(self, parentContext);
jtulach@816
  2591
jtulach@816
  2592
                // Because the above copy overwrites our own properties, we need to reset them.
jtulach@816
  2593
                // During the first execution, "subscribable" isn't set, so don't bother doing the update then.
jtulach@816
  2594
                if (subscribable) {
jtulach@816
  2595
                    self._subscribable = subscribable;
jtulach@816
  2596
                }
jtulach@816
  2597
            } else {
jtulach@816
  2598
                self['$parents'] = [];
jtulach@816
  2599
                self['$root'] = dataItem;
jtulach@816
  2600
jtulach@816
  2601
                // Export 'ko' in the binding context so it will be available in bindings and templates
jtulach@816
  2602
                // even if 'ko' isn't exported as a global, such as when using an AMD loader.
jtulach@816
  2603
                // See https://github.com/SteveSanderson/knockout/issues/490
jtulach@816
  2604
                self['ko'] = ko;
jtulach@816
  2605
            }
jtulach@816
  2606
            self['$rawData'] = dataItemOrObservable;
jtulach@816
  2607
            self['$data'] = dataItem;
jtulach@816
  2608
            if (dataItemAlias)
jtulach@816
  2609
                self[dataItemAlias] = dataItem;
jtulach@816
  2610
jtulach@816
  2611
            // The extendCallback function is provided when creating a child context or extending a context.
jtulach@816
  2612
            // It handles the specific actions needed to finish setting up the binding context. Actions in this
jtulach@816
  2613
            // function could also add dependencies to this binding context.
jtulach@816
  2614
            if (extendCallback)
jtulach@816
  2615
                extendCallback(self, parentContext, dataItem);
jtulach@816
  2616
jtulach@816
  2617
            return self['$data'];
jtulach@816
  2618
        }
jtulach@816
  2619
        function disposeWhen() {
jtulach@816
  2620
            return nodes && !ko.utils.anyDomNodeIsAttachedToDocument(nodes);
jtulach@816
  2621
        }
jtulach@816
  2622
jtulach@816
  2623
        var self = this,
jtulach@816
  2624
            isFunc = typeof(dataItemOrAccessor) == "function" && !ko.isObservable(dataItemOrAccessor),
jtulach@816
  2625
            nodes,
jtulach@816
  2626
            subscribable = ko.dependentObservable(updateContext, null, { disposeWhen: disposeWhen, disposeWhenNodeIsRemoved: true });
jtulach@816
  2627
jtulach@816
  2628
        // At this point, the binding context has been initialized, and the "subscribable" computed observable is
jtulach@816
  2629
        // subscribed to any observables that were accessed in the process. If there is nothing to track, the
jtulach@816
  2630
        // computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in
jtulach@816
  2631
        // the context object.
jtulach@816
  2632
        if (subscribable.isActive()) {
jtulach@816
  2633
            self._subscribable = subscribable;
jtulach@816
  2634
jtulach@816
  2635
            // Always notify because even if the model ($data) hasn't changed, other context properties might have changed
jtulach@816
  2636
            subscribable['equalityComparer'] = null;
jtulach@816
  2637
jtulach@816
  2638
            // We need to be able to dispose of this computed observable when it's no longer needed. This would be
jtulach@816
  2639
            // easy if we had a single node to watch, but binding contexts can be used by many different nodes, and
jtulach@816
  2640
            // we cannot assume that those nodes have any relation to each other. So instead we track any node that
jtulach@816
  2641
            // the context is attached to, and dispose the computed when all of those nodes have been cleaned.
jtulach@816
  2642
jtulach@816
  2643
            // Add properties to *subscribable* instead of *self* because any properties added to *self* may be overwritten on updates
jtulach@816
  2644
            nodes = [];
jtulach@816
  2645
            subscribable._addNode = function(node) {
jtulach@816
  2646
                nodes.push(node);
jtulach@816
  2647
                ko.utils.domNodeDisposal.addDisposeCallback(node, function(node) {
jtulach@816
  2648
                    ko.utils.arrayRemoveItem(nodes, node);
jtulach@816
  2649
                    if (!nodes.length) {
jtulach@816
  2650
                        subscribable.dispose();
jtulach@816
  2651
                        self._subscribable = subscribable = undefined;
jtulach@816
  2652
                    }
jtulach@816
  2653
                });
jtulach@816
  2654
            };
jtulach@816
  2655
        }
jtulach@816
  2656
    }
jtulach@816
  2657
jtulach@816
  2658
    // Extend the binding context hierarchy with a new view model object. If the parent context is watching
jtulach@816
  2659
    // any obsevables, the new child context will automatically get a dependency on the parent context.
jtulach@816
  2660
    // But this does not mean that the $data value of the child context will also get updated. If the child
jtulach@816
  2661
    // view model also depends on the parent view model, you must provide a function that returns the correct
jtulach@816
  2662
    // view model on each update.
jtulach@816
  2663
    ko.bindingContext.prototype['createChildContext'] = function (dataItemOrAccessor, dataItemAlias, extendCallback) {
jtulach@816
  2664
        return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, function(self, parentContext) {
jtulach@816
  2665
            // Extend the context hierarchy by setting the appropriate pointers
jtulach@816
  2666
            self['$parentContext'] = parentContext;
jtulach@816
  2667
            self['$parent'] = parentContext['$data'];
jtulach@816
  2668
            self['$parents'] = (parentContext['$parents'] || []).slice(0);
jtulach@816
  2669
            self['$parents'].unshift(self['$parent']);
jtulach@816
  2670
            if (extendCallback)
jtulach@816
  2671
                extendCallback(self);
jtulach@816
  2672
        });
jtulach@816
  2673
    };
jtulach@816
  2674
jtulach@816
  2675
    // Extend the binding context with new custom properties. This doesn't change the context hierarchy.
jtulach@816
  2676
    // Similarly to "child" contexts, provide a function here to make sure that the correct values are set
jtulach@816
  2677
    // when an observable view model is updated.
jtulach@816
  2678
    ko.bindingContext.prototype['extend'] = function(properties) {
jtulach@816
  2679
        // If the parent context references an observable view model, "_subscribable" will always be the
jtulach@816
  2680
        // latest view model object. If not, "_subscribable" isn't set, and we can use the static "$data" value.
jtulach@816
  2681
        return new ko.bindingContext(this._subscribable || this['$data'], this, null, function(self, parentContext) {
jtulach@816
  2682
            // This "child" context doesn't directly track a parent observable view model,
jtulach@816
  2683
            // so we need to manually set the $rawData value to match the parent.
jtulach@816
  2684
            self['$rawData'] = parentContext['$rawData'];
jtulach@816
  2685
            ko.utils.extend(self, typeof(properties) == "function" ? properties() : properties);
jtulach@816
  2686
        });
jtulach@816
  2687
    };
jtulach@816
  2688
jtulach@816
  2689
    // Returns the valueAccesor function for a binding value
jtulach@816
  2690
    function makeValueAccessor(value) {
jtulach@816
  2691
        return function() {
jtulach@816
  2692
            return value;
jtulach@816
  2693
        };
jtulach@816
  2694
    }
jtulach@816
  2695
jtulach@816
  2696
    // Returns the value of a valueAccessor function
jtulach@816
  2697
    function evaluateValueAccessor(valueAccessor) {
jtulach@816
  2698
        return valueAccessor();
jtulach@816
  2699
    }
jtulach@816
  2700
jtulach@816
  2701
    // Given a function that returns bindings, create and return a new object that contains
jtulach@816
  2702
    // binding value-accessors functions. Each accessor function calls the original function
jtulach@816
  2703
    // so that it always gets the latest value and all dependencies are captured. This is used
jtulach@816
  2704
    // by ko.applyBindingsToNode and getBindingsAndMakeAccessors.
jtulach@816
  2705
    function makeAccessorsFromFunction(callback) {
jtulach@816
  2706
        return ko.utils.objectMap(ko.dependencyDetection.ignore(callback), function(value, key) {
jtulach@816
  2707
            return function() {
jtulach@816
  2708
                return callback()[key];
jtulach@816
  2709
            };
jtulach@816
  2710
        });
jtulach@816
  2711
    }
jtulach@816
  2712
jtulach@816
  2713
    // Given a bindings function or object, create and return a new object that contains
jtulach@816
  2714
    // binding value-accessors functions. This is used by ko.applyBindingsToNode.
jtulach@816
  2715
    function makeBindingAccessors(bindings, context, node) {
jtulach@816
  2716
        if (typeof bindings === 'function') {
jtulach@816
  2717
            return makeAccessorsFromFunction(bindings.bind(null, context, node));
jtulach@816
  2718
        } else {
jtulach@816
  2719
            return ko.utils.objectMap(bindings, makeValueAccessor);
jtulach@816
  2720
        }
jtulach@816
  2721
    }
jtulach@816
  2722
jtulach@816
  2723
    // This function is used if the binding provider doesn't include a getBindingAccessors function.
jtulach@816
  2724
    // It must be called with 'this' set to the provider instance.
jtulach@816
  2725
    function getBindingsAndMakeAccessors(node, context) {
jtulach@816
  2726
        return makeAccessorsFromFunction(this['getBindings'].bind(this, node, context));
jtulach@816
  2727
    }
jtulach@816
  2728
jtulach@816
  2729
    function validateThatBindingIsAllowedForVirtualElements(bindingName) {
jtulach@816
  2730
        var validator = ko.virtualElements.allowedBindings[bindingName];
jtulach@816
  2731
        if (!validator)
jtulach@816
  2732
            throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
jtulach@816
  2733
    }
jtulach@816
  2734
jtulach@816
  2735
    function applyBindingsToDescendantsInternal (bindingContext, elementOrVirtualElement, bindingContextsMayDifferFromDomParentElement) {
jtulach@816
  2736
        var currentChild,
jtulach@816
  2737
            nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement),
jtulach@816
  2738
            provider = ko.bindingProvider['instance'],
jtulach@816
  2739
            preprocessNode = provider['preprocessNode'];
jtulach@816
  2740
jtulach@816
  2741
        // Preprocessing allows a binding provider to mutate a node before bindings are applied to it. For example it's
jtulach@816
  2742
        // possible to insert new siblings after it, and/or replace the node with a different one. This can be used to
jtulach@816
  2743
        // implement custom binding syntaxes, such as {{ value }} for string interpolation, or custom element types that
jtulach@816
  2744
        // trigger insertion of <template> contents at that point in the document.
jtulach@816
  2745
        if (preprocessNode) {
jtulach@816
  2746
            while (currentChild = nextInQueue) {
jtulach@816
  2747
                nextInQueue = ko.virtualElements.nextSibling(currentChild);
jtulach@816
  2748
                preprocessNode.call(provider, currentChild);
jtulach@816
  2749
            }
jtulach@816
  2750
            // Reset nextInQueue for the next loop
jtulach@816
  2751
            nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
jtulach@816
  2752
        }
jtulach@816
  2753
jtulach@816
  2754
        while (currentChild = nextInQueue) {
jtulach@816
  2755
            // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
jtulach@816
  2756
            nextInQueue = ko.virtualElements.nextSibling(currentChild);
jtulach@816
  2757
            applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild, bindingContextsMayDifferFromDomParentElement);
jtulach@816
  2758
        }
jtulach@816
  2759
    }
jtulach@816
  2760
jtulach@816
  2761
    function applyBindingsToNodeAndDescendantsInternal (bindingContext, nodeVerified, bindingContextMayDifferFromDomParentElement) {
jtulach@816
  2762
        var shouldBindDescendants = true;
jtulach@816
  2763
jtulach@816
  2764
        // Perf optimisation: Apply bindings only if...
jtulach@816
  2765
        // (1) We need to store the binding context on this node (because it may differ from the DOM parent node's binding context)
jtulach@816
  2766
        //     Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those
jtulach@816
  2767
        // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
jtulach@816
  2768
        var isElement = (nodeVerified.nodeType === 1);
jtulach@816
  2769
        if (isElement) // Workaround IE <= 8 HTML parsing weirdness
jtulach@816
  2770
            ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
jtulach@816
  2771
jtulach@816
  2772
        var shouldApplyBindings = (isElement && bindingContextMayDifferFromDomParentElement)             // Case (1)
jtulach@816
  2773
                               || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified);       // Case (2)
jtulach@816
  2774
        if (shouldApplyBindings)
jtulach@816
  2775
            shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, bindingContext, bindingContextMayDifferFromDomParentElement)['shouldBindDescendants'];
jtulach@816
  2776
jtulach@816
  2777
        if (shouldBindDescendants && !bindingDoesNotRecurseIntoElementTypes[ko.utils.tagNameLower(nodeVerified)]) {
jtulach@816
  2778
            // We're recursing automatically into (real or virtual) child nodes without changing binding contexts. So,
jtulach@816
  2779
            //  * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
jtulach@816
  2780
            //    hence bindingContextsMayDifferFromDomParentElement is false
jtulach@816
  2781
            //  * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
jtulach@816
  2782
            //    skip over any number of intermediate virtual elements, any of which might define a custom binding context,
jtulach@816
  2783
            //    hence bindingContextsMayDifferFromDomParentElement is true
jtulach@816
  2784
            applyBindingsToDescendantsInternal(bindingContext, nodeVerified, /* bindingContextsMayDifferFromDomParentElement: */ !isElement);
jtulach@816
  2785
        }
jtulach@816
  2786
    }
jtulach@816
  2787
jtulach@816
  2788
    var boundElementDomDataKey = ko.utils.domData.nextKey();
jtulach@816
  2789
jtulach@816
  2790
jtulach@816
  2791
    function topologicalSortBindings(bindings) {
jtulach@816
  2792
        // Depth-first sort
jtulach@816
  2793
        var result = [],                // The list of key/handler pairs that we will return
jtulach@816
  2794
            bindingsConsidered = {},    // A temporary record of which bindings are already in 'result'
jtulach@816
  2795
            cyclicDependencyStack = []; // Keeps track of a depth-search so that, if there's a cycle, we know which bindings caused it
jtulach@816
  2796
        ko.utils.objectForEach(bindings, function pushBinding(bindingKey) {
jtulach@816
  2797
            if (!bindingsConsidered[bindingKey]) {
jtulach@816
  2798
                var binding = ko['getBindingHandler'](bindingKey);
jtulach@816
  2799
                if (binding) {
jtulach@816
  2800
                    // First add dependencies (if any) of the current binding
jtulach@816
  2801
                    if (binding['after']) {
jtulach@816
  2802
                        cyclicDependencyStack.push(bindingKey);
jtulach@816
  2803
                        ko.utils.arrayForEach(binding['after'], function(bindingDependencyKey) {
jtulach@816
  2804
                            if (bindings[bindingDependencyKey]) {
jtulach@816
  2805
                                if (ko.utils.arrayIndexOf(cyclicDependencyStack, bindingDependencyKey) !== -1) {
jtulach@816
  2806
                                    throw Error("Cannot combine the following bindings, because they have a cyclic dependency: " + cyclicDependencyStack.join(", "));
jtulach@816
  2807
                                } else {
jtulach@816
  2808
                                    pushBinding(bindingDependencyKey);
jtulach@816
  2809
                                }
jtulach@816
  2810
                            }
jtulach@816
  2811
                        });
jtulach@816
  2812
                        cyclicDependencyStack.length--;
jtulach@816
  2813
                    }
jtulach@816
  2814
                    // Next add the current binding
jtulach@816
  2815
                    result.push({ key: bindingKey, handler: binding });
jtulach@816
  2816
                }
jtulach@816
  2817
                bindingsConsidered[bindingKey] = true;
jtulach@816
  2818
            }
jtulach@816
  2819
        });
jtulach@816
  2820
jtulach@816
  2821
        return result;
jtulach@816
  2822
    }
jtulach@816
  2823
jtulach@816
  2824
    function applyBindingsToNodeInternal(node, sourceBindings, bindingContext, bindingContextMayDifferFromDomParentElement) {
jtulach@816
  2825
        // Prevent multiple applyBindings calls for the same node, except when a binding value is specified
jtulach@816
  2826
        var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
jtulach@816
  2827
        if (!sourceBindings) {
jtulach@816
  2828
            if (alreadyBound) {
jtulach@816
  2829
                throw Error("You cannot apply bindings multiple times to the same element.");
jtulach@816
  2830
            }
jtulach@816
  2831
            ko.utils.domData.set(node, boundElementDomDataKey, true);
jtulach@816
  2832
        }
jtulach@816
  2833
jtulach@816
  2834
        // Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
jtulach@816
  2835
        // we can easily recover it just by scanning up the node's ancestors in the DOM
jtulach@816
  2836
        // (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
jtulach@816
  2837
        if (!alreadyBound && bindingContextMayDifferFromDomParentElement)
jtulach@816
  2838
            ko.storedBindingContextForNode(node, bindingContext);
jtulach@816
  2839
jtulach@816
  2840
        // Use bindings if given, otherwise fall back on asking the bindings provider to give us some bindings
jtulach@816
  2841
        var bindings;
jtulach@816
  2842
        if (sourceBindings && typeof sourceBindings !== 'function') {
jtulach@816
  2843
            bindings = sourceBindings;
jtulach@816
  2844
        } else {
jtulach@816
  2845
            var provider = ko.bindingProvider['instance'],
jtulach@816
  2846
                getBindings = provider['getBindingAccessors'] || getBindingsAndMakeAccessors;
jtulach@816
  2847
jtulach@816
  2848
            // Get the binding from the provider within a computed observable so that we can update the bindings whenever
jtulach@816
  2849
            // the binding context is updated or if the binding provider accesses observables.
jtulach@816
  2850
            var bindingsUpdater = ko.dependentObservable(
jtulach@816
  2851
                function() {
jtulach@816
  2852
                    bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext);
jtulach@816
  2853
                    // Register a dependency on the binding context to support obsevable view models.
jtulach@816
  2854
                    if (bindings && bindingContext._subscribable)
jtulach@816
  2855
                        bindingContext._subscribable();
jtulach@816
  2856
                    return bindings;
jtulach@816
  2857
                },
jtulach@816
  2858
                null, { disposeWhenNodeIsRemoved: node }
jtulach@816
  2859
            );
jtulach@816
  2860
jtulach@816
  2861
            if (!bindings || !bindingsUpdater.isActive())
jtulach@816
  2862
                bindingsUpdater = null;
jtulach@816
  2863
        }
jtulach@816
  2864
jtulach@816
  2865
        var bindingHandlerThatControlsDescendantBindings;
jtulach@816
  2866
        if (bindings) {
jtulach@816
  2867
            // Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding
jtulach@816
  2868
            // context update), just return the value accessor from the binding. Otherwise, return a function that always gets
jtulach@816
  2869
            // the latest binding value and registers a dependency on the binding updater.
jtulach@816
  2870
            var getValueAccessor = bindingsUpdater
jtulach@816
  2871
                ? function(bindingKey) {
jtulach@816
  2872
                    return function() {
jtulach@816
  2873
                        return evaluateValueAccessor(bindingsUpdater()[bindingKey]);
jtulach@816
  2874
                    };
jtulach@816
  2875
                } : function(bindingKey) {
jtulach@816
  2876
                    return bindings[bindingKey];
jtulach@816
  2877
                };
jtulach@816
  2878
jtulach@816
  2879
            // Use of allBindings as a function is maintained for backwards compatibility, but its use is deprecated
jtulach@816
  2880
            function allBindings() {
jtulach@816
  2881
                return ko.utils.objectMap(bindingsUpdater ? bindingsUpdater() : bindings, evaluateValueAccessor);
jtulach@816
  2882
            }
jtulach@816
  2883
            // The following is the 3.x allBindings API
jtulach@816
  2884
            allBindings['get'] = function(key) {
jtulach@816
  2885
                return bindings[key] && evaluateValueAccessor(getValueAccessor(key));
jtulach@816
  2886
            };
jtulach@816
  2887
            allBindings['has'] = function(key) {
jtulach@816
  2888
                return key in bindings;
jtulach@816
  2889
            };
jtulach@816
  2890
jtulach@816
  2891
            // First put the bindings into the right order
jtulach@816
  2892
            var orderedBindings = topologicalSortBindings(bindings);
jtulach@816
  2893
jtulach@816
  2894
            // Go through the sorted bindings, calling init and update for each
jtulach@816
  2895
            ko.utils.arrayForEach(orderedBindings, function(bindingKeyAndHandler) {
jtulach@816
  2896
                // Note that topologicalSortBindings has already filtered out any nonexistent binding handlers,
jtulach@816
  2897
                // so bindingKeyAndHandler.handler will always be nonnull.
jtulach@816
  2898
                var handlerInitFn = bindingKeyAndHandler.handler["init"],
jtulach@816
  2899
                    handlerUpdateFn = bindingKeyAndHandler.handler["update"],
jtulach@816
  2900
                    bindingKey = bindingKeyAndHandler.key;
jtulach@816
  2901
jtulach@816
  2902
                if (node.nodeType === 8) {
jtulach@816
  2903
                    validateThatBindingIsAllowedForVirtualElements(bindingKey);
jtulach@816
  2904
                }
jtulach@816
  2905
jtulach@816
  2906
                try {
jtulach@816
  2907
                    // Run init, ignoring any dependencies
jtulach@816
  2908
                    if (typeof handlerInitFn == "function") {
jtulach@816
  2909
                        ko.dependencyDetection.ignore(function() {
jtulach@816
  2910
                            var initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext);
jtulach@816
  2911
jtulach@816
  2912
                            // If this binding handler claims to control descendant bindings, make a note of this
jtulach@816
  2913
                            if (initResult && initResult['controlsDescendantBindings']) {
jtulach@816
  2914
                                if (bindingHandlerThatControlsDescendantBindings !== undefined)
jtulach@816
  2915
                                    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.");
jtulach@816
  2916
                                bindingHandlerThatControlsDescendantBindings = bindingKey;
jtulach@816
  2917
                            }
jtulach@816
  2918
                        });
jtulach@816
  2919
                    }
jtulach@816
  2920
jtulach@816
  2921
                    // Run update in its own computed wrapper
jtulach@816
  2922
                    if (typeof handlerUpdateFn == "function") {
jtulach@816
  2923
                        ko.dependentObservable(
jtulach@816
  2924
                            function() {
jtulach@816
  2925
                                handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext);
jtulach@816
  2926
                            },
jtulach@816
  2927
                            null,
jtulach@816
  2928
                            { disposeWhenNodeIsRemoved: node }
jtulach@816
  2929
                        );
jtulach@816
  2930
                    }
jtulach@816
  2931
                } catch (ex) {
jtulach@816
  2932
                    ex.message = "Unable to process binding \"" + bindingKey + ": " + bindings[bindingKey] + "\"\nMessage: " + ex.message;
jtulach@816
  2933
                    throw ex;
jtulach@816
  2934
                }
jtulach@816
  2935
            });
jtulach@816
  2936
        }
jtulach@816
  2937
jtulach@816
  2938
        return {
jtulach@816
  2939
            'shouldBindDescendants': bindingHandlerThatControlsDescendantBindings === undefined
jtulach@816
  2940
        };
jtulach@816
  2941
    };
jtulach@816
  2942
jtulach@816
  2943
    var storedBindingContextDomDataKey = ko.utils.domData.nextKey();
jtulach@816
  2944
    ko.storedBindingContextForNode = function (node, bindingContext) {
jtulach@816
  2945
        if (arguments.length == 2) {
jtulach@816
  2946
            ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
jtulach@816
  2947
            if (bindingContext._subscribable)
jtulach@816
  2948
                bindingContext._subscribable._addNode(node);
jtulach@816
  2949
        } else {
jtulach@816
  2950
            return ko.utils.domData.get(node, storedBindingContextDomDataKey);
jtulach@816
  2951
        }
jtulach@816
  2952
    }
jtulach@816
  2953
jtulach@816
  2954
    function getBindingContext(viewModelOrBindingContext) {
jtulach@816
  2955
        return viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
jtulach@816
  2956
            ? viewModelOrBindingContext
jtulach@816
  2957
            : new ko.bindingContext(viewModelOrBindingContext);
jtulach@816
  2958
    }
jtulach@816
  2959
jtulach@816
  2960
    ko.applyBindingAccessorsToNode = function (node, bindings, viewModelOrBindingContext) {
jtulach@816
  2961
        if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
jtulach@816
  2962
            ko.virtualElements.normaliseVirtualElementDomStructure(node);
jtulach@816
  2963
        return applyBindingsToNodeInternal(node, bindings, getBindingContext(viewModelOrBindingContext), true);
jtulach@816
  2964
    };
jtulach@816
  2965
jtulach@816
  2966
    ko.applyBindingsToNode = function (node, bindings, viewModelOrBindingContext) {
jtulach@816
  2967
        var context = getBindingContext(viewModelOrBindingContext);
jtulach@816
  2968
        return ko.applyBindingAccessorsToNode(node, makeBindingAccessors(bindings, context, node), context);
jtulach@816
  2969
    };
jtulach@816
  2970
jtulach@816
  2971
    ko.applyBindingsToDescendants = function(viewModelOrBindingContext, rootNode) {
jtulach@816
  2972
        if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
jtulach@816
  2973
            applyBindingsToDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode, true);
jtulach@816
  2974
    };
jtulach@816
  2975
jtulach@816
  2976
    ko.applyBindings = function (viewModelOrBindingContext, rootNode) {
jtulach@816
  2977
        // If jQuery is loaded after Knockout, we won't initially have access to it. So save it here.
jtulach@816
  2978
        if (!jQueryInstance && window['jQuery']) {
jtulach@816
  2979
            jQueryInstance = window['jQuery'];
jtulach@816
  2980
        }
jtulach@816
  2981
jtulach@816
  2982
        if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
jtulach@816
  2983
            throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
jtulach@816
  2984
        rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
jtulach@816
  2985
jtulach@816
  2986
        applyBindingsToNodeAndDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode, true);
jtulach@816
  2987
    };
jtulach@816
  2988
jtulach@816
  2989
    // Retrieving binding context from arbitrary nodes
jtulach@816
  2990
    ko.contextFor = function(node) {
jtulach@816
  2991
        // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
jtulach@816
  2992
        switch (node.nodeType) {
jtulach@816
  2993
            case 1:
jtulach@816
  2994
            case 8:
jtulach@816
  2995
                var context = ko.storedBindingContextForNode(node);
jtulach@816
  2996
                if (context) return context;
jtulach@816
  2997
                if (node.parentNode) return ko.contextFor(node.parentNode);
jtulach@816
  2998
                break;
jtulach@816
  2999
        }
jtulach@816
  3000
        return undefined;
jtulach@816
  3001
    };
jtulach@816
  3002
    ko.dataFor = function(node) {
jtulach@816
  3003
        var context = ko.contextFor(node);
jtulach@816
  3004
        return context ? context['$data'] : undefined;
jtulach@816
  3005
    };
jtulach@816
  3006
jtulach@816
  3007
    ko.exportSymbol('bindingHandlers', ko.bindingHandlers);
jtulach@816
  3008
    ko.exportSymbol('applyBindings', ko.applyBindings);
jtulach@816
  3009
    ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants);
jtulach@816
  3010
    ko.exportSymbol('applyBindingAccessorsToNode', ko.applyBindingAccessorsToNode);
jtulach@816
  3011
    ko.exportSymbol('applyBindingsToNode', ko.applyBindingsToNode);
jtulach@816
  3012
    ko.exportSymbol('contextFor', ko.contextFor);
jtulach@816
  3013
    ko.exportSymbol('dataFor', ko.dataFor);
jtulach@816
  3014
})();
jtulach@816
  3015
(function(undefined) {
jtulach@816
  3016
    var loadingSubscribablesCache = {}, // Tracks component loads that are currently in flight
jtulach@816
  3017
        loadedDefinitionsCache = {};    // Tracks component loads that have already completed
jtulach@816
  3018
jtulach@816
  3019
    ko.components = {
jtulach@816
  3020
        get: function(componentName, callback) {
jtulach@816
  3021
            var cachedDefinition = getObjectOwnProperty(loadedDefinitionsCache, componentName);
jtulach@816
  3022
            if (cachedDefinition) {
jtulach@816
  3023
                // It's already loaded and cached. Reuse the same definition object.
jtulach@816
  3024
                // Note that for API consistency, even cache hits complete asynchronously.
jtulach@816
  3025
                setTimeout(function() { callback(cachedDefinition) }, 0);
jtulach@816
  3026
            } else {
jtulach@816
  3027
                // Join the loading process that is already underway, or start a new one.
jtulach@816
  3028
                loadComponentAndNotify(componentName, callback);
jtulach@816
  3029
            }
jtulach@816
  3030
        },
jtulach@816
  3031
jtulach@816
  3032
        clearCachedDefinition: function(componentName) {
jtulach@816
  3033
            delete loadedDefinitionsCache[componentName];
jtulach@816
  3034
        },
jtulach@816
  3035
jtulach@816
  3036
        _getFirstResultFromLoaders: getFirstResultFromLoaders
jtulach@816
  3037
    };
jtulach@816
  3038
jtulach@816
  3039
    function getObjectOwnProperty(obj, propName) {
jtulach@816
  3040
        return obj.hasOwnProperty(propName) ? obj[propName] : undefined;
jtulach@816
  3041
    }
jtulach@816
  3042
jtulach@816
  3043
    function loadComponentAndNotify(componentName, callback) {
jtulach@816
  3044
        var subscribable = getObjectOwnProperty(loadingSubscribablesCache, componentName),
jtulach@816
  3045
            completedAsync;
jtulach@816
  3046
        if (!subscribable) {
jtulach@816
  3047
            // It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache.
jtulach@816
  3048
            subscribable = loadingSubscribablesCache[componentName] = new ko.subscribable();
jtulach@816
  3049
            beginLoadingComponent(componentName, function(definition) {
jtulach@816
  3050
                loadedDefinitionsCache[componentName] = definition;
jtulach@816
  3051
                delete loadingSubscribablesCache[componentName];
jtulach@816
  3052
jtulach@816
  3053
                // For API consistency, all loads complete asynchronously. However we want to avoid
jtulach@816
  3054
                // adding an extra setTimeout if it's unnecessary (i.e., the completion is already
jtulach@816
  3055
                // async) since setTimeout(..., 0) still takes about 16ms or more on most browsers.
jtulach@816
  3056
                if (completedAsync) {
jtulach@816
  3057
                    subscribable['notifySubscribers'](definition);
jtulach@816
  3058
                } else {
jtulach@816
  3059
                    setTimeout(function() {
jtulach@816
  3060
                        subscribable['notifySubscribers'](definition);
jtulach@816
  3061
                    }, 0);
jtulach@816
  3062
                }
jtulach@816
  3063
            });
jtulach@816
  3064
            completedAsync = true;
jtulach@816
  3065
        }
jtulach@816
  3066
        subscribable.subscribe(callback);
jtulach@816
  3067
    }
jtulach@816
  3068
jtulach@816
  3069
    function beginLoadingComponent(componentName, callback) {
jtulach@816
  3070
        getFirstResultFromLoaders('getConfig', [componentName], function(config) {
jtulach@816
  3071
            if (config) {
jtulach@816
  3072
                // We have a config, so now load its definition
jtulach@816
  3073
                getFirstResultFromLoaders('loadComponent', [componentName, config], function(definition) {
jtulach@816
  3074
                    callback(definition);
jtulach@816
  3075
                });
jtulach@816
  3076
            } else {
jtulach@816
  3077
                // The component has no config - it's unknown to all the loaders.
jtulach@816
  3078
                // Note that this is not an error (e.g., a module loading error) - that would abort the
jtulach@816
  3079
                // process and this callback would not run. For this callback to run, all loaders must
jtulach@816
  3080
                // have confirmed they don't know about this component.
jtulach@816
  3081
                callback(null);
jtulach@816
  3082
            }
jtulach@816
  3083
        });
jtulach@816
  3084
    }
jtulach@816
  3085
jtulach@816
  3086
    function getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders) {
jtulach@816
  3087
        // On the first call in the stack, start with the full set of loaders
jtulach@816
  3088
        if (!candidateLoaders) {
jtulach@816
  3089
            candidateLoaders = ko.components['loaders'].slice(0); // Use a copy, because we'll be mutating this array
jtulach@816
  3090
        }
jtulach@816
  3091
jtulach@816
  3092
        // Try the next candidate
jtulach@816
  3093
        var currentCandidateLoader = candidateLoaders.shift();
jtulach@816
  3094
        if (currentCandidateLoader) {
jtulach@816
  3095
            var methodInstance = currentCandidateLoader[methodName];
jtulach@816
  3096
            if (methodInstance) {
jtulach@816
  3097
                var wasAborted = false,
jtulach@816
  3098
                    synchronousReturnValue = methodInstance.apply(currentCandidateLoader, argsExceptCallback.concat(function(result) {
jtulach@816
  3099
                        if (wasAborted) {
jtulach@816
  3100
                            callback(null);
jtulach@816
  3101
                        } else if (result !== null) {
jtulach@816
  3102
                            // This candidate returned a value. Use it.
jtulach@816
  3103
                            callback(result);
jtulach@816
  3104
                        } else {
jtulach@816
  3105
                            // Try the next candidate
jtulach@816
  3106
                            getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
jtulach@816
  3107
                        }
jtulach@816
  3108
                    }));
jtulach@816
  3109
jtulach@816
  3110
                // Currently, loaders may not return anything synchronously. This leaves open the possibility
jtulach@816
  3111
                // that we'll extend the API to support synchronous return values in the future. It won't be
jtulach@816
  3112
                // a breaking change, because currently no loader is allowed to return anything except undefined.
jtulach@816
  3113
                if (synchronousReturnValue !== undefined) {
jtulach@816
  3114
                    wasAborted = true;
jtulach@816
  3115
jtulach@816
  3116
                    // Method to suppress exceptions will remain undocumented. This is only to keep
jtulach@816
  3117
                    // KO's specs running tidily, since we can observe the loading got aborted without
jtulach@816
  3118
                    // having exceptions cluttering up the console too.
jtulach@816
  3119
                    if (!currentCandidateLoader['suppressLoaderExceptions']) {
jtulach@816
  3120
                        throw new Error('Component loaders must supply values by invoking the callback, not by returning values synchronously.');
jtulach@816
  3121
                    }
jtulach@816
  3122
                }
jtulach@816
  3123
            } else {
jtulach@816
  3124
                // This candidate doesn't have the relevant handler. Synchronously move on to the next one.
jtulach@816
  3125
                getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
jtulach@816
  3126
            }
jtulach@816
  3127
        } else {
jtulach@816
  3128
            // No candidates returned a value
jtulach@816
  3129
            callback(null);
jtulach@816
  3130
        }
jtulach@816
  3131
    }
jtulach@816
  3132
jtulach@816
  3133
    // Reference the loaders via string name so it's possible for developers
jtulach@816
  3134
    // to replace the whole array by assigning to ko.components.loaders
jtulach@816
  3135
    ko.components['loaders'] = [];
jtulach@816
  3136
jtulach@816
  3137
    ko.exportSymbol('components', ko.components);
jtulach@816
  3138
    ko.exportSymbol('components.get', ko.components.get);
jtulach@816
  3139
    ko.exportSymbol('components.clearCachedDefinition', ko.components.clearCachedDefinition);
jtulach@816
  3140
})();
jtulach@816
  3141
(function(undefined) {
jtulach@816
  3142
jtulach@816
  3143
    // The default loader is responsible for two things:
jtulach@816
  3144
    // 1. Maintaining the default in-memory registry of component configuration objects
jtulach@816
  3145
    //    (i.e., the thing you're writing to when you call ko.components.register(someName, ...))
jtulach@816
  3146
    // 2. Answering requests for components by fetching configuration objects
jtulach@816
  3147
    //    from that default in-memory registry and resolving them into standard
jtulach@816
  3148
    //    component definition objects (of the form { createViewModel: ..., template: ... })
jtulach@816
  3149
    // Custom loaders may override either of these facilities, i.e.,
jtulach@816
  3150
    // 1. To supply configuration objects from some other source (e.g., conventions)
jtulach@816
  3151
    // 2. Or, to resolve configuration objects by loading viewmodels/templates via arbitrary logic.
jtulach@816
  3152
jtulach@816
  3153
    var defaultConfigRegistry = {};
jtulach@816
  3154
jtulach@816
  3155
    ko.components.register = function(componentName, config) {
jtulach@816
  3156
        if (!config) {
jtulach@816
  3157
            throw new Error('Invalid configuration for ' + componentName);
jtulach@816
  3158
        }
jtulach@816
  3159
jtulach@816
  3160
        if (ko.components.isRegistered(componentName)) {
jtulach@816
  3161
            throw new Error('Component ' + componentName + ' is already registered');
jtulach@816
  3162
        }
jtulach@816
  3163
jtulach@816
  3164
        defaultConfigRegistry[componentName] = config;
jtulach@816
  3165
    }
jtulach@816
  3166
jtulach@816
  3167
    ko.components.isRegistered = function(componentName) {
jtulach@816
  3168
        return componentName in defaultConfigRegistry;
jtulach@816
  3169
    }
jtulach@816
  3170
jtulach@816
  3171
    ko.components.unregister = function(componentName) {
jtulach@816
  3172
        delete defaultConfigRegistry[componentName];
jtulach@816
  3173
        ko.components.clearCachedDefinition(componentName);
jtulach@816
  3174
    }
jtulach@816
  3175
jtulach@816
  3176
    ko.components.defaultLoader = {
jtulach@816
  3177
        'getConfig': function(componentName, callback) {
jtulach@816
  3178
            var result = defaultConfigRegistry.hasOwnProperty(componentName)
jtulach@816
  3179
                ? defaultConfigRegistry[componentName]
jtulach@816
  3180
                : null;
jtulach@816
  3181
            callback(result);
jtulach@816
  3182
        },
jtulach@816
  3183
jtulach@816
  3184
        'loadComponent': function(componentName, config, callback) {
jtulach@816
  3185
            var errorCallback = makeErrorCallback(componentName);
jtulach@816
  3186
            possiblyGetConfigFromAmd(errorCallback, config, function(loadedConfig) {
jtulach@816
  3187
                resolveConfig(componentName, errorCallback, loadedConfig, callback);
jtulach@816
  3188
            });
jtulach@816
  3189
        },
jtulach@816
  3190
jtulach@816
  3191
        'loadTemplate': function(componentName, templateConfig, callback) {
jtulach@816
  3192
            resolveTemplate(makeErrorCallback(componentName), templateConfig, callback);
jtulach@816
  3193
        },
jtulach@816
  3194
jtulach@816
  3195
        'loadViewModel': function(componentName, viewModelConfig, callback) {
jtulach@816
  3196
            resolveViewModel(makeErrorCallback(componentName), viewModelConfig, callback);
jtulach@816
  3197
        }
jtulach@816
  3198
    };
jtulach@816
  3199
jtulach@816
  3200
    var createViewModelKey = 'createViewModel';
jtulach@816
  3201
jtulach@816
  3202
    // Takes a config object of the form { template: ..., viewModel: ... }, and asynchronously convert it
jtulach@816
  3203
    // into the standard component definition format:
jtulach@816
  3204
    //    { template: <ArrayOfDomNodes>, createViewModel: function(params, componentInfo) { ... } }.
jtulach@816
  3205
    // Since both template and viewModel may need to be resolved asynchronously, both tasks are performed
jtulach@816
  3206
    // in parallel, and the results joined when both are ready. We don't depend on any promises infrastructure,
jtulach@816
  3207
    // so this is implemented manually below.
jtulach@816
  3208
    function resolveConfig(componentName, errorCallback, config, callback) {
jtulach@816
  3209
        var result = {},
jtulach@816
  3210
            makeCallBackWhenZero = 2,
jtulach@816
  3211
            tryIssueCallback = function() {
jtulach@816
  3212
                if (--makeCallBackWhenZero === 0) {
jtulach@816
  3213
                    callback(result);
jtulach@816
  3214
                }
jtulach@816
  3215
            },
jtulach@816
  3216
            templateConfig = config['template'],
jtulach@816
  3217
            viewModelConfig = config['viewModel'];
jtulach@816
  3218
jtulach@816
  3219
        if (templateConfig) {
jtulach@816
  3220
            possiblyGetConfigFromAmd(errorCallback, templateConfig, function(loadedConfig) {
jtulach@816
  3221
                ko.components._getFirstResultFromLoaders('loadTemplate', [componentName, loadedConfig], function(resolvedTemplate) {
jtulach@816
  3222
                    result['template'] = resolvedTemplate;
jtulach@816
  3223
                    tryIssueCallback();
jtulach@816
  3224
                });
jtulach@816
  3225
            });
jtulach@816
  3226
        } else {
jtulach@816
  3227
            tryIssueCallback();
jtulach@816
  3228
        }
jtulach@816
  3229
jtulach@816
  3230
        if (viewModelConfig) {
jtulach@816
  3231
            possiblyGetConfigFromAmd(errorCallback, viewModelConfig, function(loadedConfig) {
jtulach@816
  3232
                ko.components._getFirstResultFromLoaders('loadViewModel', [componentName, loadedConfig], function(resolvedViewModel) {
jtulach@816
  3233
                    result[createViewModelKey] = resolvedViewModel;
jtulach@816
  3234
                    tryIssueCallback();
jtulach@816
  3235
                });
jtulach@816
  3236
            });
jtulach@816
  3237
        } else {
jtulach@816
  3238
            tryIssueCallback();
jtulach@816
  3239
        }
jtulach@816
  3240
    }
jtulach@816
  3241
jtulach@816
  3242
    function resolveTemplate(errorCallback, templateConfig, callback) {
jtulach@816
  3243
        if (typeof templateConfig === 'string') {
jtulach@816
  3244
            // Markup - parse it
jtulach@816
  3245
            callback(ko.utils.parseHtmlFragment(templateConfig));
jtulach@816
  3246
        } else if (templateConfig instanceof Array) {
jtulach@816
  3247
            // Assume already an array of DOM nodes - pass through unchanged
jtulach@816
  3248
            callback(templateConfig);
jtulach@816
  3249
        } else if (isDocumentFragment(templateConfig)) {
jtulach@816
  3250
            // Document fragment - use its child nodes
jtulach@816
  3251
            callback(ko.utils.makeArray(templateConfig.childNodes));
jtulach@816
  3252
        } else if (templateConfig['element']) {
jtulach@816
  3253
            var element = templateConfig['element'];
jtulach@816
  3254
            if (isDomElement(element)) {
jtulach@816
  3255
                // Element instance - copy its child nodes
jtulach@816
  3256
                callback(cloneNodesFromTemplateSourceElement(element));
jtulach@816
  3257
            } else if (typeof element === 'string') {
jtulach@816
  3258
                // Element ID - find it, then copy its child nodes
jtulach@816
  3259
                var elemInstance = document.getElementById(element);
jtulach@816
  3260
                if (elemInstance) {
jtulach@816
  3261
                    callback(cloneNodesFromTemplateSourceElement(elemInstance));
jtulach@816
  3262
                } else {
jtulach@816
  3263
                    errorCallback('Cannot find element with ID ' + element);
jtulach@816
  3264
                }
jtulach@816
  3265
            } else {
jtulach@816
  3266
                errorCallback('Unknown element type: ' + element);
jtulach@816
  3267
            }
jtulach@816
  3268
        } else {
jtulach@816
  3269
            errorCallback('Unknown template value: ' + templateConfig);
jtulach@816
  3270
        }
jtulach@816
  3271
    }
jtulach@816
  3272
jtulach@816
  3273
    function resolveViewModel(errorCallback, viewModelConfig, callback) {
jtulach@816
  3274
        if (typeof viewModelConfig === 'function') {
jtulach@816
  3275
            // Constructor - convert to standard factory function format
jtulach@816
  3276
            // By design, this does *not* supply componentInfo to the constructor, as the intent is that
jtulach@816
  3277
            // componentInfo contains non-viewmodel data (e.g., the component's element) that should only
jtulach@816
  3278
            // be used in factory functions, not viewmodel constructors.
jtulach@816
  3279
            callback(function (params /*, componentInfo */) {
jtulach@816
  3280
                return new viewModelConfig(params);
jtulach@816
  3281
            });
jtulach@816
  3282
        } else if (typeof viewModelConfig[createViewModelKey] === 'function') {
jtulach@816
  3283
            // Already a factory function - use it as-is
jtulach@816
  3284
            callback(viewModelConfig[createViewModelKey]);
jtulach@816
  3285
        } else if ('instance' in viewModelConfig) {
jtulach@816
  3286
            // Fixed object instance - promote to createViewModel format for API consistency
jtulach@816
  3287
            var fixedInstance = viewModelConfig['instance'];
jtulach@816
  3288
            callback(function (params, componentInfo) {
jtulach@816
  3289
                return fixedInstance;
jtulach@816
  3290
            });
jtulach@816
  3291
        } else if ('viewModel' in viewModelConfig) {
jtulach@816
  3292
            // Resolved AMD module whose value is of the form { viewModel: ... }
jtulach@816
  3293
            resolveViewModel(errorCallback, viewModelConfig['viewModel'], callback);
jtulach@816
  3294
        } else {
jtulach@816
  3295
            errorCallback('Unknown viewModel value: ' + viewModelConfig);
jtulach@816
  3296
        }
jtulach@816
  3297
    }
jtulach@816
  3298
jtulach@816
  3299
    function cloneNodesFromTemplateSourceElement(elemInstance) {
jtulach@816
  3300
        switch (ko.utils.tagNameLower(elemInstance)) {
jtulach@816
  3301
            case 'script':
jtulach@816
  3302
                return ko.utils.parseHtmlFragment(elemInstance.text);
jtulach@816
  3303
            case 'textarea':
jtulach@816
  3304
                return ko.utils.parseHtmlFragment(elemInstance.value);
jtulach@816
  3305
            case 'template':
jtulach@816
  3306
                // For browsers with proper <template> element support (i.e., where the .content property
jtulach@816
  3307
                // gives a document fragment), use that document fragment.
jtulach@816
  3308
                if (isDocumentFragment(elemInstance.content)) {
jtulach@816
  3309
                    return ko.utils.cloneNodes(elemInstance.content.childNodes);
jtulach@816
  3310
                }
jtulach@816
  3311
        }
jtulach@816
  3312
jtulach@816
  3313
        // Regular elements such as <div>, and <template> elements on old browsers that don't really
jtulach@816
  3314
        // understand <template> and just treat it as a regular container
jtulach@816
  3315
        return ko.utils.cloneNodes(elemInstance.childNodes);
jtulach@816
  3316
    }
jtulach@816
  3317
jtulach@816
  3318
    function isDomElement(obj) {
jtulach@816
  3319
        if (window['HTMLElement']) {
jtulach@816
  3320
            return obj instanceof HTMLElement;
jtulach@816
  3321
        } else {
jtulach@816
  3322
            return obj && obj.tagName && obj.nodeType === 1;
jtulach@816
  3323
        }
jtulach@816
  3324
    }
jtulach@816
  3325
jtulach@816
  3326
    function isDocumentFragment(obj) {
jtulach@816
  3327
        if (window['DocumentFragment']) {
jtulach@816
  3328
            return obj instanceof DocumentFragment;
jtulach@816
  3329
        } else {
jtulach@816
  3330
            return obj && obj.nodeType === 11;
jtulach@816
  3331
        }
jtulach@816
  3332
    }
jtulach@816
  3333
jtulach@816
  3334
    function possiblyGetConfigFromAmd(errorCallback, config, callback) {
jtulach@816
  3335
        if (typeof config['require'] === 'string') {
jtulach@816
  3336
            // The config is the value of an AMD module
jtulach@816
  3337
            if (require || window['require']) {
jtulach@816
  3338
                (require || window['require'])([config['require']], callback);
jtulach@816
  3339
            } else {
jtulach@816
  3340
                errorCallback('Uses require, but no AMD loader is present');
jtulach@816
  3341
            }
jtulach@816
  3342
        } else {
jtulach@816
  3343
            callback(config);
jtulach@816
  3344
        }
jtulach@816
  3345
    }
jtulach@816
  3346
jtulach@816
  3347
    function makeErrorCallback(componentName) {
jtulach@816
  3348
        return function (message) {
jtulach@816
  3349
            throw new Error('Component \'' + componentName + '\': ' + message);
jtulach@816
  3350
        };
jtulach@816
  3351
    }
jtulach@816
  3352
jtulach@816
  3353
    ko.exportSymbol('components.register', ko.components.register);
jtulach@816
  3354
    ko.exportSymbol('components.isRegistered', ko.components.isRegistered);
jtulach@816
  3355
    ko.exportSymbol('components.unregister', ko.components.unregister);
jtulach@816
  3356
jtulach@816
  3357
    // Expose the default loader so that developers can directly ask it for configuration
jtulach@816
  3358
    // or to resolve configuration
jtulach@816
  3359
    ko.exportSymbol('components.defaultLoader', ko.components.defaultLoader);
jtulach@816
  3360
jtulach@816
  3361
    // By default, the default loader is the only registered component loader
jtulach@816
  3362
    ko.components['loaders'].push(ko.components.defaultLoader);
jtulach@816
  3363
jtulach@816
  3364
    // Privately expose the underlying config registry for use in old-IE shim
jtulach@816
  3365
    ko.components._allRegisteredComponents = defaultConfigRegistry;
jtulach@816
  3366
})();
jtulach@816
  3367
(function (undefined) {
jtulach@816
  3368
    // Overridable API for determining which component name applies to a given node. By overriding this,
jtulach@816
  3369
    // you can for example map specific tagNames to components that are not preregistered.
jtulach@816
  3370
    ko.components['getComponentNameForNode'] = function(node) {
jtulach@816
  3371
        var tagNameLower = ko.utils.tagNameLower(node);
jtulach@816
  3372
        return ko.components.isRegistered(tagNameLower) && tagNameLower;
jtulach@816
  3373
    };
jtulach@816
  3374
jtulach@816
  3375
    ko.components.addBindingsForCustomElement = function(allBindings, node, bindingContext, valueAccessors) {
jtulach@816
  3376
        // Determine if it's really a custom element matching a component
jtulach@816
  3377
        if (node.nodeType === 1) {
jtulach@816
  3378
            var componentName = ko.components['getComponentNameForNode'](node);
jtulach@816
  3379
            if (componentName) {
jtulach@816
  3380
                // It does represent a component, so add a component binding for it
jtulach@816
  3381
                allBindings = allBindings || {};
jtulach@816
  3382
jtulach@816
  3383
                if (allBindings['component']) {
jtulach@816
  3384
                    // Avoid silently overwriting some other 'component' binding that may already be on the element
jtulach@816
  3385
                    throw new Error('Cannot use the "component" binding on a custom element matching a component');
jtulach@816
  3386
                }
jtulach@816
  3387
jtulach@816
  3388
                var componentBindingValue = { 'name': componentName, 'params': getComponentParamsFromCustomElement(node, bindingContext) };
jtulach@816
  3389
jtulach@816
  3390
                allBindings['component'] = valueAccessors
jtulach@816
  3391
                    ? function() { return componentBindingValue; }
jtulach@816
  3392
                    : componentBindingValue;
jtulach@816
  3393
            }
jtulach@816
  3394
        }
jtulach@816
  3395
jtulach@816
  3396
        return allBindings;
jtulach@816
  3397
    }
jtulach@816
  3398
jtulach@816
  3399
    var nativeBindingProviderInstance = new ko.bindingProvider();
jtulach@816
  3400
jtulach@816
  3401
    function getComponentParamsFromCustomElement(elem, bindingContext) {
jtulach@816
  3402
        var paramsAttribute = elem.getAttribute('params');
jtulach@816
  3403
jtulach@816
  3404
        if (paramsAttribute) {
jtulach@816
  3405
            var params = nativeBindingProviderInstance['parseBindingsString'](paramsAttribute, bindingContext, elem, { 'valueAccessors': true, 'bindingParams': true }),
jtulach@816
  3406
                rawParamComputedValues = ko.utils.objectMap(params, function(paramValue, paramName) {
jtulach@816
  3407
                    return ko.computed(paramValue, null, { disposeWhenNodeIsRemoved: elem });
jtulach@816
  3408
                }),
jtulach@816
  3409
                result = ko.utils.objectMap(rawParamComputedValues, function(paramValueComputed, paramName) {
jtulach@816
  3410
                    // Does the evaluation of the parameter value unwrap any observables?
jtulach@816
  3411
                    if (!paramValueComputed.isActive()) {
jtulach@816
  3412
                        // No it doesn't, so there's no need for any computed wrapper. Just pass through the supplied value directly.
jtulach@816
  3413
                        // Example: "someVal: firstName, age: 123" (whether or not firstName is an observable/computed)
jtulach@816
  3414
                        return paramValueComputed.peek();
jtulach@816
  3415
                    } else {
jtulach@816
  3416
                        // Yes it does. Supply a computed property that unwraps both the outer (binding expression)
jtulach@816
  3417
                        // level of observability, and any inner (resulting model value) level of observability.
jtulach@816
  3418
                        // This means the component doesn't have to worry about multiple unwrapping.
jtulach@816
  3419
                        return ko.computed(function() {
jtulach@816
  3420
                            return ko.utils.unwrapObservable(paramValueComputed());
jtulach@816
  3421
                        }, null, { disposeWhenNodeIsRemoved: elem });
jtulach@816
  3422
                    }
jtulach@816
  3423
                });
jtulach@816
  3424
jtulach@816
  3425
            // Give access to the raw computeds, as long as that wouldn't overwrite any custom param also called '$raw'
jtulach@816
  3426
            // This is in case the developer wants to react to outer (binding) observability separately from inner
jtulach@816
  3427
            // (model value) observability, or in case the model value observable has subobservables.
jtulach@816
  3428
            if (!result.hasOwnProperty('$raw')) {
jtulach@816
  3429
                result['$raw'] = rawParamComputedValues;
jtulach@816
  3430
            }
jtulach@816
  3431
jtulach@816
  3432
            return result;
jtulach@816
  3433
        } else {
jtulach@816
  3434
            // For consistency, absence of a "params" attribute is treated the same as the presence of
jtulach@816
  3435
            // any empty one. Otherwise component viewmodels need special code to check whether or not
jtulach@816
  3436
            // 'params' or 'params.$raw' is null/undefined before reading subproperties, which is annoying.
jtulach@816
  3437
            return { '$raw': {} };
jtulach@816
  3438
        }
jtulach@816
  3439
    }
jtulach@816
  3440
jtulach@816
  3441
    // --------------------------------------------------------------------------------
jtulach@816
  3442
    // Compatibility code for older (pre-HTML5) IE browsers
jtulach@816
  3443
jtulach@816
  3444
    if (ko.utils.ieVersion < 9) {
jtulach@816
  3445
        // Whenever you preregister a component, enable it as a custom element in the current document
jtulach@816
  3446
        ko.components['register'] = (function(originalFunction) {
jtulach@816
  3447
            return function(componentName) {
jtulach@816
  3448
                document.createElement(componentName); // Allows IE<9 to parse markup containing the custom element
jtulach@816
  3449
                return originalFunction.apply(this, arguments);
jtulach@816
  3450
            }
jtulach@816
  3451
        })(ko.components['register']);
jtulach@816
  3452
jtulach@816
  3453
        // Whenever you create a document fragment, enable all preregistered component names as custom elements
jtulach@816
  3454
        // This is needed to make innerShiv/jQuery HTML parsing correctly handle the custom elements
jtulach@816
  3455
        document.createDocumentFragment = (function(originalFunction) {
jtulach@816
  3456
            return function() {
jtulach@816
  3457
                var newDocFrag = originalFunction(),
jtulach@816
  3458
                    allComponents = ko.components._allRegisteredComponents;
jtulach@816
  3459
                for (var componentName in allComponents) {
jtulach@816
  3460
                    if (allComponents.hasOwnProperty(componentName)) {
jtulach@816
  3461
                        newDocFrag.createElement(componentName);
jtulach@816
  3462
                    }
jtulach@816
  3463
                }
jtulach@816
  3464
                return newDocFrag;
jtulach@816
  3465
            };
jtulach@816
  3466
        })(document.createDocumentFragment);
jtulach@816
  3467
    }
jtulach@816
  3468
})();(function(undefined) {
jtulach@816
  3469
jtulach@816
  3470
    var componentLoadingOperationUniqueId = 0;
jtulach@816
  3471
jtulach@816
  3472
    ko.bindingHandlers['component'] = {
jtulach@816
  3473
        'init': function(element, valueAccessor, ignored1, ignored2, bindingContext) {
jtulach@816
  3474
            var currentViewModel,
jtulach@816
  3475
                currentLoadingOperationId,
jtulach@816
  3476
                disposeAssociatedComponentViewModel = function () {
jtulach@816
  3477
                    var currentViewModelDispose = currentViewModel && currentViewModel['dispose'];
jtulach@816
  3478
                    if (typeof currentViewModelDispose === 'function') {
jtulach@816
  3479
                        currentViewModelDispose.call(currentViewModel);
jtulach@816
  3480
                    }
jtulach@816
  3481
jtulach@816
  3482
                    // Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
jtulach@816
  3483
                    currentLoadingOperationId = null;
jtulach@816
  3484
                };
jtulach@816
  3485
jtulach@816
  3486
            ko.utils.domNodeDisposal.addDisposeCallback(element, disposeAssociatedComponentViewModel);
jtulach@816
  3487
jtulach@816
  3488
            ko.computed(function () {
jtulach@816
  3489
                var value = ko.utils.unwrapObservable(valueAccessor()),
jtulach@816
  3490
                    componentName, componentParams;
jtulach@816
  3491
jtulach@816
  3492
                if (typeof value === 'string') {
jtulach@816
  3493
                    componentName = value;
jtulach@816
  3494
                } else {
jtulach@816
  3495
                    componentName = ko.utils.unwrapObservable(value['name']);
jtulach@816
  3496
                    componentParams = ko.utils.unwrapObservable(value['params']);
jtulach@816
  3497
                }
jtulach@816
  3498
jtulach@816
  3499
                if (!componentName) {
jtulach@816
  3500
                    throw new Error('No component name specified');
jtulach@816
  3501
                }
jtulach@816
  3502
jtulach@816
  3503
                var loadingOperationId = currentLoadingOperationId = ++componentLoadingOperationUniqueId;
jtulach@816
  3504
                ko.components.get(componentName, function(componentDefinition) {
jtulach@816
  3505
                    // If this is not the current load operation for this element, ignore it.
jtulach@816
  3506
                    if (currentLoadingOperationId !== loadingOperationId) {
jtulach@816
  3507
                        return;
jtulach@816
  3508
                    }
jtulach@816
  3509
jtulach@816
  3510
                    // Clean up previous state
jtulach@816
  3511
                    disposeAssociatedComponentViewModel();
jtulach@816
  3512
jtulach@816
  3513
                    // Instantiate and bind new component. Implicitly this cleans any old DOM nodes.
jtulach@816
  3514
                    if (!componentDefinition) {
jtulach@816
  3515
                        throw new Error('Unknown component \'' + componentName + '\'');
jtulach@816
  3516
                    }
jtulach@816
  3517
                    cloneTemplateIntoElement(componentName, componentDefinition, element);
jtulach@816
  3518
                    var componentViewModel = createViewModel(componentDefinition, element, componentParams),
jtulach@816
  3519
                        childBindingContext = bindingContext['createChildContext'](componentViewModel);
jtulach@816
  3520
                    currentViewModel = componentViewModel;
jtulach@816
  3521
                    ko.applyBindingsToDescendants(childBindingContext, element);
jtulach@816
  3522
                });
jtulach@816
  3523
            }, null, { disposeWhenNodeIsRemoved: element });
jtulach@816
  3524
jtulach@816
  3525
            return { 'controlsDescendantBindings': true };
jtulach@816
  3526
        }
jtulach@816
  3527
    };
jtulach@816
  3528
jtulach@816
  3529
    ko.virtualElements.allowedBindings['component'] = true;
jtulach@816
  3530
jtulach@816
  3531
    function cloneTemplateIntoElement(componentName, componentDefinition, element) {
jtulach@816
  3532
        var template = componentDefinition['template'];
jtulach@816
  3533
        if (!template) {
jtulach@816
  3534
            throw new Error('Component \'' + componentName + '\' has no template');
jtulach@816
  3535
        }
jtulach@816
  3536
jtulach@816
  3537
        var clonedNodesArray = ko.utils.cloneNodes(template);
jtulach@816
  3538
        ko.virtualElements.setDomNodeChildren(element, clonedNodesArray);
jtulach@816
  3539
    }
jtulach@816
  3540
jtulach@816
  3541
    function createViewModel(componentDefinition, element, componentParams) {
jtulach@816
  3542
        var componentViewModelFactory = componentDefinition['createViewModel'];
jtulach@816
  3543
        return componentViewModelFactory
jtulach@816
  3544
            ? componentViewModelFactory.call(componentDefinition, componentParams, { element: element })
jtulach@816
  3545
            : componentParams; // Template-only component
jtulach@816
  3546
    }
jtulach@816
  3547
jtulach@816
  3548
})();
jtulach@816
  3549
var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
jtulach@816
  3550
ko.bindingHandlers['attr'] = {
jtulach@816
  3551
    'update': function(element, valueAccessor, allBindings) {
jtulach@816
  3552
        var value = ko.utils.unwrapObservable(valueAccessor()) || {};
jtulach@816
  3553
        ko.utils.objectForEach(value, function(attrName, attrValue) {
jtulach@816
  3554
            attrValue = ko.utils.unwrapObservable(attrValue);
jtulach@816
  3555
jtulach@816
  3556
            // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
jtulach@816
  3557
            // when someProp is a "no value"-like value (strictly null, false, or undefined)
jtulach@816
  3558
            // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
jtulach@816
  3559
            var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
jtulach@816
  3560
            if (toRemove)
jtulach@816
  3561
                element.removeAttribute(attrName);
jtulach@816
  3562
jtulach@816
  3563
            // In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
jtulach@816
  3564
            // HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
jtulach@816
  3565
            // but instead of figuring out the mode, we'll just set the attribute through the Javascript
jtulach@816
  3566
            // property for IE <= 8.
jtulach@816
  3567
            if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
jtulach@816
  3568
                attrName = attrHtmlToJavascriptMap[attrName];
jtulach@816
  3569
                if (toRemove)
jtulach@816
  3570
                    element.removeAttribute(attrName);
jtulach@816
  3571
                else
jtulach@816
  3572
                    element[attrName] = attrValue;
jtulach@816
  3573
            } else if (!toRemove) {
jtulach@816
  3574
                element.setAttribute(attrName, attrValue.toString());
jtulach@816
  3575
            }
jtulach@816
  3576
jtulach@816
  3577
            // Treat "name" specially - although you can think of it as an attribute, it also needs
jtulach@816
  3578
            // special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
jtulach@816
  3579
            // Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
jtulach@816
  3580
            // entirely, and there's no strong reason to allow for such casing in HTML.
jtulach@816
  3581
            if (attrName === "name") {
jtulach@816
  3582
                ko.utils.setElementName(element, toRemove ? "" : attrValue.toString());
jtulach@816
  3583
            }
jtulach@816
  3584
        });
jtulach@816
  3585
    }
jtulach@816
  3586
};
jtulach@816
  3587
(function() {
jtulach@816
  3588
jtulach@816
  3589
ko.bindingHandlers['checked'] = {
jtulach@816
  3590
    'after': ['value', 'attr'],
jtulach@816
  3591
    'init': function (element, valueAccessor, allBindings) {
jtulach@816
  3592
        var checkedValue = ko.pureComputed(function() {
jtulach@816
  3593
            // Treat "value" like "checkedValue" when it is included with "checked" binding
jtulach@816
  3594
            if (allBindings['has']('checkedValue')) {
jtulach@816
  3595
                return ko.utils.unwrapObservable(allBindings.get('checkedValue'));
jtulach@816
  3596
            } else if (allBindings['has']('value')) {
jtulach@816
  3597
                return ko.utils.unwrapObservable(allBindings.get('value'));
jtulach@816
  3598
            }
jtulach@816
  3599
jtulach@816
  3600
            return element.value;
jtulach@816
  3601
        });
jtulach@816
  3602
jtulach@816
  3603
        function updateModel() {
jtulach@816
  3604
            // This updates the model value from the view value.
jtulach@816
  3605
            // It runs in response to DOM events (click) and changes in checkedValue.
jtulach@816
  3606
            var isChecked = element.checked,
jtulach@816
  3607
                elemValue = useCheckedValue ? checkedValue() : isChecked;
jtulach@816
  3608
jtulach@816
  3609
            // When we're first setting up this computed, don't change any model state.
jtulach@816
  3610
            if (ko.computedContext.isInitial()) {
jtulach@816
  3611
                return;
jtulach@816
  3612
            }
jtulach@816
  3613
jtulach@816
  3614
            // We can ignore unchecked radio buttons, because some other radio
jtulach@816
  3615
            // button will be getting checked, and that one can take care of updating state.
jtulach@816
  3616
            if (isRadio && !isChecked) {
jtulach@816
  3617
                return;
jtulach@816
  3618
            }
jtulach@816
  3619
jtulach@816
  3620
            var modelValue = ko.dependencyDetection.ignore(valueAccessor);
jtulach@816
  3621
            if (isValueArray) {
jtulach@816
  3622
                if (oldElemValue !== elemValue) {
jtulach@816
  3623
                    // When we're responding to the checkedValue changing, and the element is
jtulach@816
  3624
                    // currently checked, replace the old elem value with the new elem value
jtulach@816
  3625
                    // in the model array.
jtulach@816
  3626
                    if (isChecked) {
jtulach@816
  3627
                        ko.utils.addOrRemoveItem(modelValue, elemValue, true);
jtulach@816
  3628
                        ko.utils.addOrRemoveItem(modelValue, oldElemValue, false);
jtulach@816
  3629
                    }
jtulach@816
  3630
jtulach@816
  3631
                    oldElemValue = elemValue;
jtulach@816
  3632
                } else {
jtulach@816
  3633
                    // When we're responding to the user having checked/unchecked a checkbox,
jtulach@816
  3634
                    // add/remove the element value to the model array.
jtulach@816
  3635
                    ko.utils.addOrRemoveItem(modelValue, elemValue, isChecked);
jtulach@816
  3636
                }
jtulach@816
  3637
            } else {
jtulach@816
  3638
                ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'checked', elemValue, true);
jtulach@816
  3639
            }
jtulach@816
  3640
        };
jtulach@816
  3641
jtulach@816
  3642
        function updateView() {
jtulach@816
  3643
            // This updates the view value from the model value.
jtulach@816
  3644
            // It runs in response to changes in the bound (checked) value.
jtulach@816
  3645
            var modelValue = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  3646
jtulach@816
  3647
            if (isValueArray) {
jtulach@816
  3648
                // When a checkbox is bound to an array, being checked represents its value being present in that array
jtulach@816
  3649
                element.checked = ko.utils.arrayIndexOf(modelValue, checkedValue()) >= 0;
jtulach@816
  3650
            } else if (isCheckbox) {
jtulach@816
  3651
                // When a checkbox is bound to any other value (not an array), being checked represents the value being trueish
jtulach@816
  3652
                element.checked = modelValue;
jtulach@816
  3653
            } else {
jtulach@816
  3654
                // For radio buttons, being checked means that the radio button's value corresponds to the model value
jtulach@816
  3655
                element.checked = (checkedValue() === modelValue);
jtulach@816
  3656
            }
jtulach@816
  3657
        };
jtulach@816
  3658
jtulach@816
  3659
        var isCheckbox = element.type == "checkbox",
jtulach@816
  3660
            isRadio = element.type == "radio";
jtulach@816
  3661
jtulach@816
  3662
        // Only bind to check boxes and radio buttons
jtulach@816
  3663
        if (!isCheckbox && !isRadio) {
jtulach@816
  3664
            return;
jtulach@816
  3665
        }
jtulach@816
  3666
jtulach@816
  3667
        var isValueArray = isCheckbox && (ko.utils.unwrapObservable(valueAccessor()) instanceof Array),
jtulach@816
  3668
            oldElemValue = isValueArray ? checkedValue() : undefined,
jtulach@816
  3669
            useCheckedValue = isRadio || isValueArray;
jtulach@816
  3670
jtulach@816
  3671
        // IE 6 won't allow radio buttons to be selected unless they have a name
jtulach@816
  3672
        if (isRadio && !element.name)
jtulach@816
  3673
            ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
jtulach@816
  3674
jtulach@816
  3675
        // Set up two computeds to update the binding:
jtulach@816
  3676
jtulach@816
  3677
        // The first responds to changes in the checkedValue value and to element clicks
jtulach@816
  3678
        ko.computed(updateModel, null, { disposeWhenNodeIsRemoved: element });
jtulach@816
  3679
        ko.utils.registerEventHandler(element, "click", updateModel);
jtulach@816
  3680
jtulach@816
  3681
        // The second responds to changes in the model value (the one associated with the checked binding)
jtulach@816
  3682
        ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
jtulach@816
  3683
    }
jtulach@816
  3684
};
jtulach@816
  3685
ko.expressionRewriting.twoWayBindings['checked'] = true;
jtulach@816
  3686
jtulach@816
  3687
ko.bindingHandlers['checkedValue'] = {
jtulach@816
  3688
    'update': function (element, valueAccessor) {
jtulach@816
  3689
        element.value = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  3690
    }
jtulach@816
  3691
};
jtulach@816
  3692
jtulach@816
  3693
})();var classesWrittenByBindingKey = '__ko__cssValue';
jtulach@816
  3694
ko.bindingHandlers['css'] = {
jtulach@816
  3695
    'update': function (element, valueAccessor) {
jtulach@816
  3696
        var value = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  3697
        if (typeof value == "object") {
jtulach@816
  3698
            ko.utils.objectForEach(value, function(className, shouldHaveClass) {
jtulach@816
  3699
                shouldHaveClass = ko.utils.unwrapObservable(shouldHaveClass);
jtulach@816
  3700
                ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
jtulach@816
  3701
            });
jtulach@816
  3702
        } else {
jtulach@816
  3703
            value = String(value || ''); // Make sure we don't try to store or set a non-string value
jtulach@816
  3704
            ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
jtulach@816
  3705
            element[classesWrittenByBindingKey] = value;
jtulach@816
  3706
            ko.utils.toggleDomNodeCssClass(element, value, true);
jtulach@816
  3707
        }
jtulach@816
  3708
    }
jtulach@816
  3709
};
jtulach@816
  3710
ko.bindingHandlers['enable'] = {
jtulach@816
  3711
    'update': function (element, valueAccessor) {
jtulach@816
  3712
        var value = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  3713
        if (value && element.disabled)
jtulach@816
  3714
            element.removeAttribute("disabled");
jtulach@816
  3715
        else if ((!value) && (!element.disabled))
jtulach@816
  3716
            element.disabled = true;
jtulach@816
  3717
    }
jtulach@816
  3718
};
jtulach@816
  3719
jtulach@816
  3720
ko.bindingHandlers['disable'] = {
jtulach@816
  3721
    'update': function (element, valueAccessor) {
jtulach@816
  3722
        ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
jtulach@816
  3723
    }
jtulach@816
  3724
};
jtulach@816
  3725
// For certain common events (currently just 'click'), allow a simplified data-binding syntax
jtulach@816
  3726
// e.g. click:handler instead of the usual full-length event:{click:handler}
jtulach@816
  3727
function makeEventHandlerShortcut(eventName) {
jtulach@816
  3728
    ko.bindingHandlers[eventName] = {
jtulach@816
  3729
        'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
jtulach@816
  3730
            var newValueAccessor = function () {
jtulach@816
  3731
                var result = {};
jtulach@816
  3732
                result[eventName] = valueAccessor();
jtulach@816
  3733
                return result;
jtulach@816
  3734
            };
jtulach@816
  3735
            return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindings, viewModel, bindingContext);
jtulach@816
  3736
        }
jtulach@816
  3737
    }
jtulach@816
  3738
}
jtulach@816
  3739
jtulach@816
  3740
ko.bindingHandlers['event'] = {
jtulach@816
  3741
    'init' : function (element, valueAccessor, allBindings, viewModel, bindingContext) {
jtulach@816
  3742
        var eventsToHandle = valueAccessor() || {};
jtulach@816
  3743
        ko.utils.objectForEach(eventsToHandle, function(eventName) {
jtulach@816
  3744
            if (typeof eventName == "string") {
jtulach@816
  3745
                ko.utils.registerEventHandler(element, eventName, function (event) {
jtulach@816
  3746
                    var handlerReturnValue;
jtulach@816
  3747
                    var handlerFunction = valueAccessor()[eventName];
jtulach@816
  3748
                    if (!handlerFunction)
jtulach@816
  3749
                        return;
jtulach@816
  3750
jtulach@816
  3751
                    try {
jtulach@816
  3752
                        // Take all the event args, and prefix with the viewmodel
jtulach@816
  3753
                        var argsForHandler = ko.utils.makeArray(arguments);
jtulach@816
  3754
                        viewModel = bindingContext['$data'];
jtulach@816
  3755
                        argsForHandler.unshift(viewModel);
jtulach@816
  3756
                        handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);
jtulach@816
  3757
                    } finally {
jtulach@816
  3758
                        if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
jtulach@816
  3759
                            if (event.preventDefault)
jtulach@816
  3760
                                event.preventDefault();
jtulach@816
  3761
                            else
jtulach@816
  3762
                                event.returnValue = false;
jtulach@816
  3763
                        }
jtulach@816
  3764
                    }
jtulach@816
  3765
jtulach@816
  3766
                    var bubble = allBindings.get(eventName + 'Bubble') !== false;
jtulach@816
  3767
                    if (!bubble) {
jtulach@816
  3768
                        event.cancelBubble = true;
jtulach@816
  3769
                        if (event.stopPropagation)
jtulach@816
  3770
                            event.stopPropagation();
jtulach@816
  3771
                    }
jtulach@816
  3772
                });
jtulach@816
  3773
            }
jtulach@816
  3774
        });
jtulach@816
  3775
    }
jtulach@816
  3776
};
jtulach@816
  3777
// "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
jtulach@816
  3778
// "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
jtulach@816
  3779
ko.bindingHandlers['foreach'] = {
jtulach@816
  3780
    makeTemplateValueAccessor: function(valueAccessor) {
jtulach@816
  3781
        return function() {
jtulach@816
  3782
            var modelValue = valueAccessor(),
jtulach@816
  3783
                unwrappedValue = ko.utils.peekObservable(modelValue);    // Unwrap without setting a dependency here
jtulach@816
  3784
jtulach@816
  3785
            // If unwrappedValue is the array, pass in the wrapped value on its own
jtulach@816
  3786
            // The value will be unwrapped and tracked within the template binding
jtulach@816
  3787
            // (See https://github.com/SteveSanderson/knockout/issues/523)
jtulach@816
  3788
            if ((!unwrappedValue) || typeof unwrappedValue.length == "number")
jtulach@816
  3789
                return { 'foreach': modelValue, 'templateEngine': ko.nativeTemplateEngine.instance };
jtulach@816
  3790
jtulach@816
  3791
            // If unwrappedValue.data is the array, preserve all relevant options and unwrap again value so we get updates
jtulach@816
  3792
            ko.utils.unwrapObservable(modelValue);
jtulach@816
  3793
            return {
jtulach@816
  3794
                'foreach': unwrappedValue['data'],
jtulach@816
  3795
                'as': unwrappedValue['as'],
jtulach@816
  3796
                'includeDestroyed': unwrappedValue['includeDestroyed'],
jtulach@816
  3797
                'afterAdd': unwrappedValue['afterAdd'],
jtulach@816
  3798
                'beforeRemove': unwrappedValue['beforeRemove'],
jtulach@816
  3799
                'afterRender': unwrappedValue['afterRender'],
jtulach@816
  3800
                'beforeMove': unwrappedValue['beforeMove'],
jtulach@816
  3801
                'afterMove': unwrappedValue['afterMove'],
jtulach@816
  3802
                'templateEngine': ko.nativeTemplateEngine.instance
jtulach@816
  3803
            };
jtulach@816
  3804
        };
jtulach@816
  3805
    },
jtulach@816
  3806
    'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
jtulach@816
  3807
        return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
jtulach@816
  3808
    },
jtulach@816
  3809
    'update': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
jtulach@816
  3810
        return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindings, viewModel, bindingContext);
jtulach@816
  3811
    }
jtulach@816
  3812
};
jtulach@816
  3813
ko.expressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
jtulach@816
  3814
ko.virtualElements.allowedBindings['foreach'] = true;
jtulach@816
  3815
var hasfocusUpdatingProperty = '__ko_hasfocusUpdating';
jtulach@816
  3816
var hasfocusLastValue = '__ko_hasfocusLastValue';
jtulach@816
  3817
ko.bindingHandlers['hasfocus'] = {
jtulach@816
  3818
    'init': function(element, valueAccessor, allBindings) {
jtulach@816
  3819
        var handleElementFocusChange = function(isFocused) {
jtulach@816
  3820
            // Where possible, ignore which event was raised and determine focus state using activeElement,
jtulach@816
  3821
            // as this avoids phantom focus/blur events raised when changing tabs in modern browsers.
jtulach@816
  3822
            // However, not all KO-targeted browsers (Firefox 2) support activeElement. For those browsers,
jtulach@816
  3823
            // prevent a loss of focus when changing tabs/windows by setting a flag that prevents hasfocus
jtulach@816
  3824
            // from calling 'blur()' on the element when it loses focus.
jtulach@816
  3825
            // Discussion at https://github.com/SteveSanderson/knockout/pull/352
jtulach@816
  3826
            element[hasfocusUpdatingProperty] = true;
jtulach@816
  3827
            var ownerDoc = element.ownerDocument;
jtulach@816
  3828
            if ("activeElement" in ownerDoc) {
jtulach@816
  3829
                var active;
jtulach@816
  3830
                try {
jtulach@816
  3831
                    active = ownerDoc.activeElement;
jtulach@816
  3832
                } catch(e) {
jtulach@816
  3833
                    // IE9 throws if you access activeElement during page load (see issue #703)
jtulach@816
  3834
                    active = ownerDoc.body;
jtulach@816
  3835
                }
jtulach@816
  3836
                isFocused = (active === element);
jtulach@816
  3837
            }
jtulach@816
  3838
            var modelValue = valueAccessor();
jtulach@816
  3839
            ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'hasfocus', isFocused, true);
jtulach@816
  3840
jtulach@816
  3841
            //cache the latest value, so we can avoid unnecessarily calling focus/blur in the update function
jtulach@816
  3842
            element[hasfocusLastValue] = isFocused;
jtulach@816
  3843
            element[hasfocusUpdatingProperty] = false;
jtulach@816
  3844
        };
jtulach@816
  3845
        var handleElementFocusIn = handleElementFocusChange.bind(null, true);
jtulach@816
  3846
        var handleElementFocusOut = handleElementFocusChange.bind(null, false);
jtulach@816
  3847
jtulach@816
  3848
        ko.utils.registerEventHandler(element, "focus", handleElementFocusIn);
jtulach@816
  3849
        ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE
jtulach@816
  3850
        ko.utils.registerEventHandler(element, "blur",  handleElementFocusOut);
jtulach@816
  3851
        ko.utils.registerEventHandler(element, "focusout",  handleElementFocusOut); // For IE
jtulach@816
  3852
    },
jtulach@816
  3853
    'update': function(element, valueAccessor) {
jtulach@816
  3854
        var value = !!ko.utils.unwrapObservable(valueAccessor()); //force boolean to compare with last value
jtulach@816
  3855
        if (!element[hasfocusUpdatingProperty] && element[hasfocusLastValue] !== value) {
jtulach@816
  3856
            value ? element.focus() : element.blur();
jtulach@816
  3857
            ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
jtulach@816
  3858
        }
jtulach@816
  3859
    }
jtulach@816
  3860
};
jtulach@816
  3861
ko.expressionRewriting.twoWayBindings['hasfocus'] = true;
jtulach@816
  3862
jtulach@816
  3863
ko.bindingHandlers['hasFocus'] = ko.bindingHandlers['hasfocus']; // Make "hasFocus" an alias
jtulach@816
  3864
ko.expressionRewriting.twoWayBindings['hasFocus'] = true;
jtulach@816
  3865
ko.bindingHandlers['html'] = {
jtulach@816
  3866
    'init': function() {
jtulach@816
  3867
        // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
jtulach@816
  3868
        return { 'controlsDescendantBindings': true };
jtulach@816
  3869
    },
jtulach@816
  3870
    'update': function (element, valueAccessor) {
jtulach@816
  3871
        // setHtml will unwrap the value if needed
jtulach@816
  3872
        ko.utils.setHtml(element, valueAccessor());
jtulach@816
  3873
    }
jtulach@816
  3874
};
jtulach@816
  3875
// Makes a binding like with or if
jtulach@816
  3876
function makeWithIfBinding(bindingKey, isWith, isNot, makeContextCallback) {
jtulach@816
  3877
    ko.bindingHandlers[bindingKey] = {
jtulach@816
  3878
        'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
jtulach@816
  3879
            var didDisplayOnLastUpdate,
jtulach@816
  3880
                savedNodes;
jtulach@816
  3881
            ko.computed(function() {
jtulach@816
  3882
                var dataValue = ko.utils.unwrapObservable(valueAccessor()),
jtulach@816
  3883
                    shouldDisplay = !isNot !== !dataValue, // equivalent to isNot ? !dataValue : !!dataValue
jtulach@816
  3884
                    isFirstRender = !savedNodes,
jtulach@816
  3885
                    needsRefresh = isFirstRender || isWith || (shouldDisplay !== didDisplayOnLastUpdate);
jtulach@816
  3886
jtulach@816
  3887
                if (needsRefresh) {
jtulach@816
  3888
                    // Save a copy of the inner nodes on the initial update, but only if we have dependencies.
jtulach@816
  3889
                    if (isFirstRender && ko.computedContext.getDependenciesCount()) {
jtulach@816
  3890
                        savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */);
jtulach@816
  3891
                    }
jtulach@816
  3892
jtulach@816
  3893
                    if (shouldDisplay) {
jtulach@816
  3894
                        if (!isFirstRender) {
jtulach@816
  3895
                            ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(savedNodes));
jtulach@816
  3896
                        }
jtulach@816
  3897
                        ko.applyBindingsToDescendants(makeContextCallback ? makeContextCallback(bindingContext, dataValue) : bindingContext, element);
jtulach@816
  3898
                    } else {
jtulach@816
  3899
                        ko.virtualElements.emptyNode(element);
jtulach@816
  3900
                    }
jtulach@816
  3901
jtulach@816
  3902
                    didDisplayOnLastUpdate = shouldDisplay;
jtulach@816
  3903
                }
jtulach@816
  3904
            }, null, { disposeWhenNodeIsRemoved: element });
jtulach@816
  3905
            return { 'controlsDescendantBindings': true };
jtulach@816
  3906
        }
jtulach@816
  3907
    };
jtulach@816
  3908
    ko.expressionRewriting.bindingRewriteValidators[bindingKey] = false; // Can't rewrite control flow bindings
jtulach@816
  3909
    ko.virtualElements.allowedBindings[bindingKey] = true;
jtulach@816
  3910
}
jtulach@816
  3911
jtulach@816
  3912
// Construct the actual binding handlers
jtulach@816
  3913
makeWithIfBinding('if');
jtulach@816
  3914
makeWithIfBinding('ifnot', false /* isWith */, true /* isNot */);
jtulach@816
  3915
makeWithIfBinding('with', true /* isWith */, false /* isNot */,
jtulach@816
  3916
    function(bindingContext, dataValue) {
jtulach@816
  3917
        return bindingContext['createChildContext'](dataValue);
jtulach@816
  3918
    }
jtulach@816
  3919
);
jtulach@816
  3920
var captionPlaceholder = {};
jtulach@816
  3921
ko.bindingHandlers['options'] = {
jtulach@816
  3922
    'init': function(element) {
jtulach@816
  3923
        if (ko.utils.tagNameLower(element) !== "select")
jtulach@816
  3924
            throw new Error("options binding applies only to SELECT elements");
jtulach@816
  3925
jtulach@816
  3926
        // Remove all existing <option>s.
jtulach@816
  3927
        while (element.length > 0) {
jtulach@816
  3928
            element.remove(0);
jtulach@816
  3929
        }
jtulach@816
  3930
jtulach@816
  3931
        // Ensures that the binding processor doesn't try to bind the options
jtulach@816
  3932
        return { 'controlsDescendantBindings': true };
jtulach@816
  3933
    },
jtulach@816
  3934
    'update': function (element, valueAccessor, allBindings) {
jtulach@816
  3935
        function selectedOptions() {
jtulach@816
  3936
            return ko.utils.arrayFilter(element.options, function (node) { return node.selected; });
jtulach@816
  3937
        }
jtulach@816
  3938
jtulach@816
  3939
        var selectWasPreviouslyEmpty = element.length == 0;
jtulach@816
  3940
        var previousScrollTop = (!selectWasPreviouslyEmpty && element.multiple) ? element.scrollTop : null;
jtulach@816
  3941
        var unwrappedArray = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  3942
        var includeDestroyed = allBindings.get('optionsIncludeDestroyed');
jtulach@816
  3943
        var arrayToDomNodeChildrenOptions = {};
jtulach@816
  3944
        var captionValue;
jtulach@816
  3945
        var filteredArray;
jtulach@816
  3946
        var previousSelectedValues;
jtulach@816
  3947
jtulach@816
  3948
        if (element.multiple) {
jtulach@816
  3949
            previousSelectedValues = ko.utils.arrayMap(selectedOptions(), ko.selectExtensions.readValue);
jtulach@816
  3950
        } else {
jtulach@816
  3951
            previousSelectedValues = element.selectedIndex >= 0 ? [ ko.selectExtensions.readValue(element.options[element.selectedIndex]) ] : [];
jtulach@816
  3952
        }
jtulach@816
  3953
jtulach@816
  3954
        if (unwrappedArray) {
jtulach@816
  3955
            if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
jtulach@816
  3956
                unwrappedArray = [unwrappedArray];
jtulach@816
  3957
jtulach@816
  3958
            // Filter out any entries marked as destroyed
jtulach@816
  3959
            filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
jtulach@816
  3960
                return includeDestroyed || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
jtulach@816
  3961
            });
jtulach@816
  3962
jtulach@816
  3963
            // If caption is included, add it to the array
jtulach@816
  3964
            if (allBindings['has']('optionsCaption')) {
jtulach@816
  3965
                captionValue = ko.utils.unwrapObservable(allBindings.get('optionsCaption'));
jtulach@816
  3966
                // If caption value is null or undefined, don't show a caption
jtulach@816
  3967
                if (captionValue !== null && captionValue !== undefined) {
jtulach@816
  3968
                    filteredArray.unshift(captionPlaceholder);
jtulach@816
  3969
                }
jtulach@816
  3970
            }
jtulach@816
  3971
        } else {
jtulach@816
  3972
            // If a falsy value is provided (e.g. null), we'll simply empty the select element
jtulach@816
  3973
        }
jtulach@816
  3974
jtulach@816
  3975
        function applyToObject(object, predicate, defaultValue) {
jtulach@816
  3976
            var predicateType = typeof predicate;
jtulach@816
  3977
            if (predicateType == "function")    // Given a function; run it against the data value
jtulach@816
  3978
                return predicate(object);
jtulach@816
  3979
            else if (predicateType == "string") // Given a string; treat it as a property name on the data value
jtulach@816
  3980
                return object[predicate];
jtulach@816
  3981
            else                                // Given no optionsText arg; use the data value itself
jtulach@816
  3982
                return defaultValue;
jtulach@816
  3983
        }
jtulach@816
  3984
jtulach@816
  3985
        // The following functions can run at two different times:
jtulach@816
  3986
        // The first is when the whole array is being updated directly from this binding handler.
jtulach@816
  3987
        // The second is when an observable value for a specific array entry is updated.
jtulach@816
  3988
        // oldOptions will be empty in the first case, but will be filled with the previously generated option in the second.
jtulach@816
  3989
        var itemUpdate = false;
jtulach@816
  3990
        function optionForArrayItem(arrayEntry, index, oldOptions) {
jtulach@816
  3991
            if (oldOptions.length) {
jtulach@816
  3992
                previousSelectedValues = oldOptions[0].selected ? [ ko.selectExtensions.readValue(oldOptions[0]) ] : [];
jtulach@816
  3993
                itemUpdate = true;
jtulach@816
  3994
            }
jtulach@816
  3995
            var option = element.ownerDocument.createElement("option");
jtulach@816
  3996
            if (arrayEntry === captionPlaceholder) {
jtulach@816
  3997
                ko.utils.setTextContent(option, allBindings.get('optionsCaption'));
jtulach@816
  3998
                ko.selectExtensions.writeValue(option, undefined);
jtulach@816
  3999
            } else {
jtulach@816
  4000
                // Apply a value to the option element
jtulach@816
  4001
                var optionValue = applyToObject(arrayEntry, allBindings.get('optionsValue'), arrayEntry);
jtulach@816
  4002
                ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue));
jtulach@816
  4003
jtulach@816
  4004
                // Apply some text to the option element
jtulach@816
  4005
                var optionText = applyToObject(arrayEntry, allBindings.get('optionsText'), optionValue);
jtulach@816
  4006
                ko.utils.setTextContent(option, optionText);
jtulach@816
  4007
            }
jtulach@816
  4008
            return [option];
jtulach@816
  4009
        }
jtulach@816
  4010
jtulach@816
  4011
        // By using a beforeRemove callback, we delay the removal until after new items are added. This fixes a selection
jtulach@816
  4012
        // problem in IE<=8 and Firefox. See https://github.com/knockout/knockout/issues/1208
jtulach@816
  4013
        arrayToDomNodeChildrenOptions['beforeRemove'] =
jtulach@816
  4014
            function (option) {
jtulach@816
  4015
                element.removeChild(option);
jtulach@816
  4016
            };
jtulach@816
  4017
jtulach@816
  4018
        function setSelectionCallback(arrayEntry, newOptions) {
jtulach@816
  4019
            // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
jtulach@816
  4020
            // That's why we first added them without selection. Now it's time to set the selection.
jtulach@816
  4021
            if (previousSelectedValues.length) {
jtulach@816
  4022
                var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[0])) >= 0;
jtulach@816
  4023
                ko.utils.setOptionNodeSelectionState(newOptions[0], isSelected);
jtulach@816
  4024
jtulach@816
  4025
                // If this option was changed from being selected during a single-item update, notify the change
jtulach@816
  4026
                if (itemUpdate && !isSelected)
jtulach@816
  4027
                    ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
jtulach@816
  4028
            }
jtulach@816
  4029
        }
jtulach@816
  4030
jtulach@816
  4031
        var callback = setSelectionCallback;
jtulach@816
  4032
        if (allBindings['has']('optionsAfterRender')) {
jtulach@816
  4033
            callback = function(arrayEntry, newOptions) {
jtulach@816
  4034
                setSelectionCallback(arrayEntry, newOptions);
jtulach@816
  4035
                ko.dependencyDetection.ignore(allBindings.get('optionsAfterRender'), null, [newOptions[0], arrayEntry !== captionPlaceholder ? arrayEntry : undefined]);
jtulach@816
  4036
            }
jtulach@816
  4037
        }
jtulach@816
  4038
jtulach@816
  4039
        ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, arrayToDomNodeChildrenOptions, callback);
jtulach@816
  4040
jtulach@816
  4041
        ko.dependencyDetection.ignore(function () {
jtulach@816
  4042
            if (allBindings.get('valueAllowUnset') && allBindings['has']('value')) {
jtulach@816
  4043
                // The model value is authoritative, so make sure its value is the one selected
jtulach@816
  4044
                ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value')), true /* allowUnset */);
jtulach@816
  4045
            } else {
jtulach@816
  4046
                // Determine if the selection has changed as a result of updating the options list
jtulach@816
  4047
                var selectionChanged;
jtulach@816
  4048
                if (element.multiple) {
jtulach@816
  4049
                    // For a multiple-select box, compare the new selection count to the previous one
jtulach@816
  4050
                    // But if nothing was selected before, the selection can't have changed
jtulach@816
  4051
                    selectionChanged = previousSelectedValues.length && selectedOptions().length < previousSelectedValues.length;
jtulach@816
  4052
                } else {
jtulach@816
  4053
                    // For a single-select box, compare the current value to the previous value
jtulach@816
  4054
                    // But if nothing was selected before or nothing is selected now, just look for a change in selection
jtulach@816
  4055
                    selectionChanged = (previousSelectedValues.length && element.selectedIndex >= 0)
jtulach@816
  4056
                        ? (ko.selectExtensions.readValue(element.options[element.selectedIndex]) !== previousSelectedValues[0])
jtulach@816
  4057
                        : (previousSelectedValues.length || element.selectedIndex >= 0);
jtulach@816
  4058
                }
jtulach@816
  4059
jtulach@816
  4060
                // Ensure consistency between model value and selected option.
jtulach@816
  4061
                // If the dropdown was changed so that selection is no longer the same,
jtulach@816
  4062
                // notify the value or selectedOptions binding.
jtulach@816
  4063
                if (selectionChanged) {
jtulach@816
  4064
                    ko.utils.triggerEvent(element, "change");
jtulach@816
  4065
                }
jtulach@816
  4066
            }
jtulach@816
  4067
        });
jtulach@816
  4068
jtulach@816
  4069
        // Workaround for IE bug
jtulach@816
  4070
        ko.utils.ensureSelectElementIsRenderedCorrectly(element);
jtulach@816
  4071
jtulach@816
  4072
        if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20)
jtulach@816
  4073
            element.scrollTop = previousScrollTop;
jtulach@816
  4074
    }
jtulach@816
  4075
};
jtulach@816
  4076
ko.bindingHandlers['options'].optionValueDomDataKey = ko.utils.domData.nextKey();
jtulach@816
  4077
ko.bindingHandlers['selectedOptions'] = {
jtulach@816
  4078
    'after': ['options', 'foreach'],
jtulach@816
  4079
    'init': function (element, valueAccessor, allBindings) {
jtulach@816
  4080
        ko.utils.registerEventHandler(element, "change", function () {
jtulach@816
  4081
            var value = valueAccessor(), valueToWrite = [];
jtulach@816
  4082
            ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
jtulach@816
  4083
                if (node.selected)
jtulach@816
  4084
                    valueToWrite.push(ko.selectExtensions.readValue(node));
jtulach@816
  4085
            });
jtulach@816
  4086
            ko.expressionRewriting.writeValueToProperty(value, allBindings, 'selectedOptions', valueToWrite);
jtulach@816
  4087
        });
jtulach@816
  4088
    },
jtulach@816
  4089
    'update': function (element, valueAccessor) {
jtulach@816
  4090
        if (ko.utils.tagNameLower(element) != "select")
jtulach@816
  4091
            throw new Error("values binding applies only to SELECT elements");
jtulach@816
  4092
jtulach@816
  4093
        var newValue = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  4094
        if (newValue && typeof newValue.length == "number") {
jtulach@816
  4095
            ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
jtulach@816
  4096
                var isSelected = ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0;
jtulach@816
  4097
                ko.utils.setOptionNodeSelectionState(node, isSelected);
jtulach@816
  4098
            });
jtulach@816
  4099
        }
jtulach@816
  4100
    }
jtulach@816
  4101
};
jtulach@816
  4102
ko.expressionRewriting.twoWayBindings['selectedOptions'] = true;
jtulach@816
  4103
ko.bindingHandlers['style'] = {
jtulach@816
  4104
    'update': function (element, valueAccessor) {
jtulach@816
  4105
        var value = ko.utils.unwrapObservable(valueAccessor() || {});
jtulach@816
  4106
        ko.utils.objectForEach(value, function(styleName, styleValue) {
jtulach@816
  4107
            styleValue = ko.utils.unwrapObservable(styleValue);
jtulach@816
  4108
jtulach@816
  4109
            if (styleValue === null || styleValue === undefined || styleValue === false) {
jtulach@816
  4110
                // Empty string removes the value, whereas null/undefined have no effect
jtulach@816
  4111
                styleValue = "";
jtulach@816
  4112
            }
jtulach@816
  4113
jtulach@816
  4114
            element.style[styleName] = styleValue;
jtulach@816
  4115
        });
jtulach@816
  4116
    }
jtulach@816
  4117
};
jtulach@816
  4118
ko.bindingHandlers['submit'] = {
jtulach@816
  4119
    'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
jtulach@816
  4120
        if (typeof valueAccessor() != "function")
jtulach@816
  4121
            throw new Error("The value for a submit binding must be a function");
jtulach@816
  4122
        ko.utils.registerEventHandler(element, "submit", function (event) {
jtulach@816
  4123
            var handlerReturnValue;
jtulach@816
  4124
            var value = valueAccessor();
jtulach@816
  4125
            try { handlerReturnValue = value.call(bindingContext['$data'], element); }
jtulach@816
  4126
            finally {
jtulach@816
  4127
                if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
jtulach@816
  4128
                    if (event.preventDefault)
jtulach@816
  4129
                        event.preventDefault();
jtulach@816
  4130
                    else
jtulach@816
  4131
                        event.returnValue = false;
jtulach@816
  4132
                }
jtulach@816
  4133
            }
jtulach@816
  4134
        });
jtulach@816
  4135
    }
jtulach@816
  4136
};
jtulach@816
  4137
ko.bindingHandlers['text'] = {
jtulach@816
  4138
    'init': function() {
jtulach@816
  4139
        // Prevent binding on the dynamically-injected text node (as developers are unlikely to expect that, and it has security implications).
jtulach@816
  4140
        // It should also make things faster, as we no longer have to consider whether the text node might be bindable.
jtulach@816
  4141
        return { 'controlsDescendantBindings': true };
jtulach@816
  4142
    },
jtulach@816
  4143
    'update': function (element, valueAccessor) {
jtulach@816
  4144
        ko.utils.setTextContent(element, valueAccessor());
jtulach@816
  4145
    }
jtulach@816
  4146
};
jtulach@816
  4147
ko.virtualElements.allowedBindings['text'] = true;
jtulach@816
  4148
(function () {
jtulach@816
  4149
jtulach@816
  4150
if (window && window.navigator) {
jtulach@816
  4151
    var parseVersion = function (matches) {
jtulach@816
  4152
        if (matches) {
jtulach@816
  4153
            return parseFloat(matches[1]);
jtulach@816
  4154
        }
jtulach@816
  4155
    };
jtulach@816
  4156
jtulach@816
  4157
    // Detect various browser versions because some old versions don't fully support the 'input' event
jtulach@816
  4158
    var operaVersion = window.opera && window.opera.version && parseInt(window.opera.version()),
jtulach@816
  4159
        userAgent = window.navigator.userAgent,
jtulach@816
  4160
        safariVersion = parseVersion(userAgent.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),
jtulach@816
  4161
        firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]*)/));
jtulach@816
  4162
}
jtulach@816
  4163
jtulach@816
  4164
// IE 8 and 9 have bugs that prevent the normal events from firing when the value changes.
jtulach@816
  4165
// But it does fire the 'selectionchange' event on many of those, presumably because the
jtulach@816
  4166
// cursor is moving and that counts as the selection changing. The 'selectionchange' event is
jtulach@816
  4167
// fired at the document level only and doesn't directly indicate which element changed. We
jtulach@816
  4168
// set up just one event handler for the document and use 'activeElement' to determine which
jtulach@816
  4169
// element was changed.
jtulach@816
  4170
if (ko.utils.ieVersion < 10) {
jtulach@816
  4171
    var selectionChangeRegisteredName = ko.utils.domData.nextKey(),
jtulach@816
  4172
        selectionChangeHandlerName = ko.utils.domData.nextKey();
jtulach@816
  4173
    var selectionChangeHandler = function(event) {
jtulach@816
  4174
        var target = this.activeElement,
jtulach@816
  4175
            handler = target && ko.utils.domData.get(target, selectionChangeHandlerName);
jtulach@816
  4176
        if (handler) {
jtulach@816
  4177
            handler(event);
jtulach@816
  4178
        }
jtulach@816
  4179
    };
jtulach@816
  4180
    var registerForSelectionChangeEvent = function (element, handler) {
jtulach@816
  4181
        var ownerDoc = element.ownerDocument;
jtulach@816
  4182
        if (!ko.utils.domData.get(ownerDoc, selectionChangeRegisteredName)) {
jtulach@816
  4183
            ko.utils.domData.set(ownerDoc, selectionChangeRegisteredName, true);
jtulach@816
  4184
            ko.utils.registerEventHandler(ownerDoc, 'selectionchange', selectionChangeHandler);
jtulach@816
  4185
        }
jtulach@816
  4186
        ko.utils.domData.set(element, selectionChangeHandlerName, handler);
jtulach@816
  4187
    };
jtulach@816
  4188
}
jtulach@816
  4189
jtulach@816
  4190
ko.bindingHandlers['textInput'] = {
jtulach@816
  4191
    'init': function (element, valueAccessor, allBindings) {
jtulach@816
  4192
jtulach@816
  4193
        var previousElementValue = element.value,
jtulach@816
  4194
            timeoutHandle,
jtulach@816
  4195
            elementValueBeforeEvent;
jtulach@816
  4196
jtulach@816
  4197
        var updateModel = function (event) {
jtulach@816
  4198
            clearTimeout(timeoutHandle);
jtulach@816
  4199
            elementValueBeforeEvent = timeoutHandle = undefined;
jtulach@816
  4200
jtulach@816
  4201
            var elementValue = element.value;
jtulach@816
  4202
            if (previousElementValue !== elementValue) {
jtulach@816
  4203
                // Provide a way for tests to know exactly which event was processed
jtulach@816
  4204
                if (DEBUG && event) element['_ko_textInputProcessedEvent'] = event.type;
jtulach@816
  4205
                previousElementValue = elementValue;
jtulach@816
  4206
                ko.expressionRewriting.writeValueToProperty(valueAccessor(), allBindings, 'textInput', elementValue);
jtulach@816
  4207
            }
jtulach@816
  4208
        };
jtulach@816
  4209
jtulach@816
  4210
        var deferUpdateModel = function (event) {
jtulach@816
  4211
            if (!timeoutHandle) {
jtulach@816
  4212
                // The elementValueBeforeEvent variable is set *only* during the brief gap between an
jtulach@816
  4213
                // event firing and the updateModel function running. This allows us to ignore model
jtulach@816
  4214
                // updates that are from the previous state of the element, usually due to techniques
jtulach@816
  4215
                // such as rateLimit. Such updates, if not ignored, can cause keystrokes to be lost.
jtulach@816
  4216
                elementValueBeforeEvent = element.value;
jtulach@816
  4217
                var handler = DEBUG ? updateModel.bind(element, {type: event.type}) : updateModel;
jtulach@816
  4218
                timeoutHandle = setTimeout(handler, 4);
jtulach@816
  4219
            }
jtulach@816
  4220
        };
jtulach@816
  4221
jtulach@816
  4222
        var updateView = function () {
jtulach@816
  4223
            var modelValue = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  4224
jtulach@816
  4225
            if (modelValue === null || modelValue === undefined) {
jtulach@816
  4226
                modelValue = '';
jtulach@816
  4227
            }
jtulach@816
  4228
jtulach@816
  4229
            if (elementValueBeforeEvent !== undefined && modelValue === elementValueBeforeEvent) {
jtulach@816
  4230
                setTimeout(updateView, 4);
jtulach@816
  4231
                return;
jtulach@816
  4232
            }
jtulach@816
  4233
jtulach@816
  4234
            // Update the element only if the element and model are different. On some browsers, updating the value
jtulach@816
  4235
            // will move the cursor to the end of the input, which would be bad while the user is typing.
jtulach@816
  4236
            if (element.value !== modelValue) {
jtulach@816
  4237
                previousElementValue = modelValue;  // Make sure we ignore events (propertychange) that result from updating the value
jtulach@816
  4238
                element.value = modelValue;
jtulach@816
  4239
            }
jtulach@816
  4240
        };
jtulach@816
  4241
jtulach@816
  4242
        var onEvent = function (event, handler) {
jtulach@816
  4243
            ko.utils.registerEventHandler(element, event, handler);
jtulach@816
  4244
        };
jtulach@816
  4245
jtulach@816
  4246
        if (DEBUG && ko.bindingHandlers['textInput']['_forceUpdateOn']) {
jtulach@816
  4247
            // Provide a way for tests to specify exactly which events are bound
jtulach@816
  4248
            ko.utils.arrayForEach(ko.bindingHandlers['textInput']['_forceUpdateOn'], function(eventName) {
jtulach@816
  4249
                if (eventName.slice(0,5) == 'after') {
jtulach@816
  4250
                    onEvent(eventName.slice(5), deferUpdateModel);
jtulach@816
  4251
                } else {
jtulach@816
  4252
                    onEvent(eventName, updateModel);
jtulach@816
  4253
                }
jtulach@816
  4254
            });
jtulach@816
  4255
        } else {
jtulach@816
  4256
            if (ko.utils.ieVersion < 10) {
jtulach@816
  4257
                // Internet Explorer <= 8 doesn't support the 'input' event, but does include 'propertychange' that fires whenever
jtulach@816
  4258
                // any property of an element changes. Unlike 'input', it also fires if a property is changed from JavaScript code,
jtulach@816
  4259
                // but that's an acceptable compromise for this binding. IE 9 does support 'input', but since it doesn't fire it
jtulach@816
  4260
                // when using autocomplete, we'll use 'propertychange' for it also.
jtulach@816
  4261
                onEvent('propertychange', function(event) {
jtulach@816
  4262
                    if (event.propertyName === 'value') {
jtulach@816
  4263
                        updateModel(event);
jtulach@816
  4264
                    }
jtulach@816
  4265
                });
jtulach@816
  4266
jtulach@816
  4267
                if (ko.utils.ieVersion == 8) {
jtulach@816
  4268
                    // IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from
jtulach@816
  4269
                    // JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following
jtulach@816
  4270
                    // events too.
jtulach@816
  4271
                    onEvent('keyup', updateModel);      // A single keystoke
jtulach@816
  4272
                    onEvent('keydown', updateModel);    // The first character when a key is held down
jtulach@816
  4273
                }
jtulach@816
  4274
                if (ko.utils.ieVersion >= 8) {
jtulach@816
  4275
                    // Internet Explorer 9 doesn't fire the 'input' event when deleting text, including using
jtulach@816
  4276
                    // the backspace, delete, or ctrl-x keys, clicking the 'x' to clear the input, dragging text
jtulach@816
  4277
                    // out of the field, and cutting or deleting text using the context menu. 'selectionchange'
jtulach@816
  4278
                    // can detect all of those except dragging text out of the field, for which we use 'dragend'.
jtulach@816
  4279
                    // These are also needed in IE8 because of the bug described above.
jtulach@816
  4280
                    registerForSelectionChangeEvent(element, updateModel);  // 'selectionchange' covers cut, paste, drop, delete, etc.
jtulach@816
  4281
                    onEvent('dragend', deferUpdateModel);
jtulach@816
  4282
                }
jtulach@816
  4283
            } else {
jtulach@816
  4284
                // All other supported browsers support the 'input' event, which fires whenever the content of the element is changed
jtulach@816
  4285
                // through the user interface.
jtulach@816
  4286
                onEvent('input', updateModel);
jtulach@816
  4287
jtulach@816
  4288
                if (safariVersion < 5 && ko.utils.tagNameLower(element) === "textarea") {
jtulach@816
  4289
                    // Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput'
jtulach@816
  4290
                    // but only when typing). So we'll just catch as much as we can with keydown, cut, and paste.
jtulach@816
  4291
                    onEvent('keydown', deferUpdateModel);
jtulach@816
  4292
                    onEvent('paste', deferUpdateModel);
jtulach@816
  4293
                    onEvent('cut', deferUpdateModel);
jtulach@816
  4294
                } else if (operaVersion < 11) {
jtulach@816
  4295
                    // Opera 10 doesn't always fire the 'input' event for cut, paste, undo & drop operations.
jtulach@816
  4296
                    // We can try to catch some of those using 'keydown'.
jtulach@816
  4297
                    onEvent('keydown', deferUpdateModel);
jtulach@816
  4298
                } else if (firefoxVersion < 4.0) {
jtulach@816
  4299
                    // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
jtulach@816
  4300
                    onEvent('DOMAutoComplete', updateModel);
jtulach@816
  4301
jtulach@816
  4302
                    // Firefox <=3.5 doesn't fire the 'input' event when text is dropped into the input.
jtulach@816
  4303
                    onEvent('dragdrop', updateModel);       // <3.5
jtulach@816
  4304
                    onEvent('drop', updateModel);           // 3.5
jtulach@816
  4305
                }
jtulach@816
  4306
            }
jtulach@816
  4307
        }
jtulach@816
  4308
jtulach@816
  4309
        // Bind to the change event so that we can catch programmatic updates of the value that fire this event.
jtulach@816
  4310
        onEvent('change', updateModel);
jtulach@816
  4311
jtulach@816
  4312
        ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
jtulach@816
  4313
    }
jtulach@816
  4314
};
jtulach@816
  4315
ko.expressionRewriting.twoWayBindings['textInput'] = true;
jtulach@816
  4316
jtulach@816
  4317
// textinput is an alias for textInput
jtulach@816
  4318
ko.bindingHandlers['textinput'] = {
jtulach@816
  4319
    // preprocess is the only way to set up a full alias
jtulach@816
  4320
    'preprocess': function (value, name, addBinding) {
jtulach@816
  4321
        addBinding('textInput', value);
jtulach@816
  4322
    }
jtulach@816
  4323
};
jtulach@816
  4324
jtulach@816
  4325
})();ko.bindingHandlers['uniqueName'] = {
jtulach@816
  4326
    'init': function (element, valueAccessor) {
jtulach@816
  4327
        if (valueAccessor()) {
jtulach@816
  4328
            var name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
jtulach@816
  4329
            ko.utils.setElementName(element, name);
jtulach@816
  4330
        }
jtulach@816
  4331
    }
jtulach@816
  4332
};
jtulach@816
  4333
ko.bindingHandlers['uniqueName'].currentIndex = 0;
jtulach@816
  4334
ko.bindingHandlers['value'] = {
jtulach@816
  4335
    'after': ['options', 'foreach'],
jtulach@816
  4336
    'init': function (element, valueAccessor, allBindings) {
jtulach@816
  4337
        // If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
jtulach@816
  4338
        if (element.tagName.toLowerCase() == "input" && (element.type == "checkbox" || element.type == "radio")) {
jtulach@816
  4339
            ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor });
jtulach@816
  4340
            return;
jtulach@816
  4341
        }
jtulach@816
  4342
jtulach@816
  4343
        // Always catch "change" event; possibly other events too if asked
jtulach@816
  4344
        var eventsToCatch = ["change"];
jtulach@816
  4345
        var requestedEventsToCatch = allBindings.get("valueUpdate");
jtulach@816
  4346
        var propertyChangedFired = false;
jtulach@816
  4347
        var elementValueBeforeEvent = null;
jtulach@816
  4348
jtulach@816
  4349
        if (requestedEventsToCatch) {
jtulach@816
  4350
            if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
jtulach@816
  4351
                requestedEventsToCatch = [requestedEventsToCatch];
jtulach@816
  4352
            ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
jtulach@816
  4353
            eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
jtulach@816
  4354
        }
jtulach@816
  4355
jtulach@816
  4356
        var valueUpdateHandler = function() {
jtulach@816
  4357
            elementValueBeforeEvent = null;
jtulach@816
  4358
            propertyChangedFired = false;
jtulach@816
  4359
            var modelValue = valueAccessor();
jtulach@816
  4360
            var elementValue = ko.selectExtensions.readValue(element);
jtulach@816
  4361
            ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'value', elementValue);
jtulach@816
  4362
        }
jtulach@816
  4363
jtulach@816
  4364
        // Workaround for https://github.com/SteveSanderson/knockout/issues/122
jtulach@816
  4365
        // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
jtulach@816
  4366
        var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
jtulach@816
  4367
                                       && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
jtulach@816
  4368
        if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
jtulach@816
  4369
            ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
jtulach@816
  4370
            ko.utils.registerEventHandler(element, "focus", function () { propertyChangedFired = false });
jtulach@816
  4371
            ko.utils.registerEventHandler(element, "blur", function() {
jtulach@816
  4372
                if (propertyChangedFired) {
jtulach@816
  4373
                    valueUpdateHandler();
jtulach@816
  4374
                }
jtulach@816
  4375
            });
jtulach@816
  4376
        }
jtulach@816
  4377
jtulach@816
  4378
        ko.utils.arrayForEach(eventsToCatch, function(eventName) {
jtulach@816
  4379
            // The syntax "after<eventname>" means "run the handler asynchronously after the event"
jtulach@816
  4380
            // This is useful, for example, to catch "keydown" events after the browser has updated the control
jtulach@816
  4381
            // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
jtulach@816
  4382
            var handler = valueUpdateHandler;
jtulach@816
  4383
            if (ko.utils.stringStartsWith(eventName, "after")) {
jtulach@816
  4384
                handler = function() {
jtulach@816
  4385
                    // The elementValueBeforeEvent variable is non-null *only* during the brief gap between
jtulach@816
  4386
                    // a keyX event firing and the valueUpdateHandler running, which is scheduled to happen
jtulach@816
  4387
                    // at the earliest asynchronous opportunity. We store this temporary information so that
jtulach@816
  4388
                    // if, between keyX and valueUpdateHandler, the underlying model value changes separately,
jtulach@816
  4389
                    // we can overwrite that model value change with the value the user just typed. Otherwise,
jtulach@816
  4390
                    // techniques like rateLimit can trigger model changes at critical moments that will
jtulach@816
  4391
                    // override the user's inputs, causing keystrokes to be lost.
jtulach@816
  4392
                    elementValueBeforeEvent = ko.selectExtensions.readValue(element);
jtulach@816
  4393
                    setTimeout(valueUpdateHandler, 0);
jtulach@816
  4394
                };
jtulach@816
  4395
                eventName = eventName.substring("after".length);
jtulach@816
  4396
            }
jtulach@816
  4397
            ko.utils.registerEventHandler(element, eventName, handler);
jtulach@816
  4398
        });
jtulach@816
  4399
jtulach@816
  4400
        var updateFromModel = function () {
jtulach@816
  4401
            var newValue = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  4402
            var elementValue = ko.selectExtensions.readValue(element);
jtulach@816
  4403
jtulach@816
  4404
            if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
jtulach@816
  4405
                setTimeout(updateFromModel, 0);
jtulach@816
  4406
                return;
jtulach@816
  4407
            }
jtulach@816
  4408
jtulach@816
  4409
            var valueHasChanged = (newValue !== elementValue);
jtulach@816
  4410
jtulach@816
  4411
            if (valueHasChanged) {
jtulach@816
  4412
                if (ko.utils.tagNameLower(element) === "select") {
jtulach@816
  4413
                    var allowUnset = allBindings.get('valueAllowUnset');
jtulach@816
  4414
                    var applyValueAction = function () {
jtulach@816
  4415
                        ko.selectExtensions.writeValue(element, newValue, allowUnset);
jtulach@816
  4416
                    };
jtulach@816
  4417
                    applyValueAction();
jtulach@816
  4418
jtulach@816
  4419
                    if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
jtulach@816
  4420
                        // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
jtulach@816
  4421
                        // because you're not allowed to have a model value that disagrees with a visible UI selection.
jtulach@816
  4422
                        ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
jtulach@816
  4423
                    } else {
jtulach@816
  4424
                        // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
jtulach@816
  4425
                        // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
jtulach@816
  4426
                        // to apply the value as well.
jtulach@816
  4427
                        setTimeout(applyValueAction, 0);
jtulach@816
  4428
                    }
jtulach@816
  4429
                } else {
jtulach@816
  4430
                    ko.selectExtensions.writeValue(element, newValue);
jtulach@816
  4431
                }
jtulach@816
  4432
            }
jtulach@816
  4433
        };
jtulach@816
  4434
jtulach@816
  4435
        ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element });
jtulach@816
  4436
    },
jtulach@816
  4437
    'update': function() {} // Keep for backwards compatibility with code that may have wrapped value binding
jtulach@816
  4438
};
jtulach@816
  4439
ko.expressionRewriting.twoWayBindings['value'] = true;
jtulach@816
  4440
ko.bindingHandlers['visible'] = {
jtulach@816
  4441
    'update': function (element, valueAccessor) {
jtulach@816
  4442
        var value = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  4443
        var isCurrentlyVisible = !(element.style.display == "none");
jtulach@816
  4444
        if (value && !isCurrentlyVisible)
jtulach@816
  4445
            element.style.display = "";
jtulach@816
  4446
        else if ((!value) && isCurrentlyVisible)
jtulach@816
  4447
            element.style.display = "none";
jtulach@816
  4448
    }
jtulach@816
  4449
};
jtulach@816
  4450
// 'click' is just a shorthand for the usual full-length event:{click:handler}
jtulach@816
  4451
makeEventHandlerShortcut('click');
jtulach@816
  4452
// If you want to make a custom template engine,
jtulach@816
  4453
//
jtulach@816
  4454
// [1] Inherit from this class (like ko.nativeTemplateEngine does)
jtulach@816
  4455
// [2] Override 'renderTemplateSource', supplying a function with this signature:
jtulach@816
  4456
//
jtulach@816
  4457
//        function (templateSource, bindingContext, options) {
jtulach@816
  4458
//            // - templateSource.text() is the text of the template you should render
jtulach@816
  4459
//            // - bindingContext.$data is the data you should pass into the template
jtulach@816
  4460
//            //   - you might also want to make bindingContext.$parent, bindingContext.$parents,
jtulach@816
  4461
//            //     and bindingContext.$root available in the template too
jtulach@816
  4462
//            // - options gives you access to any other properties set on "data-bind: { template: options }"
jtulach@816
  4463
//            //
jtulach@816
  4464
//            // Return value: an array of DOM nodes
jtulach@816
  4465
//        }
jtulach@816
  4466
//
jtulach@816
  4467
// [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
jtulach@816
  4468
//
jtulach@816
  4469
//        function (script) {
jtulach@816
  4470
//            // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
jtulach@816
  4471
//            //               For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
jtulach@816
  4472
//        }
jtulach@816
  4473
//
jtulach@816
  4474
//     This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
jtulach@816
  4475
//     If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
jtulach@816
  4476
//     and then you don't need to override 'createJavaScriptEvaluatorBlock'.
jtulach@816
  4477
jtulach@816
  4478
ko.templateEngine = function () { };
jtulach@816
  4479
jtulach@816
  4480
ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
jtulach@816
  4481
    throw new Error("Override renderTemplateSource");
jtulach@816
  4482
};
jtulach@816
  4483
jtulach@816
  4484
ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
jtulach@816
  4485
    throw new Error("Override createJavaScriptEvaluatorBlock");
jtulach@816
  4486
};
jtulach@816
  4487
jtulach@816
  4488
ko.templateEngine.prototype['makeTemplateSource'] = function(template, templateDocument) {
jtulach@816
  4489
    // Named template
jtulach@816
  4490
    if (typeof template == "string") {
jtulach@816
  4491
        templateDocument = templateDocument || document;
jtulach@816
  4492
        var elem = templateDocument.getElementById(template);
jtulach@816
  4493
        if (!elem)
jtulach@816
  4494
            throw new Error("Cannot find template with ID " + template);
jtulach@816
  4495
        return new ko.templateSources.domElement(elem);
jtulach@816
  4496
    } else if ((template.nodeType == 1) || (template.nodeType == 8)) {
jtulach@816
  4497
        // Anonymous template
jtulach@816
  4498
        return new ko.templateSources.anonymousTemplate(template);
jtulach@816
  4499
    } else
jtulach@816
  4500
        throw new Error("Unknown template type: " + template);
jtulach@816
  4501
};
jtulach@816
  4502
jtulach@816
  4503
ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) {
jtulach@816
  4504
    var templateSource = this['makeTemplateSource'](template, templateDocument);
jtulach@816
  4505
    return this['renderTemplateSource'](templateSource, bindingContext, options);
jtulach@816
  4506
};
jtulach@816
  4507
jtulach@816
  4508
ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) {
jtulach@816
  4509
    // Skip rewriting if requested
jtulach@816
  4510
    if (this['allowTemplateRewriting'] === false)
jtulach@816
  4511
        return true;
jtulach@816
  4512
    return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten");
jtulach@816
  4513
};
jtulach@816
  4514
jtulach@816
  4515
ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, templateDocument) {
jtulach@816
  4516
    var templateSource = this['makeTemplateSource'](template, templateDocument);
jtulach@816
  4517
    var rewritten = rewriterCallback(templateSource['text']());
jtulach@816
  4518
    templateSource['text'](rewritten);
jtulach@816
  4519
    templateSource['data']("isRewritten", true);
jtulach@816
  4520
};
jtulach@816
  4521
jtulach@816
  4522
ko.exportSymbol('templateEngine', ko.templateEngine);
jtulach@816
  4523
jtulach@816
  4524
ko.templateRewriting = (function () {
jtulach@816
  4525
    var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi;
jtulach@816
  4526
    var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
jtulach@816
  4527
jtulach@816
  4528
    function validateDataBindValuesForRewriting(keyValueArray) {
jtulach@816
  4529
        var allValidators = ko.expressionRewriting.bindingRewriteValidators;
jtulach@816
  4530
        for (var i = 0; i < keyValueArray.length; i++) {
jtulach@816
  4531
            var key = keyValueArray[i]['key'];
jtulach@816
  4532
            if (allValidators.hasOwnProperty(key)) {
jtulach@816
  4533
                var validator = allValidators[key];
jtulach@816
  4534
jtulach@816
  4535
                if (typeof validator === "function") {
jtulach@816
  4536
                    var possibleErrorMessage = validator(keyValueArray[i]['value']);
jtulach@816
  4537
                    if (possibleErrorMessage)
jtulach@816
  4538
                        throw new Error(possibleErrorMessage);
jtulach@816
  4539
                } else if (!validator) {
jtulach@816
  4540
                    throw new Error("This template engine does not support the '" + key + "' binding within its templates");
jtulach@816
  4541
                }
jtulach@816
  4542
            }
jtulach@816
  4543
        }
jtulach@816
  4544
    }
jtulach@816
  4545
jtulach@816
  4546
    function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, nodeName, templateEngine) {
jtulach@816
  4547
        var dataBindKeyValueArray = ko.expressionRewriting.parseObjectLiteral(dataBindAttributeValue);
jtulach@816
  4548
        validateDataBindValuesForRewriting(dataBindKeyValueArray);
jtulach@816
  4549
        var rewrittenDataBindAttributeValue = ko.expressionRewriting.preProcessBindings(dataBindKeyValueArray, {'valueAccessors':true});
jtulach@816
  4550
jtulach@816
  4551
        // For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
jtulach@816
  4552
        // anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
jtulach@816
  4553
        // extra indirection.
jtulach@816
  4554
        var applyBindingsToNextSiblingScript =
jtulach@816
  4555
            "ko.__tr_ambtns(function($context,$element){return(function(){return{ " + rewrittenDataBindAttributeValue + " } })()},'" + nodeName.toLowerCase() + "')";
jtulach@816
  4556
        return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
jtulach@816
  4557
    }
jtulach@816
  4558
jtulach@816
  4559
    return {
jtulach@816
  4560
        ensureTemplateIsRewritten: function (template, templateEngine, templateDocument) {
jtulach@816
  4561
            if (!templateEngine['isTemplateRewritten'](template, templateDocument))
jtulach@816
  4562
                templateEngine['rewriteTemplate'](template, function (htmlString) {
jtulach@816
  4563
                    return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine);
jtulach@816
  4564
                }, templateDocument);
jtulach@816
  4565
        },
jtulach@816
  4566
jtulach@816
  4567
        memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
jtulach@816
  4568
            return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
jtulach@816
  4569
                return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[4], /* tagToRetain: */ arguments[1], /* nodeName: */ arguments[2], templateEngine);
jtulach@816
  4570
            }).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
jtulach@816
  4571
                return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", /* nodeName: */ "#comment", templateEngine);
jtulach@816
  4572
            });
jtulach@816
  4573
        },
jtulach@816
  4574
jtulach@816
  4575
        applyMemoizedBindingsToNextSibling: function (bindings, nodeName) {
jtulach@816
  4576
            return ko.memoization.memoize(function (domNode, bindingContext) {
jtulach@816
  4577
                var nodeToBind = domNode.nextSibling;
jtulach@816
  4578
                if (nodeToBind && nodeToBind.nodeName.toLowerCase() === nodeName) {
jtulach@816
  4579
                    ko.applyBindingAccessorsToNode(nodeToBind, bindings, bindingContext);
jtulach@816
  4580
                }
jtulach@816
  4581
            });
jtulach@816
  4582
        }
jtulach@816
  4583
    }
jtulach@816
  4584
})();
jtulach@816
  4585
jtulach@816
  4586
jtulach@816
  4587
// Exported only because it has to be referenced by string lookup from within rewritten template
jtulach@816
  4588
ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextSibling);
jtulach@816
  4589
(function() {
jtulach@816
  4590
    // A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving
jtulach@816
  4591
    // logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.)
jtulach@816
  4592
    //
jtulach@816
  4593
    // Two are provided by default:
jtulach@816
  4594
    //  1. ko.templateSources.domElement       - reads/writes the text content of an arbitrary DOM element
jtulach@816
  4595
    //  2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but
jtulach@816
  4596
    //                                           without reading/writing the actual element text content, since it will be overwritten
jtulach@816
  4597
    //                                           with the rendered template output.
jtulach@816
  4598
    // You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
jtulach@816
  4599
    // Template sources need to have the following functions:
jtulach@816
  4600
    //   text() 			- returns the template text from your storage location
jtulach@816
  4601
    //   text(value)		- writes the supplied template text to your storage location
jtulach@816
  4602
    //   data(key)			- reads values stored using data(key, value) - see below
jtulach@816
  4603
    //   data(key, value)	- associates "value" with this template and the key "key". Is used to store information like "isRewritten".
jtulach@816
  4604
    //
jtulach@816
  4605
    // Optionally, template sources can also have the following functions:
jtulach@816
  4606
    //   nodes()            - returns a DOM element containing the nodes of this template, where available
jtulach@816
  4607
    //   nodes(value)       - writes the given DOM element to your storage location
jtulach@816
  4608
    // If a DOM element is available for a given template source, template engines are encouraged to use it in preference over text()
jtulach@816
  4609
    // for improved speed. However, all templateSources must supply text() even if they don't supply nodes().
jtulach@816
  4610
    //
jtulach@816
  4611
    // Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were
jtulach@816
  4612
    // using and overriding "makeTemplateSource" to return an instance of your custom template source.
jtulach@816
  4613
jtulach@816
  4614
    ko.templateSources = {};
jtulach@816
  4615
jtulach@816
  4616
    // ---- ko.templateSources.domElement -----
jtulach@816
  4617
jtulach@816
  4618
    ko.templateSources.domElement = function(element) {
jtulach@816
  4619
        this.domElement = element;
jtulach@816
  4620
    }
jtulach@816
  4621
jtulach@816
  4622
    ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
jtulach@816
  4623
        var tagNameLower = ko.utils.tagNameLower(this.domElement),
jtulach@816
  4624
            elemContentsProperty = tagNameLower === "script" ? "text"
jtulach@816
  4625
                                 : tagNameLower === "textarea" ? "value"
jtulach@816
  4626
                                 : "innerHTML";
jtulach@816
  4627
jtulach@816
  4628
        if (arguments.length == 0) {
jtulach@816
  4629
            return this.domElement[elemContentsProperty];
jtulach@816
  4630
        } else {
jtulach@816
  4631
            var valueToWrite = arguments[0];
jtulach@816
  4632
            if (elemContentsProperty === "innerHTML")
jtulach@816
  4633
                ko.utils.setHtml(this.domElement, valueToWrite);
jtulach@816
  4634
            else
jtulach@816
  4635
                this.domElement[elemContentsProperty] = valueToWrite;
jtulach@816
  4636
        }
jtulach@816
  4637
    };
jtulach@816
  4638
jtulach@816
  4639
    var dataDomDataPrefix = ko.utils.domData.nextKey() + "_";
jtulach@816
  4640
    ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
jtulach@816
  4641
        if (arguments.length === 1) {
jtulach@816
  4642
            return ko.utils.domData.get(this.domElement, dataDomDataPrefix + key);
jtulach@816
  4643
        } else {
jtulach@816
  4644
            ko.utils.domData.set(this.domElement, dataDomDataPrefix + key, arguments[1]);
jtulach@816
  4645
        }
jtulach@816
  4646
    };
jtulach@816
  4647
jtulach@816
  4648
    // ---- ko.templateSources.anonymousTemplate -----
jtulach@816
  4649
    // Anonymous templates are normally saved/retrieved as DOM nodes through "nodes".
jtulach@816
  4650
    // For compatibility, you can also read "text"; it will be serialized from the nodes on demand.
jtulach@816
  4651
    // Writing to "text" is still supported, but then the template data will not be available as DOM nodes.
jtulach@816
  4652
jtulach@816
  4653
    var anonymousTemplatesDomDataKey = ko.utils.domData.nextKey();
jtulach@816
  4654
    ko.templateSources.anonymousTemplate = function(element) {
jtulach@816
  4655
        this.domElement = element;
jtulach@816
  4656
    }
jtulach@816
  4657
    ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
jtulach@816
  4658
    ko.templateSources.anonymousTemplate.prototype.constructor = ko.templateSources.anonymousTemplate;
jtulach@816
  4659
    ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
jtulach@816
  4660
        if (arguments.length == 0) {
jtulach@816
  4661
            var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
jtulach@816
  4662
            if (templateData.textData === undefined && templateData.containerData)
jtulach@816
  4663
                templateData.textData = templateData.containerData.innerHTML;
jtulach@816
  4664
            return templateData.textData;
jtulach@816
  4665
        } else {
jtulach@816
  4666
            var valueToWrite = arguments[0];
jtulach@816
  4667
            ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {textData: valueToWrite});
jtulach@816
  4668
        }
jtulach@816
  4669
    };
jtulach@816
  4670
    ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
jtulach@816
  4671
        if (arguments.length == 0) {
jtulach@816
  4672
            var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
jtulach@816
  4673
            return templateData.containerData;
jtulach@816
  4674
        } else {
jtulach@816
  4675
            var valueToWrite = arguments[0];
jtulach@816
  4676
            ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {containerData: valueToWrite});
jtulach@816
  4677
        }
jtulach@816
  4678
    };
jtulach@816
  4679
jtulach@816
  4680
    ko.exportSymbol('templateSources', ko.templateSources);
jtulach@816
  4681
    ko.exportSymbol('templateSources.domElement', ko.templateSources.domElement);
jtulach@816
  4682
    ko.exportSymbol('templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate);
jtulach@816
  4683
})();
jtulach@816
  4684
(function () {
jtulach@816
  4685
    var _templateEngine;
jtulach@816
  4686
    ko.setTemplateEngine = function (templateEngine) {
jtulach@816
  4687
        if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
jtulach@816
  4688
            throw new Error("templateEngine must inherit from ko.templateEngine");
jtulach@816
  4689
        _templateEngine = templateEngine;
jtulach@816
  4690
    }
jtulach@816
  4691
jtulach@816
  4692
    function invokeForEachNodeInContinuousRange(firstNode, lastNode, action) {
jtulach@816
  4693
        var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode);
jtulach@816
  4694
        while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) {
jtulach@816
  4695
            nextInQueue = ko.virtualElements.nextSibling(node);
jtulach@816
  4696
            action(node, nextInQueue);
jtulach@816
  4697
        }
jtulach@816
  4698
    }
jtulach@816
  4699
jtulach@816
  4700
    function activateBindingsOnContinuousNodeArray(continuousNodeArray, bindingContext) {
jtulach@816
  4701
        // To be used on any nodes that have been rendered by a template and have been inserted into some parent element
jtulach@816
  4702
        // Walks through continuousNodeArray (which *must* be continuous, i.e., an uninterrupted sequence of sibling nodes, because
jtulach@816
  4703
        // the algorithm for walking them relies on this), and for each top-level item in the virtual-element sense,
jtulach@816
  4704
        // (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings
jtulach@816
  4705
        // (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting)
jtulach@816
  4706
jtulach@816
  4707
        if (continuousNodeArray.length) {
jtulach@816
  4708
            var firstNode = continuousNodeArray[0],
jtulach@816
  4709
                lastNode = continuousNodeArray[continuousNodeArray.length - 1],
jtulach@816
  4710
                parentNode = firstNode.parentNode,
jtulach@816
  4711
                provider = ko.bindingProvider['instance'],
jtulach@816
  4712
                preprocessNode = provider['preprocessNode'];
jtulach@816
  4713
jtulach@816
  4714
            if (preprocessNode) {
jtulach@816
  4715
                invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node, nextNodeInRange) {
jtulach@816
  4716
                    var nodePreviousSibling = node.previousSibling;
jtulach@816
  4717
                    var newNodes = preprocessNode.call(provider, node);
jtulach@816
  4718
                    if (newNodes) {
jtulach@816
  4719
                        if (node === firstNode)
jtulach@816
  4720
                            firstNode = newNodes[0] || nextNodeInRange;
jtulach@816
  4721
                        if (node === lastNode)
jtulach@816
  4722
                            lastNode = newNodes[newNodes.length - 1] || nodePreviousSibling;
jtulach@816
  4723
                    }
jtulach@816
  4724
                });
jtulach@816
  4725
jtulach@816
  4726
                // Because preprocessNode can change the nodes, including the first and last nodes, update continuousNodeArray to match.
jtulach@816
  4727
                // We need the full set, including inner nodes, because the unmemoize step might remove the first node (and so the real
jtulach@816
  4728
                // first node needs to be in the array).
jtulach@816
  4729
                continuousNodeArray.length = 0;
jtulach@816
  4730
                if (!firstNode) { // preprocessNode might have removed all the nodes, in which case there's nothing left to do
jtulach@816
  4731
                    return;
jtulach@816
  4732
                }
jtulach@816
  4733
                if (firstNode === lastNode) {
jtulach@816
  4734
                    continuousNodeArray.push(firstNode);
jtulach@816
  4735
                } else {
jtulach@816
  4736
                    continuousNodeArray.push(firstNode, lastNode);
jtulach@816
  4737
                    ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode);
jtulach@816
  4738
                }
jtulach@816
  4739
            }
jtulach@816
  4740
jtulach@816
  4741
            // Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
jtulach@816
  4742
            // whereas a regular applyBindings won't introduce new memoized nodes
jtulach@816
  4743
            invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {
jtulach@816
  4744
                if (node.nodeType === 1 || node.nodeType === 8)
jtulach@816
  4745
                    ko.applyBindings(bindingContext, node);
jtulach@816
  4746
            });
jtulach@816
  4747
            invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {
jtulach@816
  4748
                if (node.nodeType === 1 || node.nodeType === 8)
jtulach@816
  4749
                    ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
jtulach@816
  4750
            });
jtulach@816
  4751
jtulach@816
  4752
            // Make sure any changes done by applyBindings or unmemoize are reflected in the array
jtulach@816
  4753
            ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode);
jtulach@816
  4754
        }
jtulach@816
  4755
    }
jtulach@816
  4756
jtulach@816
  4757
    function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
jtulach@816
  4758
        return nodeOrNodeArray.nodeType ? nodeOrNodeArray
jtulach@816
  4759
                                        : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
jtulach@816
  4760
                                        : null;
jtulach@816
  4761
    }
jtulach@816
  4762
jtulach@816
  4763
    function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
jtulach@816
  4764
        options = options || {};
jtulach@816
  4765
        var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
jtulach@816
  4766
        var templateDocument = firstTargetNode && firstTargetNode.ownerDocument;
jtulach@816
  4767
        var templateEngineToUse = (options['templateEngine'] || _templateEngine);
jtulach@816
  4768
        ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument);
jtulach@816
  4769
        var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument);
jtulach@816
  4770
jtulach@816
  4771
        // Loosely check result is an array of DOM nodes
jtulach@816
  4772
        if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
jtulach@816
  4773
            throw new Error("Template engine must return an array of DOM nodes");
jtulach@816
  4774
jtulach@816
  4775
        var haveAddedNodesToParent = false;
jtulach@816
  4776
        switch (renderMode) {
jtulach@816
  4777
            case "replaceChildren":
jtulach@816
  4778
                ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
jtulach@816
  4779
                haveAddedNodesToParent = true;
jtulach@816
  4780
                break;
jtulach@816
  4781
            case "replaceNode":
jtulach@816
  4782
                ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
jtulach@816
  4783
                haveAddedNodesToParent = true;
jtulach@816
  4784
                break;
jtulach@816
  4785
            case "ignoreTargetNode": break;
jtulach@816
  4786
            default:
jtulach@816
  4787
                throw new Error("Unknown renderMode: " + renderMode);
jtulach@816
  4788
        }
jtulach@816
  4789
jtulach@816
  4790
        if (haveAddedNodesToParent) {
jtulach@816
  4791
            activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
jtulach@816
  4792
            if (options['afterRender'])
jtulach@816
  4793
                ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext['$data']]);
jtulach@816
  4794
        }
jtulach@816
  4795
jtulach@816
  4796
        return renderedNodesArray;
jtulach@816
  4797
    }
jtulach@816
  4798
jtulach@816
  4799
    function resolveTemplateName(template, data, context) {
jtulach@816
  4800
        // The template can be specified as:
jtulach@816
  4801
        if (ko.isObservable(template)) {
jtulach@816
  4802
            // 1. An observable, with string value
jtulach@816
  4803
            return template();
jtulach@816
  4804
        } else if (typeof template === 'function') {
jtulach@816
  4805
            // 2. A function of (data, context) returning a string
jtulach@816
  4806
            return template(data, context);
jtulach@816
  4807
        } else {
jtulach@816
  4808
            // 3. A string
jtulach@816
  4809
            return template;
jtulach@816
  4810
        }
jtulach@816
  4811
    }
jtulach@816
  4812
jtulach@816
  4813
    ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
jtulach@816
  4814
        options = options || {};
jtulach@816
  4815
        if ((options['templateEngine'] || _templateEngine) == undefined)
jtulach@816
  4816
            throw new Error("Set a template engine before calling renderTemplate");
jtulach@816
  4817
        renderMode = renderMode || "replaceChildren";
jtulach@816
  4818
jtulach@816
  4819
        if (targetNodeOrNodeArray) {
jtulach@816
  4820
            var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
jtulach@816
  4821
jtulach@816
  4822
            var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
jtulach@816
  4823
            var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
jtulach@816
  4824
jtulach@816
  4825
            return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
jtulach@816
  4826
                function () {
jtulach@816
  4827
                    // Ensure we've got a proper binding context to work with
jtulach@816
  4828
                    var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
jtulach@816
  4829
                        ? dataOrBindingContext
jtulach@816
  4830
                        : new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
jtulach@816
  4831
jtulach@816
  4832
                    var templateName = resolveTemplateName(template, bindingContext['$data'], bindingContext),
jtulach@816
  4833
                        renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
jtulach@816
  4834
jtulach@816
  4835
                    if (renderMode == "replaceNode") {
jtulach@816
  4836
                        targetNodeOrNodeArray = renderedNodesArray;
jtulach@816
  4837
                        firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
jtulach@816
  4838
                    }
jtulach@816
  4839
                },
jtulach@816
  4840
                null,
jtulach@816
  4841
                { disposeWhen: whenToDispose, disposeWhenNodeIsRemoved: activelyDisposeWhenNodeIsRemoved }
jtulach@816
  4842
            );
jtulach@816
  4843
        } else {
jtulach@816
  4844
            // 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
jtulach@816
  4845
            return ko.memoization.memoize(function (domNode) {
jtulach@816
  4846
                ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
jtulach@816
  4847
            });
jtulach@816
  4848
        }
jtulach@816
  4849
    };
jtulach@816
  4850
jtulach@816
  4851
    ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
jtulach@816
  4852
        // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
jtulach@816
  4853
        // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
jtulach@816
  4854
        var arrayItemContext;
jtulach@816
  4855
jtulach@816
  4856
        // This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
jtulach@816
  4857
        var executeTemplateForArrayItem = function (arrayValue, index) {
jtulach@816
  4858
            // Support selecting template as a function of the data being rendered
jtulach@816
  4859
            arrayItemContext = parentBindingContext['createChildContext'](arrayValue, options['as'], function(context) {
jtulach@816
  4860
                context['$index'] = index;
jtulach@816
  4861
            });
jtulach@816
  4862
jtulach@816
  4863
            var templateName = resolveTemplateName(template, arrayValue, arrayItemContext);
jtulach@816
  4864
            return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
jtulach@816
  4865
        }
jtulach@816
  4866
jtulach@816
  4867
        // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
jtulach@816
  4868
        var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
jtulach@816
  4869
            activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
jtulach@816
  4870
            if (options['afterRender'])
jtulach@816
  4871
                options['afterRender'](addedNodesArray, arrayValue);
jtulach@816
  4872
        };
jtulach@816
  4873
jtulach@816
  4874
        return ko.dependentObservable(function () {
jtulach@816
  4875
            var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
jtulach@816
  4876
            if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
jtulach@816
  4877
                unwrappedArray = [unwrappedArray];
jtulach@816
  4878
jtulach@816
  4879
            // Filter out any entries marked as destroyed
jtulach@816
  4880
            var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
jtulach@816
  4881
                return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
jtulach@816
  4882
            });
jtulach@816
  4883
jtulach@816
  4884
            // Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
jtulach@816
  4885
            // If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
jtulach@816
  4886
            ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback]);
jtulach@816
  4887
jtulach@816
  4888
        }, null, { disposeWhenNodeIsRemoved: targetNode });
jtulach@816
  4889
    };
jtulach@816
  4890
jtulach@816
  4891
    var templateComputedDomDataKey = ko.utils.domData.nextKey();
jtulach@816
  4892
    function disposeOldComputedAndStoreNewOne(element, newComputed) {
jtulach@816
  4893
        var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
jtulach@816
  4894
        if (oldComputed && (typeof(oldComputed.dispose) == 'function'))
jtulach@816
  4895
            oldComputed.dispose();
jtulach@816
  4896
        ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
jtulach@816
  4897
    }
jtulach@816
  4898
jtulach@816
  4899
    ko.bindingHandlers['template'] = {
jtulach@816
  4900
        'init': function(element, valueAccessor) {
jtulach@816
  4901
            // Support anonymous templates
jtulach@816
  4902
            var bindingValue = ko.utils.unwrapObservable(valueAccessor());
jtulach@816
  4903
            if (typeof bindingValue == "string" || bindingValue['name']) {
jtulach@816
  4904
                // It's a named template - clear the element
jtulach@816
  4905
                ko.virtualElements.emptyNode(element);
jtulach@816
  4906
            } else {
jtulach@816
  4907
                // It's an anonymous template - store the element contents, then clear the element
jtulach@816
  4908
                var templateNodes = ko.virtualElements.childNodes(element),
jtulach@816
  4909
                    container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
jtulach@816
  4910
                new ko.templateSources.anonymousTemplate(element)['nodes'](container);
jtulach@816
  4911
            }
jtulach@816
  4912
            return { 'controlsDescendantBindings': true };
jtulach@816
  4913
        },
jtulach@816
  4914
        'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
jtulach@816
  4915
            var value = valueAccessor(),
jtulach@816
  4916
                dataValue,
jtulach@816
  4917
                options = ko.utils.unwrapObservable(value),
jtulach@816
  4918
                shouldDisplay = true,
jtulach@816
  4919
                templateComputed = null,
jtulach@816
  4920
                templateName;
jtulach@816
  4921
jtulach@816
  4922
            if (typeof options == "string") {
jtulach@816
  4923
                templateName = value;
jtulach@816
  4924
                options = {};
jtulach@816
  4925
            } else {
jtulach@816
  4926
                templateName = options['name'];
jtulach@816
  4927
jtulach@816
  4928
                // Support "if"/"ifnot" conditions
jtulach@816
  4929
                if ('if' in options)
jtulach@816
  4930
                    shouldDisplay = ko.utils.unwrapObservable(options['if']);
jtulach@816
  4931
                if (shouldDisplay && 'ifnot' in options)
jtulach@816
  4932
                    shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);
jtulach@816
  4933
jtulach@816
  4934
                dataValue = ko.utils.unwrapObservable(options['data']);
jtulach@816
  4935
            }
jtulach@816
  4936
jtulach@816
  4937
            if ('foreach' in options) {
jtulach@816
  4938
                // Render once for each data point (treating data set as empty if shouldDisplay==false)
jtulach@816
  4939
                var dataArray = (shouldDisplay && options['foreach']) || [];
jtulach@816
  4940
                templateComputed = ko.renderTemplateForEach(templateName || element, dataArray, options, element, bindingContext);
jtulach@816
  4941
            } else if (!shouldDisplay) {
jtulach@816
  4942
                ko.virtualElements.emptyNode(element);
jtulach@816
  4943
            } else {
jtulach@816
  4944
                // Render once for this single data point (or use the viewModel if no data was provided)
jtulach@816
  4945
                var innerBindingContext = ('data' in options) ?
jtulach@816
  4946
                    bindingContext['createChildContext'](dataValue, options['as']) :  // Given an explitit 'data' value, we create a child binding context for it
jtulach@816
  4947
                    bindingContext;                                                        // Given no explicit 'data' value, we retain the same binding context
jtulach@816
  4948
                templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
jtulach@816
  4949
            }
jtulach@816
  4950
jtulach@816
  4951
            // It only makes sense to have a single template computed per element (otherwise which one should have its output displayed?)
jtulach@816
  4952
            disposeOldComputedAndStoreNewOne(element, templateComputed);
jtulach@816
  4953
        }
jtulach@816
  4954
    };
jtulach@816
  4955
jtulach@816
  4956
    // Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
jtulach@816
  4957
    ko.expressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
jtulach@816
  4958
        var parsedBindingValue = ko.expressionRewriting.parseObjectLiteral(bindingValue);
jtulach@816
  4959
jtulach@816
  4960
        if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
jtulach@816
  4961
            return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting)
jtulach@816
  4962
jtulach@816
  4963
        if (ko.expressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
jtulach@816
  4964
            return null; // Named templates can be rewritten, so return "no error"
jtulach@816
  4965
        return "This template engine does not support anonymous templates nested within its templates";
jtulach@816
  4966
    };
jtulach@816
  4967
jtulach@816
  4968
    ko.virtualElements.allowedBindings['template'] = true;
jtulach@816
  4969
})();
jtulach@816
  4970
jtulach@816
  4971
ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
jtulach@816
  4972
ko.exportSymbol('renderTemplate', ko.renderTemplate);
jtulach@816
  4973
// Go through the items that have been added and deleted and try to find matches between them.
jtulach@816
  4974
ko.utils.findMovesInArrayComparison = function (left, right, limitFailedCompares) {
jtulach@816
  4975
    if (left.length && right.length) {
jtulach@816
  4976
        var failedCompares, l, r, leftItem, rightItem;
jtulach@816
  4977
        for (failedCompares = l = 0; (!limitFailedCompares || failedCompares < limitFailedCompares) && (leftItem = left[l]); ++l) {
jtulach@816
  4978
            for (r = 0; rightItem = right[r]; ++r) {
jtulach@816
  4979
                if (leftItem['value'] === rightItem['value']) {
jtulach@816
  4980
                    leftItem['moved'] = rightItem['index'];
jtulach@816
  4981
                    rightItem['moved'] = leftItem['index'];
jtulach@816
  4982
                    right.splice(r, 1);         // This item is marked as moved; so remove it from right list
jtulach@816
  4983
                    failedCompares = r = 0;     // Reset failed compares count because we're checking for consecutive failures
jtulach@816
  4984
                    break;
jtulach@816
  4985
                }
jtulach@816
  4986
            }
jtulach@816
  4987
            failedCompares += r;
jtulach@816
  4988
        }
jtulach@816
  4989
    }
jtulach@816
  4990
};
jtulach@816
  4991
jtulach@816
  4992
ko.utils.compareArrays = (function () {
jtulach@816
  4993
    var statusNotInOld = 'added', statusNotInNew = 'deleted';
jtulach@816
  4994
jtulach@816
  4995
    // Simple calculation based on Levenshtein distance.
jtulach@816
  4996
    function compareArrays(oldArray, newArray, options) {
jtulach@816
  4997
        // For backward compatibility, if the third arg is actually a bool, interpret
jtulach@816
  4998
        // it as the old parameter 'dontLimitMoves'. Newer code should use { dontLimitMoves: true }.
jtulach@816
  4999
        options = (typeof options === 'boolean') ? { 'dontLimitMoves': options } : (options || {});
jtulach@816
  5000
        oldArray = oldArray || [];
jtulach@816
  5001
        newArray = newArray || [];
jtulach@816
  5002
jtulach@816
  5003
        if (oldArray.length <= newArray.length)
jtulach@816
  5004
            return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, options);
jtulach@816
  5005
        else
jtulach@816
  5006
            return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, options);
jtulach@816
  5007
    }
jtulach@816
  5008
jtulach@816
  5009
    function compareSmallArrayToBigArray(smlArray, bigArray, statusNotInSml, statusNotInBig, options) {
jtulach@816
  5010
        var myMin = Math.min,
jtulach@816
  5011
            myMax = Math.max,
jtulach@816
  5012
            editDistanceMatrix = [],
jtulach@816
  5013
            smlIndex, smlIndexMax = smlArray.length,
jtulach@816
  5014
            bigIndex, bigIndexMax = bigArray.length,
jtulach@816
  5015
            compareRange = (bigIndexMax - smlIndexMax) || 1,
jtulach@816
  5016
            maxDistance = smlIndexMax + bigIndexMax + 1,
jtulach@816
  5017
            thisRow, lastRow,
jtulach@816
  5018
            bigIndexMaxForRow, bigIndexMinForRow;
jtulach@816
  5019
jtulach@816
  5020
        for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
jtulach@816
  5021
            lastRow = thisRow;
jtulach@816
  5022
            editDistanceMatrix.push(thisRow = []);
jtulach@816
  5023
            bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
jtulach@816
  5024
            bigIndexMinForRow = myMax(0, smlIndex - 1);
jtulach@816
  5025
            for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) {
jtulach@816
  5026
                if (!bigIndex)
jtulach@816
  5027
                    thisRow[bigIndex] = smlIndex + 1;
jtulach@816
  5028
                else if (!smlIndex)  // Top row - transform empty array into new array via additions
jtulach@816
  5029
                    thisRow[bigIndex] = bigIndex + 1;
jtulach@816
  5030
                else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1])
jtulach@816
  5031
                    thisRow[bigIndex] = lastRow[bigIndex - 1];                  // copy value (no edit)
jtulach@816
  5032
                else {
jtulach@816
  5033
                    var northDistance = lastRow[bigIndex] || maxDistance;       // not in big (deletion)
jtulach@816
  5034
                    var westDistance = thisRow[bigIndex - 1] || maxDistance;    // not in small (addition)
jtulach@816
  5035
                    thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
jtulach@816
  5036
                }
jtulach@816
  5037
            }
jtulach@816
  5038
        }
jtulach@816
  5039
jtulach@816
  5040
        var editScript = [], meMinusOne, notInSml = [], notInBig = [];
jtulach@816
  5041
        for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
jtulach@816
  5042
            meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
jtulach@816
  5043
            if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
jtulach@816
  5044
                notInSml.push(editScript[editScript.length] = {     // added
jtulach@816
  5045
                    'status': statusNotInSml,
jtulach@816
  5046
                    'value': bigArray[--bigIndex],
jtulach@816
  5047
                    'index': bigIndex });
jtulach@816
  5048
            } else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) {
jtulach@816
  5049
                notInBig.push(editScript[editScript.length] = {     // deleted
jtulach@816
  5050
                    'status': statusNotInBig,
jtulach@816
  5051
                    'value': smlArray[--smlIndex],
jtulach@816
  5052
                    'index': smlIndex });
jtulach@816
  5053
            } else {
jtulach@816
  5054
                --bigIndex;
jtulach@816
  5055
                --smlIndex;
jtulach@816
  5056
                if (!options['sparse']) {
jtulach@816
  5057
                    editScript.push({
jtulach@816
  5058
                        'status': "retained",
jtulach@816
  5059
                        'value': bigArray[bigIndex] });
jtulach@816
  5060
                }
jtulach@816
  5061
            }
jtulach@816
  5062
        }
jtulach@816
  5063
jtulach@816
  5064
        // Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
jtulach@816
  5065
        // smlIndexMax keeps the time complexity of this algorithm linear.
jtulach@816
  5066
        ko.utils.findMovesInArrayComparison(notInSml, notInBig, smlIndexMax * 10);
jtulach@816
  5067
jtulach@816
  5068
        return editScript.reverse();
jtulach@816
  5069
    }
jtulach@816
  5070
jtulach@816
  5071
    return compareArrays;
jtulach@816
  5072
})();
jtulach@816
  5073
jtulach@816
  5074
ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
jtulach@816
  5075
(function () {
jtulach@816
  5076
    // Objective:
jtulach@816
  5077
    // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
jtulach@816
  5078
    //   map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
jtulach@816
  5079
    // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
jtulach@816
  5080
    //   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
jtulach@816
  5081
    //   previously mapped - retain those nodes, and just insert/delete other ones
jtulach@816
  5082
jtulach@816
  5083
    // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
jtulach@816
  5084
    // You can use this, for example, to activate bindings on those nodes.
jtulach@816
  5085
jtulach@816
  5086
    function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
jtulach@816
  5087
        // Map this array value inside a dependentObservable so we re-map when any dependency changes
jtulach@816
  5088
        var mappedNodes = [];
jtulach@816
  5089
        var dependentObservable = ko.dependentObservable(function() {
jtulach@816
  5090
            var newMappedNodes = mapping(valueToMap, index, ko.utils.fixUpContinuousNodeArray(mappedNodes, containerNode)) || [];
jtulach@816
  5091
jtulach@816
  5092
            // On subsequent evaluations, just replace the previously-inserted DOM nodes
jtulach@816
  5093
            if (mappedNodes.length > 0) {
jtulach@816
  5094
                ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
jtulach@816
  5095
                if (callbackAfterAddingNodes)
jtulach@816
  5096
                    ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]);
jtulach@816
  5097
            }
jtulach@816
  5098
jtulach@816
  5099
            // Replace the contents of the mappedNodes array, thereby updating the record
jtulach@816
  5100
            // of which nodes would be deleted if valueToMap was itself later removed
jtulach@816
  5101
            mappedNodes.length = 0;
jtulach@816
  5102
            ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
jtulach@816
  5103
        }, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return !ko.utils.anyDomNodeIsAttachedToDocument(mappedNodes); } });
jtulach@816
  5104
        return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
jtulach@816
  5105
    }
jtulach@816
  5106
jtulach@816
  5107
    var lastMappingResultDomDataKey = ko.utils.domData.nextKey();
jtulach@816
  5108
jtulach@816
  5109
    ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
jtulach@816
  5110
        // Compare the provided array against the previous one
jtulach@816
  5111
        array = array || [];
jtulach@816
  5112
        options = options || {};
jtulach@816
  5113
        var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
jtulach@816
  5114
        var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
jtulach@816
  5115
        var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
jtulach@816
  5116
        var editScript = ko.utils.compareArrays(lastArray, array, options['dontLimitMoves']);
jtulach@816
  5117
jtulach@816
  5118
        // Build the new mapping result
jtulach@816
  5119
        var newMappingResult = [];
jtulach@816
  5120
        var lastMappingResultIndex = 0;
jtulach@816
  5121
        var newMappingResultIndex = 0;
jtulach@816
  5122
jtulach@816
  5123
        var nodesToDelete = [];
jtulach@816
  5124
        var itemsToProcess = [];
jtulach@816
  5125
        var itemsForBeforeRemoveCallbacks = [];
jtulach@816
  5126
        var itemsForMoveCallbacks = [];
jtulach@816
  5127
        var itemsForAfterAddCallbacks = [];
jtulach@816
  5128
        var mapData;
jtulach@816
  5129
jtulach@816
  5130
        function itemMovedOrRetained(editScriptIndex, oldPosition) {
jtulach@816
  5131
            mapData = lastMappingResult[oldPosition];
jtulach@816
  5132
            if (newMappingResultIndex !== oldPosition)
jtulach@816
  5133
                itemsForMoveCallbacks[editScriptIndex] = mapData;
jtulach@816
  5134
            // Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
jtulach@816
  5135
            mapData.indexObservable(newMappingResultIndex++);
jtulach@816
  5136
            ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
jtulach@816
  5137
            newMappingResult.push(mapData);
jtulach@816
  5138
            itemsToProcess.push(mapData);
jtulach@816
  5139
        }
jtulach@816
  5140
jtulach@816
  5141
        function callCallback(callback, items) {
jtulach@816
  5142
            if (callback) {
jtulach@816
  5143
                for (var i = 0, n = items.length; i < n; i++) {
jtulach@816
  5144
                    if (items[i]) {
jtulach@816
  5145
                        ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
jtulach@816
  5146
                            callback(node, i, items[i].arrayEntry);
jtulach@816
  5147
                        });
jtulach@816
  5148
                    }
jtulach@816
  5149
                }
jtulach@816
  5150
            }
jtulach@816
  5151
        }
jtulach@816
  5152
jtulach@816
  5153
        for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
jtulach@816
  5154
            movedIndex = editScriptItem['moved'];
jtulach@816
  5155
            switch (editScriptItem['status']) {
jtulach@816
  5156
                case "deleted":
jtulach@816
  5157
                    if (movedIndex === undefined) {
jtulach@816
  5158
                        mapData = lastMappingResult[lastMappingResultIndex];
jtulach@816
  5159
jtulach@816
  5160
                        // Stop tracking changes to the mapping for these nodes
jtulach@816
  5161
                        if (mapData.dependentObservable)
jtulach@816
  5162
                            mapData.dependentObservable.dispose();
jtulach@816
  5163
jtulach@816
  5164
                        // Queue these nodes for later removal
jtulach@816
  5165
                        nodesToDelete.push.apply(nodesToDelete, ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode));
jtulach@816
  5166
                        if (options['beforeRemove']) {
jtulach@816
  5167
                            itemsForBeforeRemoveCallbacks[i] = mapData;
jtulach@816
  5168
                            itemsToProcess.push(mapData);
jtulach@816
  5169
                        }
jtulach@816
  5170
                    }
jtulach@816
  5171
                    lastMappingResultIndex++;
jtulach@816
  5172
                    break;
jtulach@816
  5173
jtulach@816
  5174
                case "retained":
jtulach@816
  5175
                    itemMovedOrRetained(i, lastMappingResultIndex++);
jtulach@816
  5176
                    break;
jtulach@816
  5177
jtulach@816
  5178
                case "added":
jtulach@816
  5179
                    if (movedIndex !== undefined) {
jtulach@816
  5180
                        itemMovedOrRetained(i, movedIndex);
jtulach@816
  5181
                    } else {
jtulach@816
  5182
                        mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
jtulach@816
  5183
                        newMappingResult.push(mapData);
jtulach@816
  5184
                        itemsToProcess.push(mapData);
jtulach@816
  5185
                        if (!isFirstExecution)
jtulach@816
  5186
                            itemsForAfterAddCallbacks[i] = mapData;
jtulach@816
  5187
                    }
jtulach@816
  5188
                    break;
jtulach@816
  5189
            }
jtulach@816
  5190
        }
jtulach@816
  5191
jtulach@816
  5192
        // Call beforeMove first before any changes have been made to the DOM
jtulach@816
  5193
        callCallback(options['beforeMove'], itemsForMoveCallbacks);
jtulach@816
  5194
jtulach@816
  5195
        // Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
jtulach@816
  5196
        ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
jtulach@816
  5197
jtulach@816
  5198
        // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
jtulach@816
  5199
        for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
jtulach@816
  5200
            // Get nodes for newly added items
jtulach@816
  5201
            if (!mapData.mappedNodes)
jtulach@816
  5202
                ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
jtulach@816
  5203
jtulach@816
  5204
            // Put nodes in the right place if they aren't there already
jtulach@816
  5205
            for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
jtulach@816
  5206
                if (node !== nextNode)
jtulach@816
  5207
                    ko.virtualElements.insertAfter(domNode, node, lastNode);
jtulach@816
  5208
            }
jtulach@816
  5209
jtulach@816
  5210
            // Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
jtulach@816
  5211
            if (!mapData.initialized && callbackAfterAddingNodes) {
jtulach@816
  5212
                callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
jtulach@816
  5213
                mapData.initialized = true;
jtulach@816
  5214
            }
jtulach@816
  5215
        }
jtulach@816
  5216
jtulach@816
  5217
        // If there's a beforeRemove callback, call it after reordering.
jtulach@816
  5218
        // Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
jtulach@816
  5219
        // some sort of animation, which is why we first reorder the nodes that will be removed. If the
jtulach@816
  5220
        // callback instead removes the nodes right away, it would be more efficient to skip reordering them.
jtulach@816
  5221
        // Perhaps we'll make that change in the future if this scenario becomes more common.
jtulach@816
  5222
        callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
jtulach@816
  5223
jtulach@816
  5224
        // Finally call afterMove and afterAdd callbacks
jtulach@816
  5225
        callCallback(options['afterMove'], itemsForMoveCallbacks);
jtulach@816
  5226
        callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
jtulach@816
  5227
jtulach@816
  5228
        // Store a copy of the array items we just considered so we can difference it next time
jtulach@816
  5229
        ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
jtulach@816
  5230
    }
jtulach@816
  5231
})();
jtulach@816
  5232
jtulach@816
  5233
ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
jtulach@816
  5234
ko.nativeTemplateEngine = function () {
jtulach@816
  5235
    this['allowTemplateRewriting'] = false;
jtulach@816
  5236
}
jtulach@816
  5237
jtulach@816
  5238
ko.nativeTemplateEngine.prototype = new ko.templateEngine();
jtulach@816
  5239
ko.nativeTemplateEngine.prototype.constructor = ko.nativeTemplateEngine;
jtulach@816
  5240
ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
jtulach@816
  5241
    var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly
jtulach@816
  5242
        templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null,
jtulach@816
  5243
        templateNodes = templateNodesFunc ? templateSource['nodes']() : null;
jtulach@816
  5244
jtulach@816
  5245
    if (templateNodes) {
jtulach@816
  5246
        return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes);
jtulach@816
  5247
    } else {
jtulach@816
  5248
        var templateText = templateSource['text']();
jtulach@816
  5249
        return ko.utils.parseHtmlFragment(templateText);
jtulach@816
  5250
    }
jtulach@816
  5251
};
jtulach@816
  5252
jtulach@816
  5253
ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine();
jtulach@816
  5254
ko.setTemplateEngine(ko.nativeTemplateEngine.instance);
jtulach@816
  5255
jtulach@816
  5256
ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
jtulach@816
  5257
(function() {
jtulach@816
  5258
    ko.jqueryTmplTemplateEngine = function () {
jtulach@816
  5259
        // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
jtulach@816
  5260
        // doesn't expose a version number, so we have to infer it.
jtulach@816
  5261
        // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
jtulach@816
  5262
        // which KO internally refers to as version "2", so older versions are no longer detected.
jtulach@816
  5263
        var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
jtulach@816
  5264
            if (!jQueryInstance || !(jQueryInstance['tmpl']))
jtulach@816
  5265
                return 0;
jtulach@816
  5266
            // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
jtulach@816
  5267
            try {
jtulach@816
  5268
                if (jQueryInstance['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
jtulach@816
  5269
                    // Since 1.0.0pre, custom tags should append markup to an array called "__"
jtulach@816
  5270
                    return 2; // Final version of jquery.tmpl
jtulach@816
  5271
                }
jtulach@816
  5272
            } catch(ex) { /* Apparently not the version we were looking for */ }
jtulach@816
  5273
jtulach@816
  5274
            return 1; // Any older version that we don't support
jtulach@816
  5275
        })();
jtulach@816
  5276
jtulach@816
  5277
        function ensureHasReferencedJQueryTemplates() {
jtulach@816
  5278
            if (jQueryTmplVersion < 2)
jtulach@816
  5279
                throw new Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");
jtulach@816
  5280
        }
jtulach@816
  5281
jtulach@816
  5282
        function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
jtulach@816
  5283
            return jQueryInstance['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
jtulach@816
  5284
        }
jtulach@816
  5285
jtulach@816
  5286
        this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
jtulach@816
  5287
            options = options || {};
jtulach@816
  5288
            ensureHasReferencedJQueryTemplates();
jtulach@816
  5289
jtulach@816
  5290
            // Ensure we have stored a precompiled version of this template (don't want to reparse on every render)
jtulach@816
  5291
            var precompiled = templateSource['data']('precompiled');
jtulach@816
  5292
            if (!precompiled) {
jtulach@816
  5293
                var templateText = templateSource['text']() || "";
jtulach@816
  5294
                // Wrap in "with($whatever.koBindingContext) { ... }"
jtulach@816
  5295
                templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
jtulach@816
  5296
jtulach@816
  5297
                precompiled = jQueryInstance['template'](null, templateText);
jtulach@816
  5298
                templateSource['data']('precompiled', precompiled);
jtulach@816
  5299
            }
jtulach@816
  5300
jtulach@816
  5301
            var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
jtulach@816
  5302
            var jQueryTemplateOptions = jQueryInstance['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
jtulach@816
  5303
jtulach@816
  5304
            var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
jtulach@816
  5305
            resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
jtulach@816
  5306
jtulach@816
  5307
            jQueryInstance['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
jtulach@816
  5308
            return resultNodes;
jtulach@816
  5309
        };
jtulach@816
  5310
jtulach@816
  5311
        this['createJavaScriptEvaluatorBlock'] = function(script) {
jtulach@816
  5312
            return "{{ko_code ((function() { return " + script + " })()) }}";
jtulach@816
  5313
        };
jtulach@816
  5314
jtulach@816
  5315
        this['addTemplate'] = function(templateName, templateMarkup) {
jtulach@816
  5316
            document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "<" + "/script>");
jtulach@816
  5317
        };
jtulach@816
  5318
jtulach@816
  5319
        if (jQueryTmplVersion > 0) {
jtulach@816
  5320
            jQueryInstance['tmpl']['tag']['ko_code'] = {
jtulach@816
  5321
                open: "__.push($1 || '');"
jtulach@816
  5322
            };
jtulach@816
  5323
            jQueryInstance['tmpl']['tag']['ko_with'] = {
jtulach@816
  5324
                open: "with($1) {",
jtulach@816
  5325
                close: "} "
jtulach@816
  5326
            };
jtulach@816
  5327
        }
jtulach@816
  5328
    };
jtulach@816
  5329
jtulach@816
  5330
    ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
jtulach@816
  5331
    ko.jqueryTmplTemplateEngine.prototype.constructor = ko.jqueryTmplTemplateEngine;
jtulach@816
  5332
jtulach@816
  5333
    // Use this one by default *only if jquery.tmpl is referenced*
jtulach@816
  5334
    var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
jtulach@816
  5335
    if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
jtulach@816
  5336
        ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
jtulach@816
  5337
jtulach@816
  5338
    ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
jtulach@816
  5339
})();
jtulach@816
  5340
}));
jtulach@816
  5341
}());
jtulach@816
  5342
})();