knockoutjs-rails 1.01

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Jacob Swanner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # knockoutjs-rails
2
+
3
+ knockoutjs-rails wraps the [Knockout.js](http://knockoutjs.com/) library in a rails engine for simple
4
+ use with the asset pipeline provided by Rails > v. 3.1. The gem includes the development (non-minified)
5
+ source for ease of exploration. The asset pipeline will minify in production.
6
+
7
+ Knockout is a JavaScript library that helps you to create rich, responsive display and editor user
8
+ interfaces with a clean underlying data model. Please see the
9
+ [documentation](http://knockoutjs.com/documentation/introduction.html) for details.
10
+
11
+ ## Usage
12
+
13
+ Add the following to your gemfile:
14
+
15
+ gem 'knockoutjs-rails'
16
+
17
+ Add the following directive to your Javascript manifest file (application.js):
18
+
19
+ //= require knockout
20
+
21
+ ## Versioning
22
+
23
+ knockoutjs-rails 1.01 == Knockout.js 1.01
24
+
25
+ Every attempt is made to mirror the currently shipping Knockout.js version number wherever possible.
26
+ The major and minor version numbers will always represent the Knockout.js version, but the patch level
27
+ may differ should a fix to gem need to be pushed before Knockout.js ships an update to the library.
28
+
29
+ When the versions differ, it will be noted in the README.
@@ -0,0 +1,8 @@
1
+ require "knockoutjs-rails/version"
2
+
3
+ module Knockoutjs
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Knockoutjs
2
+ module Rails
3
+ VERSION = "1.01"
4
+ end
5
+ end
@@ -0,0 +1,1222 @@
1
+ // Knockout JavaScript library v1.01
2
+ // (c) 2010 Steven Sanderson - http://knockoutjs.com/
3
+ // License: Ms-Pl (http://www.opensource.org/licenses/ms-pl.html)
4
+
5
+ var ko = window.ko = {};
6
+ /// <reference path="namespace.js" />
7
+
8
+ ko.utils = new (function () {
9
+ var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
10
+
11
+ return {
12
+ arrayForEach: function (array, action) {
13
+ for (var i = 0, j = array.length; i < j; i++)
14
+ action(array[i]);
15
+ },
16
+
17
+ arrayIndexOf: function (array, item) {
18
+ if (typeof array.indexOf == "function")
19
+ return array.indexOf(item);
20
+ for (var i = 0, j = array.length; i < j; i++)
21
+ if (array[i] == item)
22
+ return i;
23
+ return -1;
24
+ },
25
+
26
+ arrayFirst: function (array, predicate, predicateOwner) {
27
+ for (var i = 0, j = array.length; i < j; i++)
28
+ if (predicate.call(predicateOwner, array[i]))
29
+ return array[i];
30
+ return null;
31
+ },
32
+
33
+ arrayRemoveItem: function (array, itemToRemove) {
34
+ var index = ko.utils.arrayIndexOf(array, itemToRemove);
35
+ if (index >= 0)
36
+ array.splice(index, 1);
37
+ },
38
+
39
+ arrayGetDistinctValues: function (array) {
40
+ array = array || [];
41
+ var result = [];
42
+ for (var i = 0, j = array.length; i < j; i++) {
43
+ if (ko.utils.arrayIndexOf(result, array[i]) < 0)
44
+ result.push(array[i]);
45
+ }
46
+ return result;
47
+ },
48
+
49
+ arrayMap: function (array, mapping) {
50
+ array = array || [];
51
+ var result = [];
52
+ for (var i = 0, j = array.length; i < j; i++)
53
+ result.push(mapping(array[i]));
54
+ return result;
55
+ },
56
+
57
+ arrayFilter: function (array, predicate) {
58
+ array = array || [];
59
+ var result = [];
60
+ for (var i = 0, j = array.length; i < j; i++)
61
+ if (predicate(array[i]))
62
+ result.push(array[i]);
63
+ return result;
64
+ },
65
+
66
+ setDomNodeChildren: function (domNode, childNodes) {
67
+ while (domNode.firstChild) {
68
+ ko.utils.domData.cleanNodeAndDescendants(domNode.firstChild);
69
+ domNode.removeChild(domNode.firstChild);
70
+ }
71
+ if (childNodes) {
72
+ ko.utils.arrayForEach(childNodes, function (childNode) {
73
+ domNode.appendChild(childNode);
74
+ });
75
+ }
76
+ },
77
+
78
+ replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
79
+ var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
80
+ if (nodesToReplaceArray.length > 0) {
81
+ var insertionPoint = nodesToReplaceArray[0];
82
+ var parent = insertionPoint.parentNode;
83
+ for (var i = 0, j = newNodesArray.length; i < j; i++)
84
+ parent.insertBefore(newNodesArray[i], insertionPoint);
85
+ for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
86
+ ko.utils.domData.cleanNodeAndDescendants(nodesToReplaceArray[i]);
87
+ parent.removeChild(nodesToReplaceArray[i]);
88
+ }
89
+ }
90
+ },
91
+
92
+ getElementsHavingAttribute: function (rootNode, attributeName) {
93
+ if ((!rootNode) || (rootNode.nodeType != 1)) return [];
94
+ var results = [];
95
+ if (rootNode.getAttribute(attributeName) !== null)
96
+ results.push(rootNode);
97
+ var descendants = rootNode.getElementsByTagName("*");
98
+ for (var i = 0, j = descendants.length; i < j; i++)
99
+ if (descendants[i].getAttribute(attributeName) !== null)
100
+ results.push(descendants[i]);
101
+ return results;
102
+ },
103
+
104
+ stringTrim: function (string) {
105
+ return (string || "").replace(stringTrimRegex, "");
106
+ },
107
+
108
+ stringTokenize: function (string, delimiter) {
109
+ var result = [];
110
+ var tokens = (string || "").split(delimiter);
111
+ for (var i = 0, j = tokens.length; i < j; i++) {
112
+ var trimmed = ko.utils.stringTrim(tokens[i]);
113
+ if (trimmed !== "")
114
+ result.push(trimmed);
115
+ }
116
+ return result;
117
+ },
118
+
119
+ evalWithinScope: function (expression, scope) {
120
+ if (scope === undefined)
121
+ return (new Function("return " + expression))();
122
+ with (scope) { return eval("(" + expression + ")"); }
123
+ },
124
+
125
+ domNodeIsContainedBy: function (node, containedByNode) {
126
+ if (containedByNode.compareDocumentPosition)
127
+ return (containedByNode.compareDocumentPosition(node) & 16) == 16;
128
+ while (node != null) {
129
+ if (node == containedByNode)
130
+ return true;
131
+ node = node.parentNode;
132
+ }
133
+ return false;
134
+ },
135
+
136
+ domNodeIsAttachedToDocument: function (node) {
137
+ return ko.utils.domNodeIsContainedBy(node, document);
138
+ },
139
+
140
+ registerEventHandler: function (element, eventType, handler) {
141
+ if (typeof jQuery != "undefined")
142
+ jQuery(element).bind(eventType, handler);
143
+ else if (typeof element.addEventListener == "function")
144
+ element.addEventListener(eventType, handler, false);
145
+ else if (typeof element.attachEvent != "undefined")
146
+ element.attachEvent("on" + eventType, function (event) {
147
+ handler.call(element, event);
148
+ });
149
+ else
150
+ throw new Error("Browser doesn't support addEventListener or attachEvent");
151
+ },
152
+
153
+ triggerEvent: function (element, eventType) {
154
+ if (!(element && element.nodeType))
155
+ throw new Error("element must be a DOM node when calling triggerEvent");
156
+
157
+ if (typeof element.fireEvent != "undefined")
158
+ element.fireEvent("on" + eventType);
159
+ else if (typeof document.createEvent == "function") {
160
+ if (typeof element.dispatchEvent == "function") {
161
+ var eventCategory = (eventType == "click" ? "MouseEvents" : "HTMLEvents"); // Might need to account for other event names at some point
162
+ var event = document.createEvent(eventCategory);
163
+ event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
164
+ element.dispatchEvent(event);
165
+ }
166
+ else
167
+ throw new Error("The supplied element doesn't support dispatchEvent");
168
+ }
169
+ else
170
+ throw new Error("Browser doesn't support triggering events");
171
+ },
172
+
173
+ unwrapObservable: function (value) {
174
+ return ko.isObservable(value) ? value() : value;
175
+ },
176
+
177
+ domNodeHasCssClass: function (node, className) {
178
+ var currentClassNames = (node.className || "").split(/\s+/);
179
+ return ko.utils.arrayIndexOf(currentClassNames, className) >= 0;
180
+ },
181
+
182
+ toggleDomNodeCssClass: function (node, className, shouldHaveClass) {
183
+ var hasClass = ko.utils.domNodeHasCssClass(node, className);
184
+ if (shouldHaveClass && !hasClass) {
185
+ node.className = (node.className || "") + " " + className;
186
+ } else if (hasClass && !shouldHaveClass) {
187
+ var currentClassNames = (node.className || "").split(/\s+/);
188
+ var newClassName = "";
189
+ for (var i = 0; i < currentClassNames.length; i++)
190
+ if (currentClassNames[i] != className)
191
+ newClassName += currentClassNames[i] + " ";
192
+ node.className = ko.utils.stringTrim(newClassName);
193
+ }
194
+ },
195
+
196
+ range: function (min, max) {
197
+ min = ko.utils.unwrapObservable(min);
198
+ max = ko.utils.unwrapObservable(max);
199
+ var result = [];
200
+ for (var i = min; i <= max; i++)
201
+ result.push(i);
202
+ return result;
203
+ },
204
+
205
+ stringifyJson: function (data) {
206
+ if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined"))
207
+ 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");
208
+ return JSON.stringify(data);
209
+ },
210
+
211
+ postJson: function (url, data) {
212
+ data = ko.utils.unwrapObservable(data);
213
+ var form = document.createElement("FORM");
214
+ form.style.display = "none";
215
+ form.action = url;
216
+ form.method = "post";
217
+ for (var key in data) {
218
+ var input = document.createElement("INPUT");
219
+ input.name = key;
220
+ input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
221
+ form.appendChild(input);
222
+ }
223
+ document.body.appendChild(form);
224
+ form.submit();
225
+ setTimeout(function () { form.parentNode.removeChild(form); }, 0);
226
+ },
227
+
228
+ domData: {
229
+ uniqueId: 0,
230
+ dataStoreKeyExpandoPropertyName: "__ko__" + (new Date).getTime(),
231
+ dataStore: {},
232
+ get: function (node, key) {
233
+ var allDataForNode = ko.utils.domData.getAll(node, false);
234
+ return allDataForNode === undefined ? undefined : allDataForNode[key];
235
+ },
236
+ set: function (node, key, value) {
237
+ var allDataForNode = ko.utils.domData.getAll(node, true);
238
+ allDataForNode[key] = value;
239
+ },
240
+ getAll: function (node, createIfNotFound) {
241
+ var dataStoreKey = node[ko.utils.domData.dataStoreKeyExpandoPropertyName];
242
+ if (!dataStoreKey) {
243
+ if (!createIfNotFound)
244
+ return undefined;
245
+ dataStoreKey = node[ko.utils.domData.dataStoreKeyExpandoPropertyName] = "ko" + ko.utils.domData.uniqueId++;
246
+ ko.utils.domData[dataStoreKey] = {};
247
+ }
248
+ return ko.utils.domData[dataStoreKey];
249
+ },
250
+ cleanNode: function (node) {
251
+ var dataStoreKey = node[ko.utils.domData.dataStoreKeyExpandoPropertyName];
252
+ if (dataStoreKey) {
253
+ delete ko.utils.domData[dataStoreKey];
254
+ node[ko.utils.domData.dataStoreKeyExpandoPropertyName] = null;
255
+ }
256
+ },
257
+ cleanNodeAndDescendants: function (node) {
258
+ if ((node.nodeType != 1) && (node.nodeType != 9))
259
+ return;
260
+ ko.utils.domData.cleanNode(node);
261
+ var descendants = node.getElementsByTagName("*");
262
+ for (var i = 0, j = descendants.length; i < j; i++)
263
+ ko.utils.domData.cleanNode(descendants[i]);
264
+ }
265
+ }
266
+ }
267
+ })();
268
+
269
+ if (!Function.prototype.bind) {
270
+ // 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)
271
+ // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
272
+ Function.prototype.bind = function (object) {
273
+ var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift();
274
+ return function () {
275
+ return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
276
+ };
277
+ };
278
+ }/// <reference path="utils.js" />
279
+
280
+ ko.memoization = (function () {
281
+ var memos = {};
282
+
283
+ function randomMax8HexChars() {
284
+ return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
285
+ }
286
+ function generateRandomId() {
287
+ return randomMax8HexChars() + randomMax8HexChars();
288
+ }
289
+ function findMemoNodes(rootNode, appendToArray) {
290
+ if (!rootNode)
291
+ return;
292
+ if (rootNode.nodeType == 8) {
293
+ var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
294
+ if (memoId != null)
295
+ appendToArray.push({ domNode: rootNode, memoId: memoId });
296
+ } else if (rootNode.nodeType == 1) {
297
+ for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)
298
+ findMemoNodes(childNodes[i], appendToArray);
299
+ }
300
+ }
301
+
302
+ return {
303
+ memoize: function (callback) {
304
+ if (typeof callback != "function")
305
+ throw new Error("You can only pass a function to ko.memoization.memoize()");
306
+ var memoId = generateRandomId();
307
+ memos[memoId] = callback;
308
+ return "<!--[ko_memo:" + memoId + "]-->";
309
+ },
310
+
311
+ unmemoize: function (memoId, callbackParams) {
312
+ var callback = memos[memoId];
313
+ if (callback === undefined)
314
+ throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");
315
+ try {
316
+ callback.apply(null, callbackParams || []);
317
+ return true;
318
+ }
319
+ finally { delete memos[memoId]; }
320
+ },
321
+
322
+ unmemoizeDomNodeAndDescendants: function (domNode) {
323
+ var memos = [];
324
+ findMemoNodes(domNode, memos);
325
+ for (var i = 0, j = memos.length; i < j; i++) {
326
+ var node = memos[i].domNode;
327
+ ko.memoization.unmemoize(memos[i].memoId, [node]);
328
+ node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
329
+ if (node.parentNode)
330
+ 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)
331
+ }
332
+ },
333
+
334
+ parseMemoText: function (memoText) {
335
+ var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
336
+ return match ? match[1] : null;
337
+ }
338
+ };
339
+ })();/// <reference path="../utils.js" />
340
+
341
+ ko.subscription = function (callback, disposeCallback) {
342
+ this.callback = callback;
343
+ this.dispose = disposeCallback;
344
+ };
345
+
346
+ ko.subscribable = function () {
347
+ var _subscriptions = [];
348
+
349
+ this.subscribe = function (callback, callbackTarget) {
350
+ var boundCallback = callbackTarget ? function () { callback.call(callbackTarget) } : callback;
351
+
352
+ var subscription = new ko.subscription(boundCallback, function () {
353
+ ko.utils.arrayRemoveItem(_subscriptions, subscription);
354
+ });
355
+ _subscriptions.push(subscription);
356
+ return subscription;
357
+ };
358
+
359
+ this.notifySubscribers = function (valueToNotify) {
360
+ ko.utils.arrayForEach(_subscriptions.slice(0), function (subscription) {
361
+ if (subscription)
362
+ subscription.callback(valueToNotify);
363
+ });
364
+ };
365
+
366
+ this.getSubscriptionsCount = function () {
367
+ return _subscriptions.length;
368
+ };
369
+ }
370
+
371
+ ko.isSubscribable = function (instance) {
372
+ return typeof instance.subscribe == "function" && typeof instance.notifySubscribers == "function";
373
+ };/// <reference path="subscribable.js" />
374
+
375
+ ko.dependencyDetection = (function () {
376
+ var _detectedDependencies = [];
377
+
378
+ return {
379
+ begin: function () {
380
+ _detectedDependencies.push([]);
381
+ },
382
+
383
+ end: function () {
384
+ return _detectedDependencies.pop();
385
+ },
386
+
387
+ registerDependency: function (subscribable) {
388
+ if (!ko.isSubscribable(subscribable))
389
+ throw "Only subscribable things can act as dependencies";
390
+ if (_detectedDependencies.length > 0) {
391
+ _detectedDependencies[_detectedDependencies.length - 1].push(subscribable);
392
+ }
393
+ }
394
+ };
395
+ })();/// <reference path="dependencyDetection.js" />
396
+
397
+ ko.observable = function (initialValue) {
398
+ var _latestValue = initialValue;
399
+
400
+ function observable(newValue) {
401
+ if (arguments.length > 0) {
402
+ _latestValue = newValue;
403
+ observable.notifySubscribers(_latestValue);
404
+ }
405
+ else // The caller only needs to be notified of changes if they did a "read" operation
406
+ ko.dependencyDetection.registerDependency(observable);
407
+
408
+ return _latestValue;
409
+ }
410
+ observable.__ko_proto__ = ko.observable;
411
+ observable.valueHasMutated = function () { observable.notifySubscribers(_latestValue); }
412
+
413
+ ko.subscribable.call(observable);
414
+ return observable;
415
+ }
416
+ ko.isObservable = function (instance) {
417
+ if ((instance === null) || (instance === undefined) || (instance.__ko_proto__ === undefined)) return false;
418
+ if (instance.__ko_proto__ === ko.observable) return true;
419
+ return ko.isObservable(instance.__ko_proto__); // Walk the prototype chain
420
+ }
421
+ ko.isWriteableObservable = function (instance) {
422
+ return (typeof instance == "function") && instance.__ko_proto__ === ko.observable;
423
+ }/// <reference path="observable.js" />
424
+
425
+ ko.observableArray = function (initialValues) {
426
+ var result = new ko.observable(initialValues);
427
+
428
+ ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
429
+ result[methodName] = function () {
430
+ var underlyingArray = result();
431
+ var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
432
+ result.valueHasMutated();
433
+ return methodCallResult;
434
+ };
435
+ });
436
+
437
+ ko.utils.arrayForEach(["slice"], function (methodName) {
438
+ result[methodName] = function () {
439
+ var underlyingArray = result();
440
+ return underlyingArray[methodName].apply(underlyingArray, arguments);
441
+ };
442
+ });
443
+
444
+ result.remove = function (valueOrPredicate) {
445
+ var underlyingArray = result();
446
+ var remainingValues = [];
447
+ var removedValues = [];
448
+ var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
449
+ for (var i = 0, j = underlyingArray.length; i < j; i++) {
450
+ var value = underlyingArray[i];
451
+ if (!predicate(value))
452
+ remainingValues.push(value);
453
+ else
454
+ removedValues.push(value);
455
+ }
456
+ result(remainingValues);
457
+ return removedValues;
458
+ };
459
+
460
+ result.removeAll = function (arrayOfValues) {
461
+ if (!arrayOfValues)
462
+ return [];
463
+ return result.remove(function (value) {
464
+ return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
465
+ });
466
+ };
467
+
468
+ result.indexOf = function (item) {
469
+ var underlyingArray = result();
470
+ return ko.utils.arrayIndexOf(underlyingArray, item);
471
+ };
472
+
473
+ return result;
474
+ }/// <reference path="observable.js" />
475
+
476
+ ko.dependentObservable = function (evaluatorFunction, evaluatorFunctionTarget, options) {
477
+ if (typeof evaluatorFunction != "function")
478
+ throw "Pass a function that returns the value of the dependentObservable";
479
+
480
+ var _subscriptionsToDependencies = [];
481
+ function disposeAllSubscriptionsToDependencies() {
482
+ ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) {
483
+ subscription.dispose();
484
+ });
485
+ _subscriptionsToDependencies = [];
486
+ }
487
+
488
+ function replaceSubscriptionsToDependencies(newDependencies) {
489
+ disposeAllSubscriptionsToDependencies();
490
+ ko.utils.arrayForEach(newDependencies, function (dependency) {
491
+ _subscriptionsToDependencies.push(dependency.subscribe(evaluate));
492
+ });
493
+ };
494
+
495
+ var _latestValue, _isFirstEvaluation = true;
496
+ function evaluate() {
497
+ if ((!_isFirstEvaluation) && options && typeof options.disposeWhen == "function") {
498
+ if (options.disposeWhen()) {
499
+ dependentObservable.dispose();
500
+ return;
501
+ }
502
+ }
503
+
504
+ try {
505
+ ko.dependencyDetection.begin();
506
+ _latestValue = evaluatorFunctionTarget ? evaluatorFunction.call(evaluatorFunctionTarget) : evaluatorFunction();
507
+ } catch (ex) {
508
+ throw ex;
509
+ } finally {
510
+ var distinctDependencies = ko.utils.arrayGetDistinctValues(ko.dependencyDetection.end());
511
+ replaceSubscriptionsToDependencies(distinctDependencies);
512
+ }
513
+
514
+ dependentObservable.notifySubscribers(_latestValue);
515
+ _isFirstEvaluation = false;
516
+ }
517
+
518
+ function dependentObservable() {
519
+ if (arguments.length > 0)
520
+ throw "Cannot write a value to a dependentObservable. Do not pass any parameters to it";
521
+
522
+ ko.dependencyDetection.registerDependency(dependentObservable);
523
+ return _latestValue;
524
+ }
525
+ dependentObservable.__ko_proto__ = ko.dependentObservable;
526
+ dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; }
527
+ dependentObservable.dispose = function () {
528
+ disposeAllSubscriptionsToDependencies();
529
+ };
530
+
531
+ ko.subscribable.call(dependentObservable);
532
+ evaluate();
533
+ return dependentObservable;
534
+ };
535
+ ko.dependentObservable.__ko_proto__ = ko.observable;/// <reference path="../utils.js" />
536
+
537
+ ko.jsonExpressionRewriting = (function () {
538
+ var restoreCapturedTokensRegex = /\[ko_token_(\d+)\]/g;
539
+ var javaScriptAssignmentTarget = /^[\_$a-z][\_$a-z]*(\[.*?\])*(\.[\_$a-z][\_$a-z]*(\[.*?\])*)*$/i;
540
+ var javaScriptReservedWords = ["true", "false"];
541
+
542
+ function restoreTokens(string, tokens) {
543
+ return string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) {
544
+ return tokens[tokenIndex];
545
+ });
546
+ }
547
+
548
+ function isWriteableValue(expression) {
549
+ if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0)
550
+ return false;
551
+ return expression.match(javaScriptAssignmentTarget) !== null;
552
+ }
553
+
554
+ return {
555
+ parseJson: function (jsonString) {
556
+ jsonString = ko.utils.stringTrim(jsonString);
557
+ if (jsonString.length < 3)
558
+ return {};
559
+
560
+ // We're going to split on commas, so first extract any blocks that may contain commas other than those at the top level
561
+ var tokens = [];
562
+ var tokenStart = null, tokenEndChar;
563
+ for (var position = jsonString.charAt(0) == "{" ? 1 : 0; position < jsonString.length; position++) {
564
+ var c = jsonString.charAt(position);
565
+ if (tokenStart === null) {
566
+ switch (c) {
567
+ case '"':
568
+ case "'":
569
+ case "/":
570
+ tokenStart = position;
571
+ tokenEndChar = c;
572
+ break;
573
+ case "{":
574
+ tokenStart = position;
575
+ tokenEndChar = "}";
576
+ break;
577
+ case "[":
578
+ tokenStart = position;
579
+ tokenEndChar = "]";
580
+ break;
581
+ }
582
+ } else if (c == tokenEndChar) {
583
+ var token = jsonString.substring(tokenStart, position + 1);
584
+ tokens.push(token);
585
+ var replacement = "[ko_token_" + (tokens.length - 1) + "]";
586
+ jsonString = jsonString.substring(0, tokenStart) + replacement + jsonString.substring(position + 1);
587
+ position -= (token.length - replacement.length);
588
+ tokenStart = null;
589
+ }
590
+ }
591
+
592
+ // Now we can safely split on commas to get the key/value pairs
593
+ var result = {};
594
+ var keyValuePairs = jsonString.split(",");
595
+ for (var i = 0, j = keyValuePairs.length; i < j; i++) {
596
+ var pair = keyValuePairs[i];
597
+ var colonPos = pair.indexOf(":");
598
+ if ((colonPos > 0) && (colonPos < pair.length - 1)) {
599
+ var key = ko.utils.stringTrim(pair.substring(0, colonPos));
600
+ var value = ko.utils.stringTrim(pair.substring(colonPos + 1));
601
+ if (key.charAt(0) == "{")
602
+ key = key.substring(1);
603
+ if (value.charAt(value.length - 1) == "}")
604
+ value = value.substring(0, value.length - 1);
605
+ key = ko.utils.stringTrim(restoreTokens(key, tokens));
606
+ value = ko.utils.stringTrim(restoreTokens(value, tokens));
607
+ result[key] = value;
608
+ }
609
+ }
610
+ return result;
611
+ },
612
+
613
+ insertPropertyAccessorsIntoJson: function (jsonString) {
614
+ var parsed = ko.jsonExpressionRewriting.parseJson(jsonString);
615
+ var propertyAccessorTokens = [];
616
+ for (var key in parsed) {
617
+ var value = parsed[key];
618
+ if (isWriteableValue(value)) {
619
+ if (propertyAccessorTokens.length > 0)
620
+ propertyAccessorTokens.push(", ");
621
+ propertyAccessorTokens.push(key + " : function(__ko_value) { " + value + " = __ko_value; }");
622
+ }
623
+ }
624
+
625
+ if (propertyAccessorTokens.length > 0) {
626
+ var allPropertyAccessors = propertyAccessorTokens.join("");
627
+ jsonString = jsonString + ", '_ko_property_writers' : { " + allPropertyAccessors + " } ";
628
+ }
629
+
630
+ return jsonString;
631
+ }
632
+ };
633
+ })();/// <reference path="../subscribables/dependentObservable.js" />
634
+
635
+ (function () {
636
+ var bindingAttributeName = "data-bind";
637
+ ko.bindingHandlers = {};
638
+
639
+ function parseBindingAttribute(attributeText, viewModel) {
640
+ try {
641
+ var json = " { " + ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(attributeText) + " } ";
642
+ return ko.utils.evalWithinScope(json, viewModel === null ? window : viewModel);
643
+ } catch (ex) {
644
+ throw new Error("Unable to parse binding attribute.\nMessage: " + ex + ";\nAttribute value: " + attributeText);
645
+ }
646
+ }
647
+
648
+ function invokeBindingHandler(handler, element, dataValue, allBindings, viewModel) {
649
+ handler(element, dataValue, allBindings, viewModel);
650
+ }
651
+
652
+ ko.applyBindingsToNode = function (node, bindings, viewModel) {
653
+ var isFirstEvaluation = true;
654
+ new ko.dependentObservable(
655
+ function () {
656
+ var evaluatedBindings = (typeof bindings == "function") ? bindings() : bindings;
657
+ var parsedBindings = evaluatedBindings || parseBindingAttribute(node.getAttribute(bindingAttributeName), viewModel);
658
+ for (var bindingKey in parsedBindings) {
659
+ if (ko.bindingHandlers[bindingKey]) {
660
+ if (isFirstEvaluation && typeof ko.bindingHandlers[bindingKey].init == "function")
661
+ invokeBindingHandler(ko.bindingHandlers[bindingKey].init, node, parsedBindings[bindingKey], parsedBindings, viewModel);
662
+ if (typeof ko.bindingHandlers[bindingKey].update == "function")
663
+ invokeBindingHandler(ko.bindingHandlers[bindingKey].update, node, parsedBindings[bindingKey], parsedBindings, viewModel);
664
+ }
665
+ }
666
+ },
667
+ null,
668
+ { disposeWhen: function () { return !ko.utils.domNodeIsAttachedToDocument(node); } }
669
+ );
670
+ isFirstEvaluation = false;
671
+ };
672
+
673
+ ko.applyBindings = function (rootNode, viewModel) {
674
+ var elemsWithBindingAttribute = ko.utils.getElementsHavingAttribute(rootNode, bindingAttributeName);
675
+ ko.utils.arrayForEach(elemsWithBindingAttribute, function (element) {
676
+ ko.applyBindingsToNode(element, null, viewModel);
677
+ });
678
+ };
679
+ })();/// <reference path="bindingAttributeSyntax.js" />
680
+
681
+ ko.bindingHandlers.click = {
682
+ init: function (element, value, allBindings, viewModel) {
683
+ ko.utils.registerEventHandler(element, "click", function (event) {
684
+ try { value.call(viewModel); }
685
+ finally {
686
+ if (event.preventDefault)
687
+ event.preventDefault();
688
+ else
689
+ event.returnValue = false;
690
+ }
691
+ });
692
+ }
693
+ };
694
+
695
+ ko.bindingHandlers.submit = {
696
+ init: function (element, value, allBindings, viewModel) {
697
+ if (typeof value != "function")
698
+ throw new Error("The value for a submit binding must be a function to invoke on submit");
699
+ ko.utils.registerEventHandler(element, "submit", function (event) {
700
+ try { value.call(viewModel); }
701
+ finally {
702
+ if (event.preventDefault)
703
+ event.preventDefault();
704
+ else
705
+ event.returnValue = false;
706
+ }
707
+ });
708
+ }
709
+ };
710
+
711
+ ko.bindingHandlers.visible = {
712
+ update: function (element, value) {
713
+ value = ko.utils.unwrapObservable(value);
714
+ var isCurrentlyVisible = !(element.style.display == "none");
715
+ if (value && !isCurrentlyVisible)
716
+ element.style.display = "";
717
+ else if ((!value) && isCurrentlyVisible)
718
+ element.style.display = "none";
719
+ }
720
+ }
721
+
722
+ ko.bindingHandlers.enable = {
723
+ update: function (element, value) {
724
+ value = ko.utils.unwrapObservable(value);
725
+ if (value && element.disabled)
726
+ element.removeAttribute("disabled");
727
+ else if ((!value) && (!element.disabled))
728
+ element.disabled = true;
729
+ }
730
+ };
731
+
732
+ ko.bindingHandlers.disable = { update: function (element, value) { ko.bindingHandlers.enable.update(element, !ko.utils.unwrapObservable(value)); } };
733
+
734
+ ko.bindingHandlers.value = {
735
+ init: function (element, value, allBindings) {
736
+ var eventName = allBindings.valueUpdate || "change";
737
+ if (ko.isWriteableObservable(value))
738
+ ko.utils.registerEventHandler(element, eventName, function () { value(this.value); });
739
+ else if (allBindings._ko_property_writers && allBindings._ko_property_writers.value)
740
+ ko.utils.registerEventHandler(element, eventName, function () { allBindings._ko_property_writers.value(this.value); });
741
+ },
742
+ update: function (element, value) {
743
+ var newValue = ko.utils.unwrapObservable(value);
744
+
745
+ if (newValue != element.value) {
746
+ var applyValueAction = function () { element.value = newValue; };
747
+ applyValueAction();
748
+
749
+ // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
750
+ // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
751
+ // to apply the value as well.
752
+ var alsoApplyAsynchronously = element.tagName == "SELECT";
753
+ if (alsoApplyAsynchronously)
754
+ setTimeout(applyValueAction, 0);
755
+ }
756
+ }
757
+ };
758
+
759
+ ko.bindingHandlers.options = {
760
+ update: function (element, value, allBindings) {
761
+ if (element.tagName != "SELECT")
762
+ throw new Error("values binding applies only to SELECT elements");
763
+
764
+ var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
765
+ return node.tagName && node.tagName == "OPTION" && node.selected;
766
+ }), function (node) {
767
+ return node.value || node.innerText || node.textContent;
768
+ });
769
+
770
+ value = ko.utils.unwrapObservable(value);
771
+ var selectedValue = element.value;
772
+ element.innerHTML = "";
773
+ if (value) {
774
+ if (typeof value.length != "number")
775
+ value = [value];
776
+ for (var i = 0, j = value.length; i < j; i++) {
777
+ var option = document.createElement("OPTION");
778
+ var optionValue = typeof allBindings.options_value == "string" ? value[i][allBindings.options_value] : value[i];
779
+ option.value = optionValue.toString();
780
+ option.innerHTML = (typeof allBindings.options_text == "string" ? value[i][allBindings.options_text] : optionValue).toString();
781
+ element.appendChild(option);
782
+ }
783
+ // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
784
+ // That's why we first added them without selection. Now it's time to set the selection.
785
+ var newOptions = element.getElementsByTagName("OPTION");
786
+ for (var i = 0, j = newOptions.length; i < j; i++) {
787
+ if (ko.utils.arrayIndexOf(previousSelectedValues, newOptions[i].value) >= 0)
788
+ newOptions[i].selected = true;
789
+ }
790
+ }
791
+ }
792
+ };
793
+
794
+ ko.bindingHandlers.selectedOptions = {
795
+ getSelectedValuesFromSelectNode: function (selectNode) {
796
+ var result = [];
797
+ var nodes = selectNode.childNodes;
798
+ for (var i = 0, j = nodes.length; i < j; i++) {
799
+ var node = nodes[i];
800
+ if ((node.tagName == "OPTION") && node.selected)
801
+ result.push(node.value);
802
+ }
803
+ return result;
804
+ },
805
+ init: function (element, value, allBindings) {
806
+ if (ko.isWriteableObservable(value))
807
+ ko.utils.registerEventHandler(element, "change", function () { value(ko.bindingHandlers.selectedOptions.getSelectedValuesFromSelectNode(this)); });
808
+ else if (allBindings._ko_property_writers && allBindings._ko_property_writers.value)
809
+ ko.utils.registerEventHandler(element, "change", function () { allBindings._ko_property_writers.value(ko.bindingHandlers.selectedOptions.getSelectedValuesFromSelectNode(this)); });
810
+ },
811
+ update: function (element, value) {
812
+ if (element.tagName != "SELECT")
813
+ throw new Error("values binding applies only to SELECT elements");
814
+
815
+ var newValue = ko.utils.unwrapObservable(value);
816
+ if (newValue && typeof newValue.length == "number") {
817
+ var nodes = element.childNodes;
818
+ for (var i = 0, j = nodes.length; i < j; i++) {
819
+ var node = nodes[i];
820
+ if (node.tagName == "OPTION")
821
+ node.selected = ko.utils.arrayIndexOf(newValue, node.value) >= 0;
822
+ }
823
+ }
824
+ }
825
+ };
826
+
827
+ ko.bindingHandlers.text = {
828
+ update: function (element, value) {
829
+ value = ko.utils.unwrapObservable(value);
830
+ typeof element.innerText == "string" ? element.innerText = value
831
+ : element.textContent = value;
832
+ }
833
+ };
834
+
835
+ ko.bindingHandlers.css = {
836
+ update: function (element, value) {
837
+ value = value || {};
838
+ for (var className in value) {
839
+ if (typeof className == "string") {
840
+ var shouldHaveClass = ko.utils.unwrapObservable(value[className]);
841
+ ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
842
+ }
843
+ }
844
+ }
845
+ };
846
+
847
+ ko.bindingHandlers.uniqueName = {
848
+ init: function (element, value) {
849
+ if (value)
850
+ element.name = "ko_unique_" + (++ko.bindingHandlers.uniqueName.currentIndex);
851
+ }
852
+ };
853
+ ko.bindingHandlers.uniqueName.currentIndex = 0;/// <reference path="../utils.js" />
854
+
855
+ ko.templateEngine = function () {
856
+ this.renderTemplate = function (templateName, data, options) {
857
+ throw "Override renderTemplate in your ko.templateEngine subclass";
858
+ },
859
+ this.isTemplateRewritten = function (templateName) {
860
+ throw "Override isTemplateRewritten in your ko.templateEngine subclass";
861
+ },
862
+ this.rewriteTemplate = function (templateName, rewriterCallback) {
863
+ throw "Override rewriteTemplate in your ko.templateEngine subclass";
864
+ },
865
+ this.createJavaScriptEvaluatorBlock = function (script) {
866
+ throw "Override createJavaScriptEvaluatorBlock in your ko.templateEngine subclass";
867
+ }
868
+ };/// <reference path="templateEngine.js" />
869
+
870
+ ko.templateRewriting = (function () {
871
+ var memoizeBindingAttributeSyntaxRegex = /(<[a-z]+(\s+(?!data-bind=)[a-z0-9]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])(.*?)\5/g;
872
+
873
+ return {
874
+ ensureTemplateIsRewritten: function (template, templateEngine) {
875
+ if (!templateEngine.isTemplateRewritten(template))
876
+ templateEngine.rewriteTemplate(template, function (htmlString) {
877
+ return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine);
878
+ });
879
+ },
880
+
881
+ memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
882
+ return htmlString.replace(memoizeBindingAttributeSyntaxRegex, function () {
883
+ var tagToRetain = arguments[1];
884
+ var dataBindAttributeValue = arguments[6];
885
+
886
+ dataBindAttributeValue = ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(dataBindAttributeValue);
887
+
888
+ // For no obvious reason, Opera fails to evaluate dataBindAttributeValue unless it's wrapped in an additional anonymous function,
889
+ // even though Opera's built-in debugger can evaluate it anyway. No other browser requires this extra indirection.
890
+ var applyBindingsToNextSiblingScript = "ko.templateRewriting.applyMemoizedBindingsToNextSibling(function() { \
891
+ return (function() { return { " + dataBindAttributeValue + " } })() \
892
+ })";
893
+ return templateEngine.createJavaScriptEvaluatorBlock(applyBindingsToNextSiblingScript) + tagToRetain;
894
+ });
895
+ },
896
+
897
+ applyMemoizedBindingsToNextSibling: function (bindings) {
898
+ return ko.memoization.memoize(function (domNode) {
899
+ if (domNode.nextSibling)
900
+ ko.applyBindingsToNode(domNode.nextSibling, bindings, null);
901
+ });
902
+ }
903
+ }
904
+ })();/// <reference path="templating.js" />
905
+ /// <reference path="../subscribables/dependentObservable.js" />
906
+
907
+ (function () {
908
+ var _templateEngine;
909
+ ko.setTemplateEngine = function (templateEngine) {
910
+ if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
911
+ throw "templateEngine must inherit from ko.templateEngine";
912
+ _templateEngine = templateEngine;
913
+ }
914
+
915
+ function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
916
+ return nodeOrNodeArray.nodeType ? nodeOrNodeArray
917
+ : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
918
+ : null;
919
+ }
920
+
921
+ function executeTemplate(targetNodeOrNodeArray, renderMode, template, data, options) {
922
+ var dataForTemplate = ko.utils.unwrapObservable(data);
923
+
924
+ options = options || {};
925
+ var templateEngineToUse = (options.templateEngine || _templateEngine);
926
+ ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse);
927
+ var renderedNodesArray = templateEngineToUse.renderTemplate(template, dataForTemplate, options);
928
+
929
+ // Loosely check result is an array of DOM nodes
930
+ if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
931
+ throw "Template engine must return an array of DOM nodes";
932
+
933
+ if (renderedNodesArray)
934
+ ko.utils.arrayForEach(renderedNodesArray, function (renderedNode) {
935
+ ko.memoization.unmemoizeDomNodeAndDescendants(renderedNode);
936
+ });
937
+
938
+ switch (renderMode) {
939
+ case "replaceChildren": ko.utils.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray); break;
940
+ case "replaceNode": ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray); break;
941
+ case "ignoreTargetNode": break;
942
+ default: throw new Error("Unknown renderMode: " + renderMode);
943
+ }
944
+
945
+ return renderedNodesArray;
946
+ }
947
+
948
+ ko.renderTemplate = function (template, data, options, targetNodeOrNodeArray, renderMode) {
949
+ options = options || {};
950
+ if ((options.templateEngine || _templateEngine) == undefined)
951
+ throw "Set a template engine before calling renderTemplate";
952
+ renderMode = renderMode || "replaceChildren";
953
+
954
+ if (targetNodeOrNodeArray) {
955
+ var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
956
+ var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); };
957
+
958
+ return new ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
959
+ function () {
960
+ var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, template, data, options);
961
+ if (renderMode == "replaceNode") {
962
+ targetNodeOrNodeArray = renderedNodesArray;
963
+ firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
964
+ }
965
+ },
966
+ null,
967
+ { disposeWhen: whenToDispose }
968
+ );
969
+ } else {
970
+ // 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
971
+ return ko.memoization.memoize(function (domNode) {
972
+ ko.renderTemplate(template, data, options, domNode, "replaceNode");
973
+ });
974
+ }
975
+ };
976
+
977
+ ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode) {
978
+ var whenToDispose = function () { return !ko.utils.domNodeIsAttachedToDocument(targetNode); };
979
+
980
+ new ko.dependentObservable(function () {
981
+ var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray);
982
+ if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
983
+ unwrappedArray = [unwrappedArray];
984
+
985
+ ko.utils.setDomNodeChildrenFromArrayMapping(targetNode, unwrappedArray, function (arrayValue) {
986
+ return executeTemplate(null, "ignoreTargetNode", template, arrayValue, options);
987
+ }, options);
988
+ }, null, { disposeWhen: whenToDispose });
989
+ };
990
+
991
+ ko.bindingHandlers.template = {
992
+ update: function (element, bindingValue, allBindings, viewModel) {
993
+ var templateName = typeof bindingValue == "string" ? bindingValue : bindingValue.name;
994
+
995
+ if (typeof bindingValue.foreach != "undefined") {
996
+ // Render once for each data point
997
+ ko.renderTemplateForEach(templateName, bindingValue.foreach || [], { afterAdd: bindingValue.afterAdd, beforeRemove: bindingValue.beforeRemove }, element);
998
+ }
999
+ else {
1000
+ // Render once for this single data point (or use the viewModel if no data was provided)
1001
+ var templateData = bindingValue.data;
1002
+ ko.renderTemplate(templateName, typeof templateData == "undefined" ? viewModel : templateData, null, element);
1003
+ }
1004
+ }
1005
+ };
1006
+ })();/// <reference path="../../utils.js" />
1007
+
1008
+ // Simple calculation based on Levenshtein distance.
1009
+ (function () {
1010
+
1011
+ function calculateEditDistanceMatrix(oldArray, newArray, maxAllowedDistance) {
1012
+ var distances = [];
1013
+ for (var i = 0; i <= newArray.length; i++)
1014
+ distances[i] = [];
1015
+
1016
+ // Top row - transform old array into empty array via deletions
1017
+ for (var i = 0, j = Math.min(oldArray.length, maxAllowedDistance); i <= j; i++)
1018
+ distances[0][i] = i;
1019
+
1020
+ // Left row - transform empty array into new array via additions
1021
+ for (var i = 1, j = Math.min(newArray.length, maxAllowedDistance); i <= j; i++) {
1022
+ distances[i][0] = i;
1023
+ }
1024
+
1025
+ // Fill out the body of the array
1026
+ var oldIndex, oldIndexMax = oldArray.length, newIndex, newIndexMax = newArray.length;
1027
+ var distanceViaAddition, distanceViaDeletion;
1028
+ for (oldIndex = 1; oldIndex <= oldIndexMax; oldIndex++) {
1029
+ var newIndexMinForRow = Math.max(1, oldIndex - maxAllowedDistance);
1030
+ var newIndexMaxForRow = Math.min(newIndexMax, oldIndex + maxAllowedDistance);
1031
+ for (newIndex = newIndexMinForRow; newIndex <= newIndexMaxForRow; newIndex++) {
1032
+ if (oldArray[oldIndex - 1] === newArray[newIndex - 1])
1033
+ distances[newIndex][oldIndex] = distances[newIndex - 1][oldIndex - 1];
1034
+ else {
1035
+ var northDistance = distances[newIndex - 1][oldIndex] === undefined ? Number.MAX_VALUE : distances[newIndex - 1][oldIndex] + 1;
1036
+ var westDistance = distances[newIndex][oldIndex - 1] === undefined ? Number.MAX_VALUE : distances[newIndex][oldIndex - 1] + 1;
1037
+ distances[newIndex][oldIndex] = Math.min(northDistance, westDistance);
1038
+ }
1039
+ }
1040
+ }
1041
+
1042
+ return distances;
1043
+ }
1044
+
1045
+ function findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray) {
1046
+ var oldIndex = oldArray.length;
1047
+ var newIndex = newArray.length;
1048
+ var editScript = [];
1049
+ var maxDistance = editDistanceMatrix[newIndex][oldIndex];
1050
+ if (maxDistance === undefined)
1051
+ return null; // maxAllowedDistance must be too small
1052
+ while ((oldIndex > 0) || (newIndex > 0)) {
1053
+ var me = editDistanceMatrix[newIndex][oldIndex];
1054
+ var distanceViaAdd = (newIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex] : maxDistance + 1;
1055
+ var distanceViaDelete = (oldIndex > 0) ? editDistanceMatrix[newIndex][oldIndex - 1] : maxDistance + 1;
1056
+ var distanceViaRetain = (newIndex > 0) && (oldIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex - 1] : maxDistance + 1;
1057
+ if ((distanceViaAdd === undefined) || (distanceViaAdd < me - 1)) distanceViaAdd = maxDistance + 1;
1058
+ if ((distanceViaDelete === undefined) || (distanceViaDelete < me - 1)) distanceViaDelete = maxDistance + 1;
1059
+ if (distanceViaRetain < me - 1) distanceViaRetain = maxDistance + 1;
1060
+
1061
+ if ((distanceViaAdd <= distanceViaDelete) && (distanceViaAdd < distanceViaRetain)) {
1062
+ editScript.push({ status: "added", value: newArray[newIndex - 1] });
1063
+ newIndex--;
1064
+ } else if ((distanceViaDelete < distanceViaAdd) && (distanceViaDelete < distanceViaRetain)) {
1065
+ editScript.push({ status: "deleted", value: oldArray[oldIndex - 1] });
1066
+ oldIndex--;
1067
+ } else {
1068
+ editScript.push({ status: "retained", value: oldArray[oldIndex - 1] });
1069
+ newIndex--;
1070
+ oldIndex--;
1071
+ }
1072
+ }
1073
+ return editScript.reverse();
1074
+ }
1075
+
1076
+ ko.utils.compareArrays = function (oldArray, newArray, maxEditsToConsider) {
1077
+ if (maxEditsToConsider === undefined) {
1078
+ return ko.utils.compareArrays(oldArray, newArray, 1) // First consider likely case where there is at most one edit (very fast)
1079
+ || ko.utils.compareArrays(oldArray, newArray, 10) // If that fails, account for a fair number of changes while still being fast
1080
+ || ko.utils.compareArrays(oldArray, newArray, Number.MAX_VALUE); // Ultimately give the right answer, even though it may take a long time
1081
+ } else {
1082
+ oldArray = oldArray || [];
1083
+ newArray = newArray || [];
1084
+ var editDistanceMatrix = calculateEditDistanceMatrix(oldArray, newArray, maxEditsToConsider);
1085
+ return findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray);
1086
+ }
1087
+ };
1088
+ })();/// <reference path="compareArrays.js" />
1089
+
1090
+ (function () {
1091
+ // Objective:
1092
+ // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
1093
+ // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
1094
+ // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
1095
+ // 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
1096
+ // previously mapped - retain those nodes, and just insert/delete other ones
1097
+
1098
+ ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options) {
1099
+ // Compare the provided array against the previous one
1100
+ array = array || [];
1101
+ options = options || {};
1102
+ var isFirstExecution = ko.utils.domData.get(domNode, "setDomNodeChildrenFromArrayMapping_lastMappingResult") === undefined;
1103
+ var lastMappingResult = ko.utils.domData.get(domNode, "setDomNodeChildrenFromArrayMapping_lastMappingResult") || [];
1104
+ var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
1105
+ var editScript = ko.utils.compareArrays(lastArray, array);
1106
+
1107
+ // Build the new mapping result
1108
+ var newMappingResult = [];
1109
+ var lastMappingResultIndex = 0;
1110
+ var nodesToDelete = [];
1111
+ var nodesAdded = [];
1112
+ var insertAfterNode = null;
1113
+ for (var i = 0, j = editScript.length; i < j; i++) {
1114
+ switch (editScript[i].status) {
1115
+ case "retained":
1116
+ // Just keep the information - don't touch the nodes
1117
+ var dataToRetain = lastMappingResult[lastMappingResultIndex];
1118
+ newMappingResult.push(dataToRetain);
1119
+ if (dataToRetain.domNodes.length > 0)
1120
+ insertAfterNode = dataToRetain.domNodes[dataToRetain.domNodes.length - 1];
1121
+ lastMappingResultIndex++;
1122
+ break;
1123
+
1124
+ case "deleted":
1125
+ // Queue these nodes for later removal
1126
+ ko.utils.arrayForEach(lastMappingResult[lastMappingResultIndex].domNodes, function (node) {
1127
+ nodesToDelete.push(node);
1128
+ insertAfterNode = node;
1129
+ });
1130
+ lastMappingResultIndex++;
1131
+ break;
1132
+
1133
+ case "added":
1134
+ // Map this array value and insert the resulting nodes at the current insertion point
1135
+ var mappedNodes = mapping(editScript[i].value) || [];
1136
+ newMappingResult.push({ arrayEntry: editScript[i].value, domNodes: mappedNodes });
1137
+ for (var nodeIndex = 0, nodeIndexMax = mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) {
1138
+ var node = mappedNodes[nodeIndex];
1139
+ nodesAdded.push(node);
1140
+ if (insertAfterNode == null) {
1141
+ // Insert at beginning
1142
+ if (domNode.firstChild)
1143
+ domNode.insertBefore(node, domNode.firstChild);
1144
+ else
1145
+ domNode.appendChild(node);
1146
+ } else {
1147
+ // Insert after insertion point
1148
+ if (insertAfterNode.nextSibling)
1149
+ domNode.insertBefore(node, insertAfterNode.nextSibling);
1150
+ else
1151
+ domNode.appendChild(node);
1152
+ }
1153
+ insertAfterNode = node;
1154
+ }
1155
+ break;
1156
+ }
1157
+ }
1158
+
1159
+ ko.utils.arrayForEach(nodesToDelete, function (node) { ko.utils.domData.cleanNodeAndDescendants(node); });
1160
+
1161
+ var invokedBeforeRemoveCallback = false;
1162
+ if (!isFirstExecution) {
1163
+ if (options.afterAdd)
1164
+ options.afterAdd(nodesAdded);
1165
+ if (options.beforeRemove) {
1166
+ options.beforeRemove(nodesToDelete);
1167
+ invokedBeforeRemoveCallback = true;
1168
+ }
1169
+ }
1170
+ if (!invokedBeforeRemoveCallback)
1171
+ ko.utils.arrayForEach(nodesToDelete, function (node) {
1172
+ if (node.parentNode)
1173
+ node.parentNode.removeChild(node);
1174
+ });
1175
+
1176
+ // Store a copy of the array items we just considered so we can difference it next time
1177
+ ko.utils.domData.set(domNode, "setDomNodeChildrenFromArrayMapping_lastMappingResult", newMappingResult);
1178
+ }
1179
+ })();/// <reference path="../templating.js" />
1180
+
1181
+ ko.jqueryTmplTemplateEngine = function () {
1182
+ function getTemplateNode(template) {
1183
+ var templateNode = document.getElementById(template);
1184
+ if (templateNode == null)
1185
+ throw new Error("Cannot find template with ID=" + template);
1186
+ return templateNode;
1187
+ }
1188
+
1189
+ this.renderTemplate = function (template, data, options) {
1190
+ // jquery.tmpl doesn't like it if the template returns just text content or nothing - it only likes you to return DOM nodes.
1191
+ // To make things more flexible, we can wrap the whole template in a <script> node so that jquery.tmpl just processes it as
1192
+ // text and doesn't try to parse the output. Then, since jquery.tmpl has jQuery as a dependency anyway, we can use jQuery to
1193
+ // parse that text into a document fragment using jQuery.clean().
1194
+ var templateTextInWrapper = "<script type=\"text/html\">" + ko.utils.stringTrim(getTemplateNode(template).text) + "</script>";
1195
+ var renderedMarkupInWrapper = $.tmpl(templateTextInWrapper, data);
1196
+ return jQuery.clean([renderedMarkupInWrapper[0].text], document);
1197
+ },
1198
+
1199
+ this.isTemplateRewritten = function (template) {
1200
+ return getTemplateNode(template).isRewritten === true;
1201
+ },
1202
+
1203
+ this.rewriteTemplate = function (template, rewriterCallback) {
1204
+ var templateNode = getTemplateNode(template);
1205
+ var rewritten = rewriterCallback(templateNode.text)
1206
+ templateNode.text = rewritten;
1207
+ templateNode.isRewritten = true;
1208
+ },
1209
+
1210
+ this.createJavaScriptEvaluatorBlock = function (script) {
1211
+ return "{{= " + script + "}}";
1212
+ },
1213
+
1214
+ // Am considering making template registration a native part of the API (and storing added templates centrally), but for now it's specific to this template engine
1215
+ this.addTemplate = function (templateName, templateMarkup) {
1216
+ document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
1217
+ }
1218
+ };
1219
+ ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
1220
+
1221
+ // Use this one by default
1222
+ ko.setTemplateEngine(new ko.jqueryTmplTemplateEngine());