knockout-rails 0.0.1

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