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