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