knockout-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);