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