knockoutjs-rails 2.1.0 → 2.2.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 +833 -699
- metadata +10 -5
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 2.
|
23
|
+
knockoutjs-rails 2.2.0 == Knockout.js 2.2.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,9 +1,10 @@
|
|
1
|
-
// Knockout JavaScript library v2.
|
1
|
+
// Knockout JavaScript library v2.2.0
|
2
2
|
// (c) Steven Sanderson - http://knockoutjs.com/
|
3
3
|
// License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
4
4
|
|
5
|
-
(function(
|
5
|
+
(function(){
|
6
6
|
var DEBUG=true;
|
7
|
+
(function(window,document,navigator,jQuery,undefined){
|
7
8
|
!function(factory) {
|
8
9
|
// Support three module loading scenarios
|
9
10
|
if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
|
@@ -36,7 +37,7 @@ ko.exportSymbol = function(koPath, object) {
|
|
36
37
|
ko.exportProperty = function(owner, publicName, object) {
|
37
38
|
owner[publicName] = object;
|
38
39
|
};
|
39
|
-
ko.version = "2.
|
40
|
+
ko.version = "2.2.0";
|
40
41
|
|
41
42
|
ko.exportSymbol('version', ko.version);
|
42
43
|
ko.utils = new (function () {
|
@@ -57,6 +58,9 @@ ko.utils = new (function () {
|
|
57
58
|
var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
|
58
59
|
|
59
60
|
// Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
|
61
|
+
// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
|
62
|
+
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
|
63
|
+
// If there is a future need to detect specific versions of IE10+, we will amend this.
|
60
64
|
var ieVersion = (function() {
|
61
65
|
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
|
62
66
|
|
@@ -167,12 +171,19 @@ ko.utils = new (function () {
|
|
167
171
|
|
168
172
|
var container = document.createElement('div');
|
169
173
|
for (var i = 0, j = nodesArray.length; i < j; i++) {
|
170
|
-
ko.cleanNode(nodesArray[i]);
|
171
|
-
container.appendChild(nodesArray[i]);
|
174
|
+
container.appendChild(ko.cleanNode(nodesArray[i]));
|
172
175
|
}
|
173
176
|
return container;
|
174
177
|
},
|
175
178
|
|
179
|
+
cloneNodes: function (nodesArray, shouldCleanNodes) {
|
180
|
+
for (var i = 0, j = nodesArray.length, newNodesArray = []; i < j; i++) {
|
181
|
+
var clonedNode = nodesArray[i].cloneNode(true);
|
182
|
+
newNodesArray.push(shouldCleanNodes ? ko.cleanNode(clonedNode) : clonedNode);
|
183
|
+
}
|
184
|
+
return newNodesArray;
|
185
|
+
},
|
186
|
+
|
176
187
|
setDomNodeChildren: function (domNode, childNodes) {
|
177
188
|
ko.utils.emptyDomNode(domNode);
|
178
189
|
if (childNodes) {
|
@@ -196,7 +207,7 @@ ko.utils = new (function () {
|
|
196
207
|
|
197
208
|
setOptionNodeSelectionState: function (optionNode, isSelected) {
|
198
209
|
// IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
|
199
|
-
if (
|
210
|
+
if (ieVersion < 7)
|
200
211
|
optionNode.setAttribute("selected", isSelected);
|
201
212
|
else
|
202
213
|
optionNode.selected = isSelected;
|
@@ -224,17 +235,6 @@ ko.utils = new (function () {
|
|
224
235
|
return string.substring(0, startsWith.length) === startsWith;
|
225
236
|
},
|
226
237
|
|
227
|
-
buildEvalWithinScopeFunction: function (expression, scopeLevels) {
|
228
|
-
// Build the source for a function that evaluates "expression"
|
229
|
-
// For each scope variable, add an extra level of "with" nesting
|
230
|
-
// Example result: with(sc[1]) { with(sc[0]) { return (expression) } }
|
231
|
-
var functionBody = "return (" + expression + ")";
|
232
|
-
for (var i = 0; i < scopeLevels; i++) {
|
233
|
-
functionBody = "with(sc[" + i + "]) { " + functionBody + " } ";
|
234
|
-
}
|
235
|
-
return new Function("sc", functionBody);
|
236
|
-
},
|
237
|
-
|
238
238
|
domNodeIsContainedBy: function (node, containedByNode) {
|
239
239
|
if (containedByNode.compareDocumentPosition)
|
240
240
|
return (containedByNode.compareDocumentPosition(node) & 16) == 16;
|
@@ -320,18 +320,25 @@ ko.utils = new (function () {
|
|
320
320
|
return ko.isObservable(value) ? value() : value;
|
321
321
|
},
|
322
322
|
|
323
|
-
|
324
|
-
|
325
|
-
|
323
|
+
peekObservable: function (value) {
|
324
|
+
return ko.isObservable(value) ? value.peek() : value;
|
325
|
+
},
|
326
326
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
327
|
+
toggleDomNodeCssClass: function (node, classNames, shouldHaveClass) {
|
328
|
+
if (classNames) {
|
329
|
+
var cssClassNameRegex = /[\w-]+/g,
|
330
|
+
currentClassNames = node.className.match(cssClassNameRegex) || [];
|
331
|
+
ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
|
332
|
+
var indexOfClass = ko.utils.arrayIndexOf(currentClassNames, className);
|
333
|
+
if (indexOfClass >= 0) {
|
334
|
+
if (!shouldHaveClass)
|
335
|
+
currentClassNames.splice(indexOfClass, 1);
|
336
|
+
} else {
|
337
|
+
if (shouldHaveClass)
|
338
|
+
currentClassNames.push(className);
|
339
|
+
}
|
340
|
+
});
|
341
|
+
node.className = currentClassNames.join(" ");
|
335
342
|
}
|
336
343
|
},
|
337
344
|
|
@@ -340,13 +347,44 @@ ko.utils = new (function () {
|
|
340
347
|
if ((value === null) || (value === undefined))
|
341
348
|
value = "";
|
342
349
|
|
343
|
-
|
344
|
-
|
350
|
+
if (element.nodeType === 3) {
|
351
|
+
element.data = value;
|
352
|
+
} else {
|
353
|
+
// We need there to be exactly one child: a text node.
|
354
|
+
// If there are no children, more than one, or if it's not a text node,
|
355
|
+
// we'll clear everything and create a single text node.
|
356
|
+
var innerTextNode = ko.virtualElements.firstChild(element);
|
357
|
+
if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) {
|
358
|
+
ko.virtualElements.setDomNodeChildren(element, [document.createTextNode(value)]);
|
359
|
+
} else {
|
360
|
+
innerTextNode.data = value;
|
361
|
+
}
|
362
|
+
|
363
|
+
ko.utils.forceRefresh(element);
|
364
|
+
}
|
365
|
+
},
|
366
|
+
|
367
|
+
setElementName: function(element, name) {
|
368
|
+
element.name = name;
|
345
369
|
|
370
|
+
// Workaround IE 6/7 issue
|
371
|
+
// - https://github.com/SteveSanderson/knockout/issues/197
|
372
|
+
// - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
|
373
|
+
if (ieVersion <= 7) {
|
374
|
+
try {
|
375
|
+
element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
|
376
|
+
}
|
377
|
+
catch(e) {} // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View"
|
378
|
+
}
|
379
|
+
},
|
380
|
+
|
381
|
+
forceRefresh: function(node) {
|
382
|
+
// Workaround for an IE9 rendering bug - https://github.com/SteveSanderson/knockout/issues/209
|
346
383
|
if (ieVersion >= 9) {
|
347
|
-
//
|
348
|
-
|
349
|
-
|
384
|
+
// For text nodes and comment nodes (most likely virtual elements), we will have to refresh the container
|
385
|
+
var elem = node.nodeType == 1 ? node : node.parentNode;
|
386
|
+
if (elem.style)
|
387
|
+
elem.style.zoom = elem.style.zoom;
|
350
388
|
}
|
351
389
|
},
|
352
390
|
|
@@ -465,6 +503,7 @@ ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
|
|
465
503
|
ko.exportSymbol('utils.extend', ko.utils.extend);
|
466
504
|
ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
|
467
505
|
ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
|
506
|
+
ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable);
|
468
507
|
ko.exportSymbol('utils.postJson', ko.utils.postJson);
|
469
508
|
ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
|
470
509
|
ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler);
|
@@ -505,7 +544,7 @@ ko.utils.domData = new (function () {
|
|
505
544
|
},
|
506
545
|
getAll: function (node, createIfNotFound) {
|
507
546
|
var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
|
508
|
-
var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null");
|
547
|
+
var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];
|
509
548
|
if (!hasExistingDataStore) {
|
510
549
|
if (!createIfNotFound)
|
511
550
|
return undefined;
|
@@ -519,7 +558,9 @@ ko.utils.domData = new (function () {
|
|
519
558
|
if (dataStoreKey) {
|
520
559
|
delete dataStore[dataStoreKey];
|
521
560
|
node[dataStoreKeyExpandoPropertyName] = null;
|
561
|
+
return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
|
522
562
|
}
|
563
|
+
return false;
|
523
564
|
}
|
524
565
|
}
|
525
566
|
})();
|
@@ -607,6 +648,7 @@ ko.utils.domNodeDisposal = new (function () {
|
|
607
648
|
cleanSingleNode(descendants[i]);
|
608
649
|
}
|
609
650
|
}
|
651
|
+
return node;
|
610
652
|
},
|
611
653
|
|
612
654
|
removeNode : function(node) {
|
@@ -687,6 +729,9 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
|
|
687
729
|
ko.utils.setHtml = function(node, html) {
|
688
730
|
ko.utils.emptyDomNode(node);
|
689
731
|
|
732
|
+
// There's no legitimate reason to display a stringified observable without unwrapping it, so we'll unwrap it
|
733
|
+
html = ko.utils.unwrapObservable(html);
|
734
|
+
|
690
735
|
if ((html !== null) && (html !== undefined)) {
|
691
736
|
if (typeof html != 'string')
|
692
737
|
html = html.toString();
|
@@ -863,12 +908,14 @@ ko.subscribable['fn'] = {
|
|
863
908
|
"notifySubscribers": function (valueToNotify, event) {
|
864
909
|
event = event || defaultEvent;
|
865
910
|
if (this._subscriptions[event]) {
|
866
|
-
ko.
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
subscription.
|
871
|
-
|
911
|
+
ko.dependencyDetection.ignore(function() {
|
912
|
+
ko.utils.arrayForEach(this._subscriptions[event].slice(0), function (subscription) {
|
913
|
+
// In case a subscription was disposed during the arrayForEach cycle, check
|
914
|
+
// for isDisposed on each subscription before invoking its callback
|
915
|
+
if (subscription && (subscription.isDisposed !== true))
|
916
|
+
subscription.callback(valueToNotify);
|
917
|
+
});
|
918
|
+
}, this);
|
872
919
|
}
|
873
920
|
},
|
874
921
|
|
@@ -909,11 +956,20 @@ ko.dependencyDetection = (function () {
|
|
909
956
|
throw new Error("Only subscribable things can act as dependencies");
|
910
957
|
if (_frames.length > 0) {
|
911
958
|
var topFrame = _frames[_frames.length - 1];
|
912
|
-
if (ko.utils.arrayIndexOf(topFrame.distinctDependencies, subscribable) >= 0)
|
959
|
+
if (!topFrame || ko.utils.arrayIndexOf(topFrame.distinctDependencies, subscribable) >= 0)
|
913
960
|
return;
|
914
961
|
topFrame.distinctDependencies.push(subscribable);
|
915
962
|
topFrame.callback(subscribable);
|
916
963
|
}
|
964
|
+
},
|
965
|
+
|
966
|
+
ignore: function(callback, callbackTarget, callbackArgs) {
|
967
|
+
try {
|
968
|
+
_frames.push(null);
|
969
|
+
return callback.apply(callbackTarget, callbackArgs || []);
|
970
|
+
} finally {
|
971
|
+
_frames.pop();
|
972
|
+
}
|
917
973
|
}
|
918
974
|
};
|
919
975
|
})();
|
@@ -943,10 +999,12 @@ ko.observable = function (initialValue) {
|
|
943
999
|
}
|
944
1000
|
if (DEBUG) observable._latestValue = _latestValue;
|
945
1001
|
ko.subscribable.call(observable);
|
1002
|
+
observable.peek = function() { return _latestValue };
|
946
1003
|
observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }
|
947
1004
|
observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }
|
948
1005
|
ko.utils.extend(observable, ko.observable['fn']);
|
949
1006
|
|
1007
|
+
ko.exportProperty(observable, 'peek', observable.peek);
|
950
1008
|
ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
|
951
1009
|
ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
|
952
1010
|
|
@@ -1002,7 +1060,7 @@ ko.observableArray = function (initialValues) {
|
|
1002
1060
|
|
1003
1061
|
ko.observableArray['fn'] = {
|
1004
1062
|
'remove': function (valueOrPredicate) {
|
1005
|
-
var underlyingArray = this();
|
1063
|
+
var underlyingArray = this.peek();
|
1006
1064
|
var removedValues = [];
|
1007
1065
|
var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
|
1008
1066
|
for (var i = 0; i < underlyingArray.length; i++) {
|
@@ -1025,7 +1083,7 @@ ko.observableArray['fn'] = {
|
|
1025
1083
|
'removeAll': function (arrayOfValues) {
|
1026
1084
|
// If you passed zero args, we remove everything
|
1027
1085
|
if (arrayOfValues === undefined) {
|
1028
|
-
var underlyingArray = this();
|
1086
|
+
var underlyingArray = this.peek();
|
1029
1087
|
var allValues = underlyingArray.slice(0);
|
1030
1088
|
this.valueWillMutate();
|
1031
1089
|
underlyingArray.splice(0, underlyingArray.length);
|
@@ -1041,7 +1099,7 @@ ko.observableArray['fn'] = {
|
|
1041
1099
|
},
|
1042
1100
|
|
1043
1101
|
'destroy': function (valueOrPredicate) {
|
1044
|
-
var underlyingArray = this();
|
1102
|
+
var underlyingArray = this.peek();
|
1045
1103
|
var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
|
1046
1104
|
this.valueWillMutate();
|
1047
1105
|
for (var i = underlyingArray.length - 1; i >= 0; i--) {
|
@@ -1074,16 +1132,20 @@ ko.observableArray['fn'] = {
|
|
1074
1132
|
var index = this['indexOf'](oldItem);
|
1075
1133
|
if (index >= 0) {
|
1076
1134
|
this.valueWillMutate();
|
1077
|
-
this()[index] = newItem;
|
1135
|
+
this.peek()[index] = newItem;
|
1078
1136
|
this.valueHasMutated();
|
1079
1137
|
}
|
1080
1138
|
}
|
1081
1139
|
}
|
1082
1140
|
|
1083
1141
|
// Populate ko.observableArray.fn with read/write functions from native arrays
|
1142
|
+
// Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
|
1143
|
+
// because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale
|
1084
1144
|
ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
|
1085
1145
|
ko.observableArray['fn'][methodName] = function () {
|
1086
|
-
|
1146
|
+
// Use "peek" to avoid creating a subscription in any computed that we're executing in the context of
|
1147
|
+
// (for consistency with mutating regular observables)
|
1148
|
+
var underlyingArray = this.peek();
|
1087
1149
|
this.valueWillMutate();
|
1088
1150
|
var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
|
1089
1151
|
this.valueHasMutated();
|
@@ -1116,41 +1178,20 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
|
|
1116
1178
|
if (!readFunction)
|
1117
1179
|
readFunction = options["read"];
|
1118
1180
|
}
|
1119
|
-
// By here, "options" is always non-null
|
1120
1181
|
if (typeof readFunction != "function")
|
1121
1182
|
throw new Error("Pass a function that returns the value of the ko.computed");
|
1122
1183
|
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1184
|
+
function addSubscriptionToDependency(subscribable) {
|
1185
|
+
_subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync));
|
1186
|
+
}
|
1126
1187
|
|
1127
|
-
var _subscriptionsToDependencies = [];
|
1128
1188
|
function disposeAllSubscriptionsToDependencies() {
|
1129
1189
|
ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) {
|
1130
1190
|
subscription.dispose();
|
1131
1191
|
});
|
1132
1192
|
_subscriptionsToDependencies = [];
|
1133
1193
|
}
|
1134
|
-
var dispose = disposeAllSubscriptionsToDependencies;
|
1135
|
-
|
1136
|
-
// Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values
|
1137
|
-
// (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(),
|
1138
|
-
// plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
|
1139
|
-
var disposeWhenNodeIsRemoved = (typeof options["disposeWhenNodeIsRemoved"] == "object") ? options["disposeWhenNodeIsRemoved"] : null;
|
1140
|
-
var disposeWhen = options["disposeWhen"] || function() { return false; };
|
1141
|
-
if (disposeWhenNodeIsRemoved) {
|
1142
|
-
dispose = function() {
|
1143
|
-
ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, arguments.callee);
|
1144
|
-
disposeAllSubscriptionsToDependencies();
|
1145
|
-
};
|
1146
|
-
ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
|
1147
|
-
var existingDisposeWhenFunction = disposeWhen;
|
1148
|
-
disposeWhen = function () {
|
1149
|
-
return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || existingDisposeWhenFunction();
|
1150
|
-
}
|
1151
|
-
}
|
1152
1194
|
|
1153
|
-
var evaluationTimeoutInstance = null;
|
1154
1195
|
function evaluatePossiblyAsync() {
|
1155
1196
|
var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
|
1156
1197
|
if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
|
@@ -1188,7 +1229,7 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
|
|
1188
1229
|
if ((inOld = ko.utils.arrayIndexOf(disposalCandidates, subscribable)) >= 0)
|
1189
1230
|
disposalCandidates[inOld] = undefined; // Don't want to dispose this subscription, as it's still being used
|
1190
1231
|
else
|
1191
|
-
|
1232
|
+
addSubscriptionToDependency(subscribable); // Brand new subscription - add it
|
1192
1233
|
});
|
1193
1234
|
|
1194
1235
|
var newValue = readFunction.call(evaluatorFunctionTarget);
|
@@ -1209,46 +1250,82 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
|
|
1209
1250
|
|
1210
1251
|
dependentObservable["notifySubscribers"](_latestValue);
|
1211
1252
|
_isBeingEvaluated = false;
|
1212
|
-
|
1253
|
+
if (!_subscriptionsToDependencies.length)
|
1254
|
+
dispose();
|
1213
1255
|
}
|
1214
1256
|
|
1215
1257
|
function dependentObservable() {
|
1216
1258
|
if (arguments.length > 0) {
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
if (typeof writeFunction === "function") {
|
1225
|
-
// Writing a value
|
1226
|
-
writeFunction.apply(evaluatorFunctionTarget, arguments);
|
1259
|
+
if (typeof writeFunction === "function") {
|
1260
|
+
// Writing a value
|
1261
|
+
writeFunction.apply(evaluatorFunctionTarget, arguments);
|
1262
|
+
} else {
|
1263
|
+
throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
|
1264
|
+
}
|
1265
|
+
return this; // Permits chained assignments
|
1227
1266
|
} else {
|
1228
|
-
|
1267
|
+
// Reading the value
|
1268
|
+
if (!_hasBeenEvaluated)
|
1269
|
+
evaluateImmediate();
|
1270
|
+
ko.dependencyDetection.registerDependency(dependentObservable);
|
1271
|
+
return _latestValue;
|
1229
1272
|
}
|
1230
1273
|
}
|
1231
1274
|
|
1232
|
-
function
|
1233
|
-
// Reading the value
|
1275
|
+
function peek() {
|
1234
1276
|
if (!_hasBeenEvaluated)
|
1235
1277
|
evaluateImmediate();
|
1236
|
-
ko.dependencyDetection.registerDependency(dependentObservable);
|
1237
1278
|
return _latestValue;
|
1238
1279
|
}
|
1239
1280
|
|
1281
|
+
function isActive() {
|
1282
|
+
return !_hasBeenEvaluated || _subscriptionsToDependencies.length > 0;
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
// By here, "options" is always non-null
|
1286
|
+
var writeFunction = options["write"],
|
1287
|
+
disposeWhenNodeIsRemoved = options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
|
1288
|
+
disposeWhen = options["disposeWhen"] || options.disposeWhen || function() { return false; },
|
1289
|
+
dispose = disposeAllSubscriptionsToDependencies,
|
1290
|
+
_subscriptionsToDependencies = [],
|
1291
|
+
evaluationTimeoutInstance = null;
|
1292
|
+
|
1293
|
+
if (!evaluatorFunctionTarget)
|
1294
|
+
evaluatorFunctionTarget = options["owner"];
|
1295
|
+
|
1296
|
+
dependentObservable.peek = peek;
|
1240
1297
|
dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; };
|
1241
1298
|
dependentObservable.hasWriteFunction = typeof options["write"] === "function";
|
1242
1299
|
dependentObservable.dispose = function () { dispose(); };
|
1300
|
+
dependentObservable.isActive = isActive;
|
1243
1301
|
|
1244
1302
|
ko.subscribable.call(dependentObservable);
|
1245
1303
|
ko.utils.extend(dependentObservable, ko.dependentObservable['fn']);
|
1246
1304
|
|
1305
|
+
ko.exportProperty(dependentObservable, 'peek', dependentObservable.peek);
|
1306
|
+
ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
|
1307
|
+
ko.exportProperty(dependentObservable, 'isActive', dependentObservable.isActive);
|
1308
|
+
ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount);
|
1309
|
+
|
1310
|
+
// Evaluate, unless deferEvaluation is true
|
1247
1311
|
if (options['deferEvaluation'] !== true)
|
1248
1312
|
evaluateImmediate();
|
1249
1313
|
|
1250
|
-
|
1251
|
-
|
1314
|
+
// Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values.
|
1315
|
+
// But skip if isActive is false (there will never be any dependencies to dispose).
|
1316
|
+
// (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(),
|
1317
|
+
// plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
|
1318
|
+
if (disposeWhenNodeIsRemoved && isActive()) {
|
1319
|
+
dispose = function() {
|
1320
|
+
ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, arguments.callee);
|
1321
|
+
disposeAllSubscriptionsToDependencies();
|
1322
|
+
};
|
1323
|
+
ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
|
1324
|
+
var existingDisposeWhenFunction = disposeWhen;
|
1325
|
+
disposeWhen = function () {
|
1326
|
+
return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || existingDisposeWhenFunction();
|
1327
|
+
}
|
1328
|
+
}
|
1252
1329
|
|
1253
1330
|
return dependentObservable;
|
1254
1331
|
};
|
@@ -1369,7 +1446,9 @@ ko.exportSymbol('toJSON', ko.toJSON);
|
|
1369
1446
|
case 'option':
|
1370
1447
|
if (element[hasDomDataExpandoProperty] === true)
|
1371
1448
|
return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
|
1372
|
-
return
|
1449
|
+
return ko.utils.ieVersion <= 7
|
1450
|
+
? (element.getAttributeNode('value').specified ? element.value : element.text)
|
1451
|
+
: element.value;
|
1373
1452
|
case 'select':
|
1374
1453
|
return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
|
1375
1454
|
default:
|
@@ -1419,12 +1498,14 @@ ko.exportSymbol('toJSON', ko.toJSON);
|
|
1419
1498
|
ko.exportSymbol('selectExtensions', ko.selectExtensions);
|
1420
1499
|
ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
|
1421
1500
|
ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
|
1422
|
-
|
1423
|
-
ko.jsonExpressionRewriting = (function () {
|
1501
|
+
ko.expressionRewriting = (function () {
|
1424
1502
|
var restoreCapturedTokensRegex = /\@ko_token_(\d+)\@/g;
|
1425
|
-
var javaScriptAssignmentTarget = /^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i;
|
1426
1503
|
var javaScriptReservedWords = ["true", "false"];
|
1427
1504
|
|
1505
|
+
// Matches something that can be assigned to--either an isolated identifier or something ending with a property accessor
|
1506
|
+
// This is designed to be simple and avoid false negatives, but could produce false positives (e.g., a+b.c).
|
1507
|
+
var javaScriptAssignmentTarget = /^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i;
|
1508
|
+
|
1428
1509
|
function restoreTokens(string, tokens) {
|
1429
1510
|
var prevValue = null;
|
1430
1511
|
while (string != prevValue) { // Keep restoring tokens until it no longer makes a difference (they may be nested)
|
@@ -1436,10 +1517,11 @@ ko.jsonExpressionRewriting = (function () {
|
|
1436
1517
|
return string;
|
1437
1518
|
}
|
1438
1519
|
|
1439
|
-
function
|
1520
|
+
function getWriteableValue(expression) {
|
1440
1521
|
if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0)
|
1441
1522
|
return false;
|
1442
|
-
|
1523
|
+
var match = expression.match(javaScriptAssignmentTarget);
|
1524
|
+
return match === null ? false : match[1] ? ('Object(' + match[1] + ')' + match[2]) : expression;
|
1443
1525
|
}
|
1444
1526
|
|
1445
1527
|
function ensureQuoted(key) {
|
@@ -1542,9 +1624,9 @@ ko.jsonExpressionRewriting = (function () {
|
|
1542
1624
|
return result;
|
1543
1625
|
},
|
1544
1626
|
|
1545
|
-
|
1627
|
+
preProcessBindings: function (objectLiteralStringOrKeyValueArray) {
|
1546
1628
|
var keyValueArray = typeof objectLiteralStringOrKeyValueArray === "string"
|
1547
|
-
? ko.
|
1629
|
+
? ko.expressionRewriting.parseObjectLiteral(objectLiteralStringOrKeyValueArray)
|
1548
1630
|
: objectLiteralStringOrKeyValueArray;
|
1549
1631
|
var resultStrings = [], propertyAccessorResultStrings = [];
|
1550
1632
|
|
@@ -1559,7 +1641,7 @@ ko.jsonExpressionRewriting = (function () {
|
|
1559
1641
|
resultStrings.push(":");
|
1560
1642
|
resultStrings.push(val);
|
1561
1643
|
|
1562
|
-
if (
|
1644
|
+
if (val = getWriteableValue(ko.utils.stringTrim(val))) {
|
1563
1645
|
if (propertyAccessorResultStrings.length > 0)
|
1564
1646
|
propertyAccessorResultStrings.push(", ");
|
1565
1647
|
propertyAccessorResultStrings.push(quotedKey + " : function(__ko_value) { " + val + " = __ko_value; }");
|
@@ -1599,18 +1681,22 @@ ko.jsonExpressionRewriting = (function () {
|
|
1599
1681
|
var propWriters = allBindingsAccessor()['_ko_property_writers'];
|
1600
1682
|
if (propWriters && propWriters[key])
|
1601
1683
|
propWriters[key](value);
|
1602
|
-
} else if (!checkIfDifferent || property() !== value) {
|
1684
|
+
} else if (!checkIfDifferent || property.peek() !== value) {
|
1603
1685
|
property(value);
|
1604
1686
|
}
|
1605
1687
|
}
|
1606
1688
|
};
|
1607
1689
|
})();
|
1608
1690
|
|
1609
|
-
ko.exportSymbol('
|
1610
|
-
ko.exportSymbol('
|
1611
|
-
ko.exportSymbol('
|
1612
|
-
ko.exportSymbol('
|
1613
|
-
|
1691
|
+
ko.exportSymbol('expressionRewriting', ko.expressionRewriting);
|
1692
|
+
ko.exportSymbol('expressionRewriting.bindingRewriteValidators', ko.expressionRewriting.bindingRewriteValidators);
|
1693
|
+
ko.exportSymbol('expressionRewriting.parseObjectLiteral', ko.expressionRewriting.parseObjectLiteral);
|
1694
|
+
ko.exportSymbol('expressionRewriting.preProcessBindings', ko.expressionRewriting.preProcessBindings);
|
1695
|
+
|
1696
|
+
// For backward compatibility, define the following aliases. (Previously, these function names were misleading because
|
1697
|
+
// they referred to JSON specifically, even though they actually work with arbitrary JavaScript object literal expressions.)
|
1698
|
+
ko.exportSymbol('jsonExpressionRewriting', ko.expressionRewriting);
|
1699
|
+
ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.expressionRewriting.preProcessBindings);(function() {
|
1614
1700
|
// "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
|
1615
1701
|
// may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
|
1616
1702
|
// If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
|
@@ -1624,7 +1710,7 @@ ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.js
|
|
1624
1710
|
// So, use node.text where available, and node.nodeValue elsewhere
|
1625
1711
|
var commentNodesHaveTextProperty = document.createComment("test").text === "<!--test-->";
|
1626
1712
|
|
1627
|
-
var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko
|
1713
|
+
var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*-->$/ : /^\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*$/;
|
1628
1714
|
var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
|
1629
1715
|
var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
|
1630
1716
|
|
@@ -1730,7 +1816,9 @@ ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.js
|
|
1730
1816
|
},
|
1731
1817
|
|
1732
1818
|
insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
|
1733
|
-
if (!
|
1819
|
+
if (!insertAfterNode) {
|
1820
|
+
ko.virtualElements.prepend(containerNode, nodeToInsert);
|
1821
|
+
} else if (!isStartComment(containerNode)) {
|
1734
1822
|
// Insert after insertion point
|
1735
1823
|
if (insertAfterNode.nextSibling)
|
1736
1824
|
containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
|
@@ -1819,7 +1907,7 @@ ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomN
|
|
1819
1907
|
|
1820
1908
|
'getBindings': function(node, bindingContext) {
|
1821
1909
|
var bindingsString = this['getBindingsString'](node, bindingContext);
|
1822
|
-
return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext) : null;
|
1910
|
+
return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
|
1823
1911
|
},
|
1824
1912
|
|
1825
1913
|
// The following function is only used internally by this default provider.
|
@@ -1834,12 +1922,10 @@ ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomN
|
|
1834
1922
|
|
1835
1923
|
// The following function is only used internally by this default provider.
|
1836
1924
|
// It's not part of the interface definition for a general binding provider.
|
1837
|
-
'parseBindingsString': function(bindingsString, bindingContext) {
|
1925
|
+
'parseBindingsString': function(bindingsString, bindingContext, node) {
|
1838
1926
|
try {
|
1839
|
-
var
|
1840
|
-
|
1841
|
-
bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, scopes.length, this.bindingCache);
|
1842
|
-
return bindingFunction(scopes);
|
1927
|
+
var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache);
|
1928
|
+
return bindingFunction(bindingContext, node);
|
1843
1929
|
} catch (ex) {
|
1844
1930
|
throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
|
1845
1931
|
}
|
@@ -1848,15 +1934,19 @@ ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomN
|
|
1848
1934
|
|
1849
1935
|
ko.bindingProvider['instance'] = new ko.bindingProvider();
|
1850
1936
|
|
1851
|
-
function createBindingsStringEvaluatorViaCache(bindingsString,
|
1852
|
-
var cacheKey =
|
1937
|
+
function createBindingsStringEvaluatorViaCache(bindingsString, cache) {
|
1938
|
+
var cacheKey = bindingsString;
|
1853
1939
|
return cache[cacheKey]
|
1854
|
-
|| (cache[cacheKey] = createBindingsStringEvaluator(bindingsString
|
1940
|
+
|| (cache[cacheKey] = createBindingsStringEvaluator(bindingsString));
|
1855
1941
|
}
|
1856
1942
|
|
1857
|
-
function createBindingsStringEvaluator(bindingsString
|
1858
|
-
|
1859
|
-
|
1943
|
+
function createBindingsStringEvaluator(bindingsString) {
|
1944
|
+
// Build the source for a function that evaluates "expression"
|
1945
|
+
// For each scope variable, add an extra level of "with" nesting
|
1946
|
+
// Example result: with(sc1) { with(sc0) { return (expression) } }
|
1947
|
+
var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString),
|
1948
|
+
functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
|
1949
|
+
return new Function("$context", "$element", functionBody);
|
1860
1950
|
}
|
1861
1951
|
})();
|
1862
1952
|
|
@@ -1864,7 +1954,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
1864
1954
|
(function () {
|
1865
1955
|
ko.bindingHandlers = {};
|
1866
1956
|
|
1867
|
-
ko.bindingContext = function(dataItem, parentBindingContext) {
|
1957
|
+
ko.bindingContext = function(dataItem, parentBindingContext, dataItemAlias) {
|
1868
1958
|
if (parentBindingContext) {
|
1869
1959
|
ko.utils.extend(this, parentBindingContext); // Inherit $root and any custom properties
|
1870
1960
|
this['$parentContext'] = parentBindingContext;
|
@@ -1874,11 +1964,17 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
1874
1964
|
} else {
|
1875
1965
|
this['$parents'] = [];
|
1876
1966
|
this['$root'] = dataItem;
|
1967
|
+
// Export 'ko' in the binding context so it will be available in bindings and templates
|
1968
|
+
// even if 'ko' isn't exported as a global, such as when using an AMD loader.
|
1969
|
+
// See https://github.com/SteveSanderson/knockout/issues/490
|
1970
|
+
this['ko'] = ko;
|
1877
1971
|
}
|
1878
1972
|
this['$data'] = dataItem;
|
1973
|
+
if (dataItemAlias)
|
1974
|
+
this[dataItemAlias] = dataItem;
|
1879
1975
|
}
|
1880
|
-
ko.bindingContext.prototype['createChildContext'] = function (dataItem) {
|
1881
|
-
return new ko.bindingContext(dataItem, this);
|
1976
|
+
ko.bindingContext.prototype['createChildContext'] = function (dataItem, dataItemAlias) {
|
1977
|
+
return new ko.bindingContext(dataItem, this, dataItemAlias);
|
1882
1978
|
};
|
1883
1979
|
ko.bindingContext.prototype['extend'] = function(properties) {
|
1884
1980
|
var clone = ko.utils.extend(new ko.bindingContext(), this);
|
@@ -1961,7 +2057,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
1961
2057
|
ko.storedBindingContextForNode(node, bindingContextInstance);
|
1962
2058
|
|
1963
2059
|
// Use evaluatedBindings if given, otherwise fall back on asking the bindings provider to give us some bindings
|
1964
|
-
var evaluatedBindings = (typeof bindings == "function") ? bindings() : bindings;
|
2060
|
+
var evaluatedBindings = (typeof bindings == "function") ? bindings(bindingContextInstance, node) : bindings;
|
1965
2061
|
parsedBindings = evaluatedBindings || ko.bindingProvider['instance']['getBindings'](node, bindingContextInstance);
|
1966
2062
|
|
1967
2063
|
if (parsedBindings) {
|
@@ -2001,7 +2097,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2001
2097
|
}
|
2002
2098
|
},
|
2003
2099
|
null,
|
2004
|
-
{
|
2100
|
+
{ disposeWhenNodeIsRemoved : node }
|
2005
2101
|
);
|
2006
2102
|
|
2007
2103
|
return {
|
@@ -2061,10 +2157,128 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2061
2157
|
ko.exportSymbol('contextFor', ko.contextFor);
|
2062
2158
|
ko.exportSymbol('dataFor', ko.dataFor);
|
2063
2159
|
})();
|
2160
|
+
var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
|
2161
|
+
ko.bindingHandlers['attr'] = {
|
2162
|
+
'update': function(element, valueAccessor, allBindingsAccessor) {
|
2163
|
+
var value = ko.utils.unwrapObservable(valueAccessor()) || {};
|
2164
|
+
for (var attrName in value) {
|
2165
|
+
if (typeof attrName == "string") {
|
2166
|
+
var attrValue = ko.utils.unwrapObservable(value[attrName]);
|
2167
|
+
|
2168
|
+
// To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
|
2169
|
+
// when someProp is a "no value"-like value (strictly null, false, or undefined)
|
2170
|
+
// (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
|
2171
|
+
var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
|
2172
|
+
if (toRemove)
|
2173
|
+
element.removeAttribute(attrName);
|
2174
|
+
|
2175
|
+
// In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
|
2176
|
+
// HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
|
2177
|
+
// but instead of figuring out the mode, we'll just set the attribute through the Javascript
|
2178
|
+
// property for IE <= 8.
|
2179
|
+
if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
|
2180
|
+
attrName = attrHtmlToJavascriptMap[attrName];
|
2181
|
+
if (toRemove)
|
2182
|
+
element.removeAttribute(attrName);
|
2183
|
+
else
|
2184
|
+
element[attrName] = attrValue;
|
2185
|
+
} else if (!toRemove) {
|
2186
|
+
element.setAttribute(attrName, attrValue.toString());
|
2187
|
+
}
|
2188
|
+
|
2189
|
+
// Treat "name" specially - although you can think of it as an attribute, it also needs
|
2190
|
+
// special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
|
2191
|
+
// Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
|
2192
|
+
// entirely, and there's no strong reason to allow for such casing in HTML.
|
2193
|
+
if (attrName === "name") {
|
2194
|
+
ko.utils.setElementName(element, toRemove ? "" : attrValue.toString());
|
2195
|
+
}
|
2196
|
+
}
|
2197
|
+
}
|
2198
|
+
}
|
2199
|
+
};
|
2200
|
+
ko.bindingHandlers['checked'] = {
|
2201
|
+
'init': function (element, valueAccessor, allBindingsAccessor) {
|
2202
|
+
var updateHandler = function() {
|
2203
|
+
var valueToWrite;
|
2204
|
+
if (element.type == "checkbox") {
|
2205
|
+
valueToWrite = element.checked;
|
2206
|
+
} else if ((element.type == "radio") && (element.checked)) {
|
2207
|
+
valueToWrite = element.value;
|
2208
|
+
} else {
|
2209
|
+
return; // "checked" binding only responds to checkboxes and selected radio buttons
|
2210
|
+
}
|
2211
|
+
|
2212
|
+
var modelValue = valueAccessor(), unwrappedValue = ko.utils.unwrapObservable(modelValue);
|
2213
|
+
if ((element.type == "checkbox") && (unwrappedValue instanceof Array)) {
|
2214
|
+
// For checkboxes bound to an array, we add/remove the checkbox value to that array
|
2215
|
+
// This works for both observable and non-observable arrays
|
2216
|
+
var existingEntryIndex = ko.utils.arrayIndexOf(unwrappedValue, element.value);
|
2217
|
+
if (element.checked && (existingEntryIndex < 0))
|
2218
|
+
modelValue.push(element.value);
|
2219
|
+
else if ((!element.checked) && (existingEntryIndex >= 0))
|
2220
|
+
modelValue.splice(existingEntryIndex, 1);
|
2221
|
+
} else {
|
2222
|
+
ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
|
2223
|
+
}
|
2224
|
+
};
|
2225
|
+
ko.utils.registerEventHandler(element, "click", updateHandler);
|
2226
|
+
|
2227
|
+
// IE 6 won't allow radio buttons to be selected unless they have a name
|
2228
|
+
if ((element.type == "radio") && !element.name)
|
2229
|
+
ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
|
2230
|
+
},
|
2231
|
+
'update': function (element, valueAccessor) {
|
2232
|
+
var value = ko.utils.unwrapObservable(valueAccessor());
|
2233
|
+
|
2234
|
+
if (element.type == "checkbox") {
|
2235
|
+
if (value instanceof Array) {
|
2236
|
+
// When bound to an array, the checkbox being checked represents its value being present in that array
|
2237
|
+
element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0;
|
2238
|
+
} else {
|
2239
|
+
// When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
|
2240
|
+
element.checked = value;
|
2241
|
+
}
|
2242
|
+
} else if (element.type == "radio") {
|
2243
|
+
element.checked = (element.value == value);
|
2244
|
+
}
|
2245
|
+
}
|
2246
|
+
};
|
2247
|
+
var classesWrittenByBindingKey = '__ko__cssValue';
|
2248
|
+
ko.bindingHandlers['css'] = {
|
2249
|
+
'update': function (element, valueAccessor) {
|
2250
|
+
var value = ko.utils.unwrapObservable(valueAccessor());
|
2251
|
+
if (typeof value == "object") {
|
2252
|
+
for (var className in value) {
|
2253
|
+
var shouldHaveClass = ko.utils.unwrapObservable(value[className]);
|
2254
|
+
ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
|
2255
|
+
}
|
2256
|
+
} else {
|
2257
|
+
value = String(value || ''); // Make sure we don't try to store or set a non-string value
|
2258
|
+
ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
|
2259
|
+
element[classesWrittenByBindingKey] = value;
|
2260
|
+
ko.utils.toggleDomNodeCssClass(element, value, true);
|
2261
|
+
}
|
2262
|
+
}
|
2263
|
+
};
|
2264
|
+
ko.bindingHandlers['enable'] = {
|
2265
|
+
'update': function (element, valueAccessor) {
|
2266
|
+
var value = ko.utils.unwrapObservable(valueAccessor());
|
2267
|
+
if (value && element.disabled)
|
2268
|
+
element.removeAttribute("disabled");
|
2269
|
+
else if ((!value) && (!element.disabled))
|
2270
|
+
element.disabled = true;
|
2271
|
+
}
|
2272
|
+
};
|
2273
|
+
|
2274
|
+
ko.bindingHandlers['disable'] = {
|
2275
|
+
'update': function (element, valueAccessor) {
|
2276
|
+
ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
|
2277
|
+
}
|
2278
|
+
};
|
2064
2279
|
// For certain common events (currently just 'click'), allow a simplified data-binding syntax
|
2065
2280
|
// e.g. click:handler instead of the usual full-length event:{click:handler}
|
2066
|
-
|
2067
|
-
ko.utils.arrayForEach(eventHandlersWithShortcuts, function(eventName) {
|
2281
|
+
function makeEventHandlerShortcut(eventName) {
|
2068
2282
|
ko.bindingHandlers[eventName] = {
|
2069
2283
|
'init': function(element, valueAccessor, allBindingsAccessor, viewModel) {
|
2070
2284
|
var newValueAccessor = function () {
|
@@ -2075,8 +2289,7 @@ ko.utils.arrayForEach(eventHandlersWithShortcuts, function(eventName) {
|
|
2075
2289
|
return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindingsAccessor, viewModel);
|
2076
2290
|
}
|
2077
2291
|
}
|
2078
|
-
}
|
2079
|
-
|
2292
|
+
}
|
2080
2293
|
|
2081
2294
|
ko.bindingHandlers['event'] = {
|
2082
2295
|
'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) {
|
@@ -2118,54 +2331,134 @@ ko.bindingHandlers['event'] = {
|
|
2118
2331
|
}
|
2119
2332
|
}
|
2120
2333
|
};
|
2334
|
+
// "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
|
2335
|
+
// "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
|
2336
|
+
ko.bindingHandlers['foreach'] = {
|
2337
|
+
makeTemplateValueAccessor: function(valueAccessor) {
|
2338
|
+
return function() {
|
2339
|
+
var modelValue = valueAccessor(),
|
2340
|
+
unwrappedValue = ko.utils.peekObservable(modelValue); // Unwrap without setting a dependency here
|
2121
2341
|
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2125
|
-
|
2126
|
-
|
2127
|
-
var handlerReturnValue;
|
2128
|
-
var value = valueAccessor();
|
2129
|
-
try { handlerReturnValue = value.call(viewModel, element); }
|
2130
|
-
finally {
|
2131
|
-
if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
|
2132
|
-
if (event.preventDefault)
|
2133
|
-
event.preventDefault();
|
2134
|
-
else
|
2135
|
-
event.returnValue = false;
|
2136
|
-
}
|
2137
|
-
}
|
2138
|
-
});
|
2139
|
-
}
|
2140
|
-
};
|
2342
|
+
// If unwrappedValue is the array, pass in the wrapped value on its own
|
2343
|
+
// The value will be unwrapped and tracked within the template binding
|
2344
|
+
// (See https://github.com/SteveSanderson/knockout/issues/523)
|
2345
|
+
if ((!unwrappedValue) || typeof unwrappedValue.length == "number")
|
2346
|
+
return { 'foreach': modelValue, 'templateEngine': ko.nativeTemplateEngine.instance };
|
2141
2347
|
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2145
|
-
|
2146
|
-
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2348
|
+
// If unwrappedValue.data is the array, preserve all relevant options and unwrap again value so we get updates
|
2349
|
+
ko.utils.unwrapObservable(modelValue);
|
2350
|
+
return {
|
2351
|
+
'foreach': unwrappedValue['data'],
|
2352
|
+
'as': unwrappedValue['as'],
|
2353
|
+
'includeDestroyed': unwrappedValue['includeDestroyed'],
|
2354
|
+
'afterAdd': unwrappedValue['afterAdd'],
|
2355
|
+
'beforeRemove': unwrappedValue['beforeRemove'],
|
2356
|
+
'afterRender': unwrappedValue['afterRender'],
|
2357
|
+
'beforeMove': unwrappedValue['beforeMove'],
|
2358
|
+
'afterMove': unwrappedValue['afterMove'],
|
2359
|
+
'templateEngine': ko.nativeTemplateEngine.instance
|
2360
|
+
};
|
2361
|
+
};
|
2362
|
+
},
|
2363
|
+
'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2364
|
+
return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
|
2365
|
+
},
|
2366
|
+
'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2367
|
+
return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
|
2150
2368
|
}
|
2151
|
-
}
|
2369
|
+
};
|
2370
|
+
ko.expressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
|
2371
|
+
ko.virtualElements.allowedBindings['foreach'] = true;
|
2372
|
+
var hasfocusUpdatingProperty = '__ko_hasfocusUpdating';
|
2373
|
+
ko.bindingHandlers['hasfocus'] = {
|
2374
|
+
'init': function(element, valueAccessor, allBindingsAccessor) {
|
2375
|
+
var handleElementFocusChange = function(isFocused) {
|
2376
|
+
// Where possible, ignore which event was raised and determine focus state using activeElement,
|
2377
|
+
// as this avoids phantom focus/blur events raised when changing tabs in modern browsers.
|
2378
|
+
// However, not all KO-targeted browsers (Firefox 2) support activeElement. For those browsers,
|
2379
|
+
// prevent a loss of focus when changing tabs/windows by setting a flag that prevents hasfocus
|
2380
|
+
// from calling 'blur()' on the element when it loses focus.
|
2381
|
+
// Discussion at https://github.com/SteveSanderson/knockout/pull/352
|
2382
|
+
element[hasfocusUpdatingProperty] = true;
|
2383
|
+
var ownerDoc = element.ownerDocument;
|
2384
|
+
if ("activeElement" in ownerDoc) {
|
2385
|
+
isFocused = (ownerDoc.activeElement === element);
|
2386
|
+
}
|
2387
|
+
var modelValue = valueAccessor();
|
2388
|
+
ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'hasfocus', isFocused, true);
|
2389
|
+
element[hasfocusUpdatingProperty] = false;
|
2390
|
+
};
|
2391
|
+
var handleElementFocusIn = handleElementFocusChange.bind(null, true);
|
2392
|
+
var handleElementFocusOut = handleElementFocusChange.bind(null, false);
|
2152
2393
|
|
2153
|
-
ko.
|
2154
|
-
|
2394
|
+
ko.utils.registerEventHandler(element, "focus", handleElementFocusIn);
|
2395
|
+
ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE
|
2396
|
+
ko.utils.registerEventHandler(element, "blur", handleElementFocusOut);
|
2397
|
+
ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
|
2398
|
+
},
|
2399
|
+
'update': function(element, valueAccessor) {
|
2155
2400
|
var value = ko.utils.unwrapObservable(valueAccessor());
|
2156
|
-
if (
|
2157
|
-
element.
|
2158
|
-
|
2159
|
-
|
2401
|
+
if (!element[hasfocusUpdatingProperty]) {
|
2402
|
+
value ? element.focus() : element.blur();
|
2403
|
+
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
|
2404
|
+
}
|
2160
2405
|
}
|
2161
2406
|
};
|
2162
|
-
|
2163
|
-
|
2407
|
+
ko.bindingHandlers['html'] = {
|
2408
|
+
'init': function() {
|
2409
|
+
// Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
|
2410
|
+
return { 'controlsDescendantBindings': true };
|
2411
|
+
},
|
2164
2412
|
'update': function (element, valueAccessor) {
|
2165
|
-
|
2413
|
+
// setHtml will unwrap the value if needed
|
2414
|
+
ko.utils.setHtml(element, valueAccessor());
|
2166
2415
|
}
|
2167
2416
|
};
|
2417
|
+
var withIfDomDataKey = '__ko_withIfBindingData';
|
2418
|
+
// Makes a binding like with or if
|
2419
|
+
function makeWithIfBinding(bindingKey, isWith, isNot, makeContextCallback) {
|
2420
|
+
ko.bindingHandlers[bindingKey] = {
|
2421
|
+
'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2422
|
+
ko.utils.domData.set(element, withIfDomDataKey, {});
|
2423
|
+
return { 'controlsDescendantBindings': true };
|
2424
|
+
},
|
2425
|
+
'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2426
|
+
var withIfData = ko.utils.domData.get(element, withIfDomDataKey),
|
2427
|
+
dataValue = ko.utils.unwrapObservable(valueAccessor()),
|
2428
|
+
shouldDisplay = !isNot !== !dataValue, // equivalent to isNot ? !dataValue : !!dataValue
|
2429
|
+
isFirstRender = !withIfData.savedNodes,
|
2430
|
+
needsRefresh = isFirstRender || isWith || (shouldDisplay !== withIfData.didDisplayOnLastUpdate);
|
2431
|
+
|
2432
|
+
if (needsRefresh) {
|
2433
|
+
if (isFirstRender) {
|
2434
|
+
withIfData.savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */);
|
2435
|
+
}
|
2168
2436
|
|
2437
|
+
if (shouldDisplay) {
|
2438
|
+
if (!isFirstRender) {
|
2439
|
+
ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(withIfData.savedNodes));
|
2440
|
+
}
|
2441
|
+
ko.applyBindingsToDescendants(makeContextCallback ? makeContextCallback(bindingContext, dataValue) : bindingContext, element);
|
2442
|
+
} else {
|
2443
|
+
ko.virtualElements.emptyNode(element);
|
2444
|
+
}
|
2445
|
+
|
2446
|
+
withIfData.didDisplayOnLastUpdate = shouldDisplay;
|
2447
|
+
}
|
2448
|
+
}
|
2449
|
+
};
|
2450
|
+
ko.expressionRewriting.bindingRewriteValidators[bindingKey] = false; // Can't rewrite control flow bindings
|
2451
|
+
ko.virtualElements.allowedBindings[bindingKey] = true;
|
2452
|
+
}
|
2453
|
+
|
2454
|
+
// Construct the actual binding handlers
|
2455
|
+
makeWithIfBinding('if');
|
2456
|
+
makeWithIfBinding('ifnot', false /* isWith */, true /* isNot */);
|
2457
|
+
makeWithIfBinding('with', true /* isWith */, false /* isNot */,
|
2458
|
+
function(bindingContext, dataValue) {
|
2459
|
+
return bindingContext['createChildContext'](dataValue);
|
2460
|
+
}
|
2461
|
+
);
|
2169
2462
|
function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) {
|
2170
2463
|
if (preferModelValue) {
|
2171
2464
|
if (modelValue !== ko.selectExtensions.readValue(element))
|
@@ -2176,82 +2469,7 @@ function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue,
|
|
2176
2469
|
// If they aren't equal, either we prefer the dropdown value, or the model value couldn't be represented, so either way,
|
2177
2470
|
// change the model value to match the dropdown.
|
2178
2471
|
if (modelValue !== ko.selectExtensions.readValue(element))
|
2179
|
-
ko.utils.triggerEvent
|
2180
|
-
};
|
2181
|
-
|
2182
|
-
ko.bindingHandlers['value'] = {
|
2183
|
-
'init': function (element, valueAccessor, allBindingsAccessor) {
|
2184
|
-
// Always catch "change" event; possibly other events too if asked
|
2185
|
-
var eventsToCatch = ["change"];
|
2186
|
-
var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
|
2187
|
-
if (requestedEventsToCatch) {
|
2188
|
-
if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
|
2189
|
-
requestedEventsToCatch = [requestedEventsToCatch];
|
2190
|
-
ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
|
2191
|
-
eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
|
2192
|
-
}
|
2193
|
-
|
2194
|
-
var valueUpdateHandler = function() {
|
2195
|
-
var modelValue = valueAccessor();
|
2196
|
-
var elementValue = ko.selectExtensions.readValue(element);
|
2197
|
-
ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue, /* checkIfDifferent: */ true);
|
2198
|
-
}
|
2199
|
-
|
2200
|
-
// Workaround for https://github.com/SteveSanderson/knockout/issues/122
|
2201
|
-
// IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
|
2202
|
-
var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
|
2203
|
-
&& element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
|
2204
|
-
if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
|
2205
|
-
var propertyChangedFired = false;
|
2206
|
-
ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
|
2207
|
-
ko.utils.registerEventHandler(element, "blur", function() {
|
2208
|
-
if (propertyChangedFired) {
|
2209
|
-
propertyChangedFired = false;
|
2210
|
-
valueUpdateHandler();
|
2211
|
-
}
|
2212
|
-
});
|
2213
|
-
}
|
2214
|
-
|
2215
|
-
ko.utils.arrayForEach(eventsToCatch, function(eventName) {
|
2216
|
-
// The syntax "after<eventname>" means "run the handler asynchronously after the event"
|
2217
|
-
// This is useful, for example, to catch "keydown" events after the browser has updated the control
|
2218
|
-
// (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
|
2219
|
-
var handler = valueUpdateHandler;
|
2220
|
-
if (ko.utils.stringStartsWith(eventName, "after")) {
|
2221
|
-
handler = function() { setTimeout(valueUpdateHandler, 0) };
|
2222
|
-
eventName = eventName.substring("after".length);
|
2223
|
-
}
|
2224
|
-
ko.utils.registerEventHandler(element, eventName, handler);
|
2225
|
-
});
|
2226
|
-
},
|
2227
|
-
'update': function (element, valueAccessor) {
|
2228
|
-
var valueIsSelectOption = ko.utils.tagNameLower(element) === "select";
|
2229
|
-
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
2230
|
-
var elementValue = ko.selectExtensions.readValue(element);
|
2231
|
-
var valueHasChanged = (newValue != elementValue);
|
2232
|
-
|
2233
|
-
// 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).
|
2234
|
-
// 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.
|
2235
|
-
if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0"))
|
2236
|
-
valueHasChanged = true;
|
2237
|
-
|
2238
|
-
if (valueHasChanged) {
|
2239
|
-
var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
|
2240
|
-
applyValueAction();
|
2241
|
-
|
2242
|
-
// Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
|
2243
|
-
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
|
2244
|
-
// to apply the value as well.
|
2245
|
-
var alsoApplyAsynchronously = valueIsSelectOption;
|
2246
|
-
if (alsoApplyAsynchronously)
|
2247
|
-
setTimeout(applyValueAction, 0);
|
2248
|
-
}
|
2249
|
-
|
2250
|
-
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
|
2251
|
-
// because you're not allowed to have a model value that disagrees with a visible UI selection.
|
2252
|
-
if (valueIsSelectOption && (element.length > 0))
|
2253
|
-
ensureDropdownSelectionIsConsistentWithModelValue(element, newValue, /* preferModelValue */ false);
|
2254
|
-
}
|
2472
|
+
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
|
2255
2473
|
};
|
2256
2474
|
|
2257
2475
|
ko.bindingHandlers['options'] = {
|
@@ -2278,7 +2496,9 @@ ko.bindingHandlers['options'] = {
|
|
2278
2496
|
}
|
2279
2497
|
|
2280
2498
|
if (value) {
|
2281
|
-
var allBindings = allBindingsAccessor()
|
2499
|
+
var allBindings = allBindingsAccessor(),
|
2500
|
+
includeDestroyed = allBindings['optionsIncludeDestroyed'];
|
2501
|
+
|
2282
2502
|
if (typeof value.length != "number")
|
2283
2503
|
value = [value];
|
2284
2504
|
if (allBindings['optionsCaption']) {
|
@@ -2287,26 +2507,31 @@ ko.bindingHandlers['options'] = {
|
|
2287
2507
|
ko.selectExtensions.writeValue(option, undefined);
|
2288
2508
|
element.appendChild(option);
|
2289
2509
|
}
|
2510
|
+
|
2290
2511
|
for (var i = 0, j = value.length; i < j; i++) {
|
2512
|
+
// Skip destroyed items
|
2513
|
+
var arrayEntry = value[i];
|
2514
|
+
if (arrayEntry && arrayEntry['_destroy'] && !includeDestroyed)
|
2515
|
+
continue;
|
2516
|
+
|
2291
2517
|
var option = document.createElement("option");
|
2292
2518
|
|
2519
|
+
function applyToObject(object, predicate, defaultValue) {
|
2520
|
+
var predicateType = typeof predicate;
|
2521
|
+
if (predicateType == "function") // Given a function; run it against the data value
|
2522
|
+
return predicate(object);
|
2523
|
+
else if (predicateType == "string") // Given a string; treat it as a property name on the data value
|
2524
|
+
return object[predicate];
|
2525
|
+
else // Given no optionsText arg; use the data value itself
|
2526
|
+
return defaultValue;
|
2527
|
+
}
|
2528
|
+
|
2293
2529
|
// Apply a value to the option element
|
2294
|
-
var optionValue =
|
2295
|
-
|
2296
|
-
ko.selectExtensions.writeValue(option, optionValue);
|
2530
|
+
var optionValue = applyToObject(arrayEntry, allBindings['optionsValue'], arrayEntry);
|
2531
|
+
ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue));
|
2297
2532
|
|
2298
2533
|
// Apply some text to the option element
|
2299
|
-
var
|
2300
|
-
var optionText;
|
2301
|
-
if (typeof optionsTextValue == "function")
|
2302
|
-
optionText = optionsTextValue(value[i]); // Given a function; run it against the data value
|
2303
|
-
else if (typeof optionsTextValue == "string")
|
2304
|
-
optionText = value[i][optionsTextValue]; // Given a string; treat it as a property name on the data value
|
2305
|
-
else
|
2306
|
-
optionText = optionValue; // Given no optionsText arg; use the data value itself
|
2307
|
-
if ((optionText === null) || (optionText === undefined))
|
2308
|
-
optionText = "";
|
2309
|
-
|
2534
|
+
var optionText = applyToObject(arrayEntry, allBindings['optionsText'], optionValue);
|
2310
2535
|
ko.utils.setTextContent(option, optionText);
|
2311
2536
|
|
2312
2537
|
element.appendChild(option);
|
@@ -2329,7 +2554,7 @@ ko.bindingHandlers['options'] = {
|
|
2329
2554
|
// Ensure consistency between model value and selected option.
|
2330
2555
|
// If the dropdown is being populated for the first time here (or was otherwise previously empty),
|
2331
2556
|
// the dropdown selection state is meaningless, so we preserve the model value.
|
2332
|
-
ensureDropdownSelectionIsConsistentWithModelValue(element, ko.utils.
|
2557
|
+
ensureDropdownSelectionIsConsistentWithModelValue(element, ko.utils.peekObservable(allBindings['value']), /* preferModelValue */ true);
|
2333
2558
|
}
|
2334
2559
|
|
2335
2560
|
// Workaround for IE9 bug
|
@@ -2338,27 +2563,15 @@ ko.bindingHandlers['options'] = {
|
|
2338
2563
|
}
|
2339
2564
|
};
|
2340
2565
|
ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.optionValueDomData__';
|
2341
|
-
|
2342
2566
|
ko.bindingHandlers['selectedOptions'] = {
|
2343
|
-
getSelectedValuesFromSelectNode: function (selectNode) {
|
2344
|
-
var result = [];
|
2345
|
-
var nodes = selectNode.childNodes;
|
2346
|
-
for (var i = 0, j = nodes.length; i < j; i++) {
|
2347
|
-
var node = nodes[i], tagName = ko.utils.tagNameLower(node);
|
2348
|
-
if (tagName == "option" && node.selected)
|
2349
|
-
result.push(ko.selectExtensions.readValue(node));
|
2350
|
-
else if (tagName == "optgroup") {
|
2351
|
-
var selectedValuesFromOptGroup = ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(node);
|
2352
|
-
Array.prototype.splice.apply(result, [result.length, 0].concat(selectedValuesFromOptGroup)); // Add new entries to existing 'result' instance
|
2353
|
-
}
|
2354
|
-
}
|
2355
|
-
return result;
|
2356
|
-
},
|
2357
2567
|
'init': function (element, valueAccessor, allBindingsAccessor) {
|
2358
2568
|
ko.utils.registerEventHandler(element, "change", function () {
|
2359
|
-
var value = valueAccessor();
|
2360
|
-
|
2361
|
-
|
2569
|
+
var value = valueAccessor(), valueToWrite = [];
|
2570
|
+
ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
|
2571
|
+
if (node.selected)
|
2572
|
+
valueToWrite.push(ko.selectExtensions.readValue(node));
|
2573
|
+
});
|
2574
|
+
ko.expressionRewriting.writeValueToProperty(value, allBindingsAccessor, 'value', valueToWrite);
|
2362
2575
|
});
|
2363
2576
|
},
|
2364
2577
|
'update': function (element, valueAccessor) {
|
@@ -2367,45 +2580,13 @@ ko.bindingHandlers['selectedOptions'] = {
|
|
2367
2580
|
|
2368
2581
|
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
2369
2582
|
if (newValue && typeof newValue.length == "number") {
|
2370
|
-
|
2371
|
-
|
2372
|
-
|
2373
|
-
|
2374
|
-
ko.utils.setOptionNodeSelectionState(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0);
|
2375
|
-
}
|
2376
|
-
}
|
2377
|
-
}
|
2378
|
-
};
|
2379
|
-
|
2380
|
-
ko.bindingHandlers['text'] = {
|
2381
|
-
'update': function (element, valueAccessor) {
|
2382
|
-
ko.utils.setTextContent(element, valueAccessor());
|
2383
|
-
}
|
2384
|
-
};
|
2385
|
-
|
2386
|
-
ko.bindingHandlers['html'] = {
|
2387
|
-
'init': function() {
|
2388
|
-
// Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
|
2389
|
-
return { 'controlsDescendantBindings': true };
|
2390
|
-
},
|
2391
|
-
'update': function (element, valueAccessor) {
|
2392
|
-
var value = ko.utils.unwrapObservable(valueAccessor());
|
2393
|
-
ko.utils.setHtml(element, value);
|
2394
|
-
}
|
2395
|
-
};
|
2396
|
-
|
2397
|
-
ko.bindingHandlers['css'] = {
|
2398
|
-
'update': function (element, valueAccessor) {
|
2399
|
-
var value = ko.utils.unwrapObservable(valueAccessor() || {});
|
2400
|
-
for (var className in value) {
|
2401
|
-
if (typeof className == "string") {
|
2402
|
-
var shouldHaveClass = ko.utils.unwrapObservable(value[className]);
|
2403
|
-
ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
|
2404
|
-
}
|
2583
|
+
ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
|
2584
|
+
var isSelected = ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0;
|
2585
|
+
ko.utils.setOptionNodeSelectionState(node, isSelected);
|
2586
|
+
});
|
2405
2587
|
}
|
2406
2588
|
}
|
2407
2589
|
};
|
2408
|
-
|
2409
2590
|
ko.bindingHandlers['style'] = {
|
2410
2591
|
'update': function (element, valueAccessor) {
|
2411
2592
|
var value = ko.utils.unwrapObservable(valueAccessor() || {});
|
@@ -2417,197 +2598,126 @@ ko.bindingHandlers['style'] = {
|
|
2417
2598
|
}
|
2418
2599
|
}
|
2419
2600
|
};
|
2420
|
-
|
2601
|
+
ko.bindingHandlers['submit'] = {
|
2602
|
+
'init': function (element, valueAccessor, allBindingsAccessor, viewModel) {
|
2603
|
+
if (typeof valueAccessor() != "function")
|
2604
|
+
throw new Error("The value for a submit binding must be a function");
|
2605
|
+
ko.utils.registerEventHandler(element, "submit", function (event) {
|
2606
|
+
var handlerReturnValue;
|
2607
|
+
var value = valueAccessor();
|
2608
|
+
try { handlerReturnValue = value.call(viewModel, element); }
|
2609
|
+
finally {
|
2610
|
+
if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
|
2611
|
+
if (event.preventDefault)
|
2612
|
+
event.preventDefault();
|
2613
|
+
else
|
2614
|
+
event.returnValue = false;
|
2615
|
+
}
|
2616
|
+
}
|
2617
|
+
});
|
2618
|
+
}
|
2619
|
+
};
|
2620
|
+
ko.bindingHandlers['text'] = {
|
2621
|
+
'update': function (element, valueAccessor) {
|
2622
|
+
ko.utils.setTextContent(element, valueAccessor());
|
2623
|
+
}
|
2624
|
+
};
|
2625
|
+
ko.virtualElements.allowedBindings['text'] = true;
|
2421
2626
|
ko.bindingHandlers['uniqueName'] = {
|
2422
2627
|
'init': function (element, valueAccessor) {
|
2423
2628
|
if (valueAccessor()) {
|
2424
|
-
|
2425
|
-
|
2426
|
-
// Workaround IE 6/7 issue
|
2427
|
-
// - https://github.com/SteveSanderson/knockout/issues/197
|
2428
|
-
// - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
|
2429
|
-
if (ko.utils.isIe6 || ko.utils.isIe7)
|
2430
|
-
element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
|
2629
|
+
var name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
|
2630
|
+
ko.utils.setElementName(element, name);
|
2431
2631
|
}
|
2432
2632
|
}
|
2433
2633
|
};
|
2434
2634
|
ko.bindingHandlers['uniqueName'].currentIndex = 0;
|
2435
|
-
|
2436
|
-
ko.bindingHandlers['checked'] = {
|
2635
|
+
ko.bindingHandlers['value'] = {
|
2437
2636
|
'init': function (element, valueAccessor, allBindingsAccessor) {
|
2438
|
-
|
2439
|
-
|
2440
|
-
|
2441
|
-
|
2442
|
-
|
2443
|
-
|
2444
|
-
|
2445
|
-
|
2446
|
-
|
2637
|
+
// Always catch "change" event; possibly other events too if asked
|
2638
|
+
var eventsToCatch = ["change"];
|
2639
|
+
var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
|
2640
|
+
var propertyChangedFired = false;
|
2641
|
+
if (requestedEventsToCatch) {
|
2642
|
+
if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
|
2643
|
+
requestedEventsToCatch = [requestedEventsToCatch];
|
2644
|
+
ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
|
2645
|
+
eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
|
2646
|
+
}
|
2447
2647
|
|
2648
|
+
var valueUpdateHandler = function() {
|
2649
|
+
propertyChangedFired = false;
|
2448
2650
|
var modelValue = valueAccessor();
|
2449
|
-
|
2450
|
-
|
2451
|
-
// This works for both observable and non-observable arrays
|
2452
|
-
var existingEntryIndex = ko.utils.arrayIndexOf(ko.utils.unwrapObservable(modelValue), element.value);
|
2453
|
-
if (element.checked && (existingEntryIndex < 0))
|
2454
|
-
modelValue.push(element.value);
|
2455
|
-
else if ((!element.checked) && (existingEntryIndex >= 0))
|
2456
|
-
modelValue.splice(existingEntryIndex, 1);
|
2457
|
-
} else {
|
2458
|
-
ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
|
2459
|
-
}
|
2460
|
-
};
|
2461
|
-
ko.utils.registerEventHandler(element, "click", updateHandler);
|
2462
|
-
|
2463
|
-
// IE 6 won't allow radio buttons to be selected unless they have a name
|
2464
|
-
if ((element.type == "radio") && !element.name)
|
2465
|
-
ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
|
2466
|
-
},
|
2467
|
-
'update': function (element, valueAccessor) {
|
2468
|
-
var value = ko.utils.unwrapObservable(valueAccessor());
|
2469
|
-
|
2470
|
-
if (element.type == "checkbox") {
|
2471
|
-
if (value instanceof Array) {
|
2472
|
-
// When bound to an array, the checkbox being checked represents its value being present in that array
|
2473
|
-
element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0;
|
2474
|
-
} else {
|
2475
|
-
// When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
|
2476
|
-
element.checked = value;
|
2477
|
-
}
|
2478
|
-
} else if (element.type == "radio") {
|
2479
|
-
element.checked = (element.value == value);
|
2651
|
+
var elementValue = ko.selectExtensions.readValue(element);
|
2652
|
+
ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue);
|
2480
2653
|
}
|
2481
|
-
}
|
2482
|
-
};
|
2483
|
-
|
2484
|
-
var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
|
2485
|
-
ko.bindingHandlers['attr'] = {
|
2486
|
-
'update': function(element, valueAccessor, allBindingsAccessor) {
|
2487
|
-
var value = ko.utils.unwrapObservable(valueAccessor()) || {};
|
2488
|
-
for (var attrName in value) {
|
2489
|
-
if (typeof attrName == "string") {
|
2490
|
-
var attrValue = ko.utils.unwrapObservable(value[attrName]);
|
2491
|
-
|
2492
|
-
// To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
|
2493
|
-
// when someProp is a "no value"-like value (strictly null, false, or undefined)
|
2494
|
-
// (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
|
2495
|
-
var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
|
2496
|
-
if (toRemove)
|
2497
|
-
element.removeAttribute(attrName);
|
2498
2654
|
|
2499
|
-
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
2503
|
-
|
2504
|
-
|
2505
|
-
|
2506
|
-
|
2507
|
-
|
2508
|
-
element[attrName] = attrValue;
|
2509
|
-
} else if (!toRemove) {
|
2510
|
-
element.setAttribute(attrName, attrValue.toString());
|
2655
|
+
// Workaround for https://github.com/SteveSanderson/knockout/issues/122
|
2656
|
+
// IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
|
2657
|
+
var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
|
2658
|
+
&& element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
|
2659
|
+
if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
|
2660
|
+
ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
|
2661
|
+
ko.utils.registerEventHandler(element, "blur", function() {
|
2662
|
+
if (propertyChangedFired) {
|
2663
|
+
valueUpdateHandler();
|
2511
2664
|
}
|
2512
|
-
}
|
2665
|
+
});
|
2513
2666
|
}
|
2514
|
-
}
|
2515
|
-
};
|
2516
2667
|
|
2517
|
-
ko.
|
2518
|
-
|
2519
|
-
|
2520
|
-
|
2521
|
-
|
2522
|
-
|
2523
|
-
|
2524
|
-
|
2525
|
-
|
2526
|
-
|
2668
|
+
ko.utils.arrayForEach(eventsToCatch, function(eventName) {
|
2669
|
+
// The syntax "after<eventname>" means "run the handler asynchronously after the event"
|
2670
|
+
// This is useful, for example, to catch "keydown" events after the browser has updated the control
|
2671
|
+
// (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
|
2672
|
+
var handler = valueUpdateHandler;
|
2673
|
+
if (ko.utils.stringStartsWith(eventName, "after")) {
|
2674
|
+
handler = function() { setTimeout(valueUpdateHandler, 0) };
|
2675
|
+
eventName = eventName.substring("after".length);
|
2676
|
+
}
|
2677
|
+
ko.utils.registerEventHandler(element, eventName, handler);
|
2678
|
+
});
|
2527
2679
|
},
|
2528
|
-
'update': function(element, valueAccessor) {
|
2529
|
-
var
|
2530
|
-
|
2531
|
-
ko.
|
2532
|
-
|
2533
|
-
};
|
2680
|
+
'update': function (element, valueAccessor) {
|
2681
|
+
var valueIsSelectOption = ko.utils.tagNameLower(element) === "select";
|
2682
|
+
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
2683
|
+
var elementValue = ko.selectExtensions.readValue(element);
|
2684
|
+
var valueHasChanged = (newValue != elementValue);
|
2534
2685
|
|
2535
|
-
//
|
2536
|
-
|
2537
|
-
|
2538
|
-
|
2539
|
-
},
|
2540
|
-
'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2541
|
-
return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor));
|
2542
|
-
},
|
2543
|
-
'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2544
|
-
return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['with'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
|
2545
|
-
}
|
2546
|
-
};
|
2547
|
-
ko.jsonExpressionRewriting.bindingRewriteValidators['with'] = false; // Can't rewrite control flow bindings
|
2548
|
-
ko.virtualElements.allowedBindings['with'] = true;
|
2686
|
+
// 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).
|
2687
|
+
// 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.
|
2688
|
+
if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0"))
|
2689
|
+
valueHasChanged = true;
|
2549
2690
|
|
2550
|
-
|
2551
|
-
ko.
|
2552
|
-
|
2553
|
-
return function() { return { 'if': valueAccessor(), 'templateEngine': ko.nativeTemplateEngine.instance } };
|
2554
|
-
},
|
2555
|
-
'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2556
|
-
return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor));
|
2557
|
-
},
|
2558
|
-
'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2559
|
-
return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['if'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
|
2560
|
-
}
|
2561
|
-
};
|
2562
|
-
ko.jsonExpressionRewriting.bindingRewriteValidators['if'] = false; // Can't rewrite control flow bindings
|
2563
|
-
ko.virtualElements.allowedBindings['if'] = true;
|
2691
|
+
if (valueHasChanged) {
|
2692
|
+
var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
|
2693
|
+
applyValueAction();
|
2564
2694
|
|
2565
|
-
//
|
2566
|
-
|
2567
|
-
|
2568
|
-
|
2569
|
-
|
2570
|
-
|
2571
|
-
|
2572
|
-
|
2573
|
-
|
2574
|
-
|
2695
|
+
// Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
|
2696
|
+
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
|
2697
|
+
// to apply the value as well.
|
2698
|
+
var alsoApplyAsynchronously = valueIsSelectOption;
|
2699
|
+
if (alsoApplyAsynchronously)
|
2700
|
+
setTimeout(applyValueAction, 0);
|
2701
|
+
}
|
2702
|
+
|
2703
|
+
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
|
2704
|
+
// because you're not allowed to have a model value that disagrees with a visible UI selection.
|
2705
|
+
if (valueIsSelectOption && (element.length > 0))
|
2706
|
+
ensureDropdownSelectionIsConsistentWithModelValue(element, newValue, /* preferModelValue */ false);
|
2575
2707
|
}
|
2576
2708
|
};
|
2577
|
-
ko.
|
2578
|
-
|
2579
|
-
|
2580
|
-
|
2581
|
-
|
2582
|
-
|
2583
|
-
|
2584
|
-
|
2585
|
-
var bindingValue = ko.utils.unwrapObservable(valueAccessor());
|
2586
|
-
|
2587
|
-
// If bindingValue is the array, just pass it on its own
|
2588
|
-
if ((!bindingValue) || typeof bindingValue.length == "number")
|
2589
|
-
return { 'foreach': bindingValue, 'templateEngine': ko.nativeTemplateEngine.instance };
|
2590
|
-
|
2591
|
-
// If bindingValue.data is the array, preserve all relevant options
|
2592
|
-
return {
|
2593
|
-
'foreach': bindingValue['data'],
|
2594
|
-
'includeDestroyed': bindingValue['includeDestroyed'],
|
2595
|
-
'afterAdd': bindingValue['afterAdd'],
|
2596
|
-
'beforeRemove': bindingValue['beforeRemove'],
|
2597
|
-
'afterRender': bindingValue['afterRender'],
|
2598
|
-
'templateEngine': ko.nativeTemplateEngine.instance
|
2599
|
-
};
|
2600
|
-
};
|
2601
|
-
},
|
2602
|
-
'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2603
|
-
return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
|
2604
|
-
},
|
2605
|
-
'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
2606
|
-
return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
|
2709
|
+
ko.bindingHandlers['visible'] = {
|
2710
|
+
'update': function (element, valueAccessor) {
|
2711
|
+
var value = ko.utils.unwrapObservable(valueAccessor());
|
2712
|
+
var isCurrentlyVisible = !(element.style.display == "none");
|
2713
|
+
if (value && !isCurrentlyVisible)
|
2714
|
+
element.style.display = "";
|
2715
|
+
else if ((!value) && isCurrentlyVisible)
|
2716
|
+
element.style.display = "none";
|
2607
2717
|
}
|
2608
2718
|
};
|
2609
|
-
|
2610
|
-
|
2719
|
+
// 'click' is just a shorthand for the usual full-length event:{click:handler}
|
2720
|
+
makeEventHandlerShortcut('click');
|
2611
2721
|
// If you want to make a custom template engine,
|
2612
2722
|
//
|
2613
2723
|
// [1] Inherit from this class (like ko.nativeTemplateEngine does)
|
@@ -2668,12 +2778,6 @@ ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templat
|
|
2668
2778
|
// Skip rewriting if requested
|
2669
2779
|
if (this['allowTemplateRewriting'] === false)
|
2670
2780
|
return true;
|
2671
|
-
|
2672
|
-
// Perf optimisation - see below
|
2673
|
-
var templateIsInExternalDocument = templateDocument && templateDocument != document;
|
2674
|
-
if (!templateIsInExternalDocument && this.knownRewrittenTemplates && this.knownRewrittenTemplates[template])
|
2675
|
-
return true;
|
2676
|
-
|
2677
2781
|
return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten");
|
2678
2782
|
};
|
2679
2783
|
|
@@ -2682,19 +2786,6 @@ ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCal
|
|
2682
2786
|
var rewritten = rewriterCallback(templateSource['text']());
|
2683
2787
|
templateSource['text'](rewritten);
|
2684
2788
|
templateSource['data']("isRewritten", true);
|
2685
|
-
|
2686
|
-
// Perf optimisation - for named templates, track which ones have been rewritten so we can
|
2687
|
-
// answer 'isTemplateRewritten' *without* having to use getElementById (which is slow on IE < 8)
|
2688
|
-
//
|
2689
|
-
// Note that we only cache the status for templates in the main document, because caching on a per-doc
|
2690
|
-
// basis complicates the implementation excessively. In a future version of KO, we will likely remove
|
2691
|
-
// this 'isRewritten' cache entirely anyway, because the benefit is extremely minor and only applies
|
2692
|
-
// to rewritable templates, which are pretty much deprecated since KO 2.0.
|
2693
|
-
var templateIsInExternalDocument = templateDocument && templateDocument != document;
|
2694
|
-
if (!templateIsInExternalDocument && typeof template == "string") {
|
2695
|
-
this.knownRewrittenTemplates = this.knownRewrittenTemplates || {};
|
2696
|
-
this.knownRewrittenTemplates[template] = true;
|
2697
|
-
}
|
2698
2789
|
};
|
2699
2790
|
|
2700
2791
|
ko.exportSymbol('templateEngine', ko.templateEngine);
|
@@ -2704,7 +2795,7 @@ ko.templateRewriting = (function () {
|
|
2704
2795
|
var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
|
2705
2796
|
|
2706
2797
|
function validateDataBindValuesForRewriting(keyValueArray) {
|
2707
|
-
var allValidators = ko.
|
2798
|
+
var allValidators = ko.expressionRewriting.bindingRewriteValidators;
|
2708
2799
|
for (var i = 0; i < keyValueArray.length; i++) {
|
2709
2800
|
var key = keyValueArray[i]['key'];
|
2710
2801
|
if (allValidators.hasOwnProperty(key)) {
|
@@ -2722,16 +2813,15 @@ ko.templateRewriting = (function () {
|
|
2722
2813
|
}
|
2723
2814
|
|
2724
2815
|
function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, templateEngine) {
|
2725
|
-
var dataBindKeyValueArray = ko.
|
2816
|
+
var dataBindKeyValueArray = ko.expressionRewriting.parseObjectLiteral(dataBindAttributeValue);
|
2726
2817
|
validateDataBindValuesForRewriting(dataBindKeyValueArray);
|
2727
|
-
var rewrittenDataBindAttributeValue = ko.
|
2818
|
+
var rewrittenDataBindAttributeValue = ko.expressionRewriting.preProcessBindings(dataBindKeyValueArray);
|
2728
2819
|
|
2729
2820
|
// For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
|
2730
2821
|
// anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
|
2731
2822
|
// extra indirection.
|
2732
|
-
var applyBindingsToNextSiblingScript =
|
2733
|
-
return
|
2734
|
-
})";
|
2823
|
+
var applyBindingsToNextSiblingScript =
|
2824
|
+
"ko.__tr_ambtns(function($context,$element){return(function(){return{ " + rewrittenDataBindAttributeValue + " } })()})";
|
2735
2825
|
return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
|
2736
2826
|
}
|
2737
2827
|
|
@@ -2760,8 +2850,9 @@ ko.templateRewriting = (function () {
|
|
2760
2850
|
}
|
2761
2851
|
})();
|
2762
2852
|
|
2763
|
-
|
2764
|
-
|
2853
|
+
|
2854
|
+
// Exported only because it has to be referenced by string lookup from within rewritten template
|
2855
|
+
ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextSibling);
|
2765
2856
|
(function() {
|
2766
2857
|
// A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving
|
2767
2858
|
// logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.)
|
@@ -2929,7 +3020,7 @@ ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templ
|
|
2929
3020
|
if (haveAddedNodesToParent) {
|
2930
3021
|
activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
|
2931
3022
|
if (options['afterRender'])
|
2932
|
-
options['afterRender']
|
3023
|
+
ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext['$data']]);
|
2933
3024
|
}
|
2934
3025
|
|
2935
3026
|
return renderedNodesArray;
|
@@ -2955,7 +3046,7 @@ ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templ
|
|
2955
3046
|
: new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
|
2956
3047
|
|
2957
3048
|
// Support selecting template as a function of the data being rendered
|
2958
|
-
var templateName = typeof(template) == 'function' ? template(bindingContext['$data']) : template;
|
3049
|
+
var templateName = typeof(template) == 'function' ? template(bindingContext['$data'], bindingContext) : template;
|
2959
3050
|
|
2960
3051
|
var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
|
2961
3052
|
if (renderMode == "replaceNode") {
|
@@ -2964,7 +3055,7 @@ ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templ
|
|
2964
3055
|
}
|
2965
3056
|
},
|
2966
3057
|
null,
|
2967
|
-
{
|
3058
|
+
{ disposeWhen: whenToDispose, disposeWhenNodeIsRemoved: activelyDisposeWhenNodeIsRemoved }
|
2968
3059
|
);
|
2969
3060
|
} else {
|
2970
3061
|
// 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
|
@@ -2982,9 +3073,9 @@ ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templ
|
|
2982
3073
|
// This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
|
2983
3074
|
var executeTemplateForArrayItem = function (arrayValue, index) {
|
2984
3075
|
// Support selecting template as a function of the data being rendered
|
2985
|
-
|
2986
|
-
arrayItemContext = parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue));
|
3076
|
+
arrayItemContext = parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue), options['as']);
|
2987
3077
|
arrayItemContext['$index'] = index;
|
3078
|
+
var templateName = typeof(template) == 'function' ? template(arrayValue, arrayItemContext) : template;
|
2988
3079
|
return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
|
2989
3080
|
}
|
2990
3081
|
|
@@ -3005,17 +3096,19 @@ ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templ
|
|
3005
3096
|
return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
|
3006
3097
|
});
|
3007
3098
|
|
3008
|
-
|
3099
|
+
// Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
|
3100
|
+
// If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
|
3101
|
+
ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback]);
|
3009
3102
|
|
3010
|
-
}, null, {
|
3103
|
+
}, null, { disposeWhenNodeIsRemoved: targetNode });
|
3011
3104
|
};
|
3012
3105
|
|
3013
|
-
var
|
3014
|
-
function
|
3015
|
-
var
|
3016
|
-
if (
|
3017
|
-
|
3018
|
-
ko.utils.domData.set(element,
|
3106
|
+
var templateComputedDomDataKey = '__ko__templateComputedDomDataKey__';
|
3107
|
+
function disposeOldComputedAndStoreNewOne(element, newComputed) {
|
3108
|
+
var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
|
3109
|
+
if (oldComputed && (typeof(oldComputed.dispose) == 'function'))
|
3110
|
+
oldComputed.dispose();
|
3111
|
+
ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
|
3019
3112
|
}
|
3020
3113
|
|
3021
3114
|
ko.bindingHandlers['template'] = {
|
@@ -3031,52 +3124,52 @@ ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templ
|
|
3031
3124
|
return { 'controlsDescendantBindings': true };
|
3032
3125
|
},
|
3033
3126
|
'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
3034
|
-
var
|
3035
|
-
|
3036
|
-
|
3127
|
+
var templateName = ko.utils.unwrapObservable(valueAccessor()),
|
3128
|
+
options = {},
|
3129
|
+
shouldDisplay = true,
|
3130
|
+
dataValue,
|
3131
|
+
templateComputed = null;
|
3037
3132
|
|
3038
|
-
if (typeof
|
3039
|
-
|
3040
|
-
|
3041
|
-
templateName = bindingValue['name'];
|
3133
|
+
if (typeof templateName != "string") {
|
3134
|
+
options = templateName;
|
3135
|
+
templateName = options['name'];
|
3042
3136
|
|
3043
3137
|
// Support "if"/"ifnot" conditions
|
3044
|
-
if ('if' in
|
3045
|
-
shouldDisplay =
|
3046
|
-
if ('ifnot' in
|
3047
|
-
shouldDisplay =
|
3048
|
-
}
|
3138
|
+
if ('if' in options)
|
3139
|
+
shouldDisplay = ko.utils.unwrapObservable(options['if']);
|
3140
|
+
if (shouldDisplay && 'ifnot' in options)
|
3141
|
+
shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);
|
3049
3142
|
|
3050
|
-
|
3143
|
+
dataValue = ko.utils.unwrapObservable(options['data']);
|
3144
|
+
}
|
3051
3145
|
|
3052
|
-
if (
|
3146
|
+
if ('foreach' in options) {
|
3053
3147
|
// Render once for each data point (treating data set as empty if shouldDisplay==false)
|
3054
|
-
var dataArray = (shouldDisplay &&
|
3055
|
-
|
3148
|
+
var dataArray = (shouldDisplay && options['foreach']) || [];
|
3149
|
+
templateComputed = ko.renderTemplateForEach(templateName || element, dataArray, options, element, bindingContext);
|
3150
|
+
} else if (!shouldDisplay) {
|
3151
|
+
ko.virtualElements.emptyNode(element);
|
3056
3152
|
} else {
|
3057
|
-
if
|
3058
|
-
|
3059
|
-
|
3060
|
-
|
3061
|
-
|
3062
|
-
templateSubscription = ko.renderTemplate(templateName || element, innerBindingContext, /* options: */ bindingValue, element);
|
3063
|
-
} else
|
3064
|
-
ko.virtualElements.emptyNode(element);
|
3153
|
+
// Render once for this single data point (or use the viewModel if no data was provided)
|
3154
|
+
var innerBindingContext = ('data' in options) ?
|
3155
|
+
bindingContext['createChildContext'](dataValue, options['as']) : // Given an explitit 'data' value, we create a child binding context for it
|
3156
|
+
bindingContext; // Given no explicit 'data' value, we retain the same binding context
|
3157
|
+
templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
|
3065
3158
|
}
|
3066
3159
|
|
3067
|
-
// It only makes sense to have a single template
|
3068
|
-
|
3160
|
+
// It only makes sense to have a single template computed per element (otherwise which one should have its output displayed?)
|
3161
|
+
disposeOldComputedAndStoreNewOne(element, templateComputed);
|
3069
3162
|
}
|
3070
3163
|
};
|
3071
3164
|
|
3072
3165
|
// Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
|
3073
|
-
ko.
|
3074
|
-
var parsedBindingValue = ko.
|
3166
|
+
ko.expressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
|
3167
|
+
var parsedBindingValue = ko.expressionRewriting.parseObjectLiteral(bindingValue);
|
3075
3168
|
|
3076
3169
|
if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
|
3077
3170
|
return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting)
|
3078
3171
|
|
3079
|
-
if (ko.
|
3172
|
+
if (ko.expressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
|
3080
3173
|
return null; // Named templates can be rewritten, so return "no error"
|
3081
3174
|
return "This template engine does not support anonymous templates nested within its templates";
|
3082
3175
|
};
|
@@ -3087,85 +3180,95 @@ ko.exportSymbol('templateRewriting.applyMemoizedBindingsToNextSibling', ko.templ
|
|
3087
3180
|
ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
|
3088
3181
|
ko.exportSymbol('renderTemplate', ko.renderTemplate);
|
3089
3182
|
|
3090
|
-
(function () {
|
3183
|
+
ko.utils.compareArrays = (function () {
|
3184
|
+
var statusNotInOld = 'added', statusNotInNew = 'deleted';
|
3185
|
+
|
3091
3186
|
// Simple calculation based on Levenshtein distance.
|
3092
|
-
function
|
3093
|
-
|
3094
|
-
|
3095
|
-
|
3096
|
-
|
3097
|
-
|
3098
|
-
|
3099
|
-
|
3100
|
-
|
3101
|
-
|
3102
|
-
|
3103
|
-
|
3104
|
-
|
3105
|
-
|
3106
|
-
|
3107
|
-
|
3108
|
-
|
3109
|
-
|
3110
|
-
|
3111
|
-
|
3112
|
-
|
3113
|
-
|
3114
|
-
|
3187
|
+
function compareArrays(oldArray, newArray, dontLimitMoves) {
|
3188
|
+
oldArray = oldArray || [];
|
3189
|
+
newArray = newArray || [];
|
3190
|
+
|
3191
|
+
if (oldArray.length <= newArray.length)
|
3192
|
+
return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, dontLimitMoves);
|
3193
|
+
else
|
3194
|
+
return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, dontLimitMoves);
|
3195
|
+
}
|
3196
|
+
|
3197
|
+
function compareSmallArrayToBigArray(smlArray, bigArray, statusNotInSml, statusNotInBig, dontLimitMoves) {
|
3198
|
+
var myMin = Math.min,
|
3199
|
+
myMax = Math.max,
|
3200
|
+
editDistanceMatrix = [],
|
3201
|
+
smlIndex, smlIndexMax = smlArray.length,
|
3202
|
+
bigIndex, bigIndexMax = bigArray.length,
|
3203
|
+
compareRange = (bigIndexMax - smlIndexMax) || 1,
|
3204
|
+
maxDistance = smlIndexMax + bigIndexMax + 1,
|
3205
|
+
thisRow, lastRow,
|
3206
|
+
bigIndexMaxForRow, bigIndexMinForRow;
|
3207
|
+
|
3208
|
+
for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
|
3209
|
+
lastRow = thisRow;
|
3210
|
+
editDistanceMatrix.push(thisRow = []);
|
3211
|
+
bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
|
3212
|
+
bigIndexMinForRow = myMax(0, smlIndex - 1);
|
3213
|
+
for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) {
|
3214
|
+
if (!bigIndex)
|
3215
|
+
thisRow[bigIndex] = smlIndex + 1;
|
3216
|
+
else if (!smlIndex) // Top row - transform empty array into new array via additions
|
3217
|
+
thisRow[bigIndex] = bigIndex + 1;
|
3218
|
+
else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1])
|
3219
|
+
thisRow[bigIndex] = lastRow[bigIndex - 1]; // copy value (no edit)
|
3115
3220
|
else {
|
3116
|
-
var northDistance =
|
3117
|
-
var westDistance =
|
3118
|
-
|
3221
|
+
var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion)
|
3222
|
+
var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition)
|
3223
|
+
thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
|
3119
3224
|
}
|
3120
3225
|
}
|
3121
3226
|
}
|
3122
3227
|
|
3123
|
-
|
3124
|
-
|
3125
|
-
|
3126
|
-
|
3127
|
-
|
3128
|
-
|
3129
|
-
|
3130
|
-
|
3131
|
-
|
3132
|
-
|
3133
|
-
|
3134
|
-
|
3135
|
-
|
3136
|
-
var distanceViaDelete = (oldIndex > 0) ? editDistanceMatrix[newIndex][oldIndex - 1] : maxDistance + 1;
|
3137
|
-
var distanceViaRetain = (newIndex > 0) && (oldIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex - 1] : maxDistance + 1;
|
3138
|
-
if ((distanceViaAdd === undefined) || (distanceViaAdd < me - 1)) distanceViaAdd = maxDistance + 1;
|
3139
|
-
if ((distanceViaDelete === undefined) || (distanceViaDelete < me - 1)) distanceViaDelete = maxDistance + 1;
|
3140
|
-
if (distanceViaRetain < me - 1) distanceViaRetain = maxDistance + 1;
|
3141
|
-
|
3142
|
-
if ((distanceViaAdd <= distanceViaDelete) && (distanceViaAdd < distanceViaRetain)) {
|
3143
|
-
editScript.push({ status: "added", value: newArray[newIndex - 1] });
|
3144
|
-
newIndex--;
|
3145
|
-
} else if ((distanceViaDelete < distanceViaAdd) && (distanceViaDelete < distanceViaRetain)) {
|
3146
|
-
editScript.push({ status: "deleted", value: oldArray[oldIndex - 1] });
|
3147
|
-
oldIndex--;
|
3228
|
+
var editScript = [], meMinusOne, notInSml = [], notInBig = [];
|
3229
|
+
for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
|
3230
|
+
meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
|
3231
|
+
if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
|
3232
|
+
notInSml.push(editScript[editScript.length] = { // added
|
3233
|
+
'status': statusNotInSml,
|
3234
|
+
'value': bigArray[--bigIndex],
|
3235
|
+
'index': bigIndex });
|
3236
|
+
} else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) {
|
3237
|
+
notInBig.push(editScript[editScript.length] = { // deleted
|
3238
|
+
'status': statusNotInBig,
|
3239
|
+
'value': smlArray[--smlIndex],
|
3240
|
+
'index': smlIndex });
|
3148
3241
|
} else {
|
3149
|
-
editScript.push({
|
3150
|
-
|
3151
|
-
|
3242
|
+
editScript.push({
|
3243
|
+
'status': "retained",
|
3244
|
+
'value': bigArray[--bigIndex] });
|
3245
|
+
--smlIndex;
|
3246
|
+
}
|
3247
|
+
}
|
3248
|
+
|
3249
|
+
if (notInSml.length && notInBig.length) {
|
3250
|
+
// Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
|
3251
|
+
// smlIndexMax keeps the time complexity of this algorithm linear.
|
3252
|
+
var limitFailedCompares = smlIndexMax * 10, failedCompares,
|
3253
|
+
a, d, notInSmlItem, notInBigItem;
|
3254
|
+
// Go through the items that have been added and deleted and try to find matches between them.
|
3255
|
+
for (failedCompares = a = 0; (dontLimitMoves || failedCompares < limitFailedCompares) && (notInSmlItem = notInSml[a]); a++) {
|
3256
|
+
for (d = 0; notInBigItem = notInBig[d]; d++) {
|
3257
|
+
if (notInSmlItem['value'] === notInBigItem['value']) {
|
3258
|
+
notInSmlItem['moved'] = notInBigItem['index'];
|
3259
|
+
notInBigItem['moved'] = notInSmlItem['index'];
|
3260
|
+
notInBig.splice(d,1); // This item is marked as moved; so remove it from notInBig list
|
3261
|
+
failedCompares = d = 0; // Reset failed compares count because we're checking for consecutive failures
|
3262
|
+
break;
|
3263
|
+
}
|
3264
|
+
}
|
3265
|
+
failedCompares += d;
|
3152
3266
|
}
|
3153
3267
|
}
|
3154
3268
|
return editScript.reverse();
|
3155
3269
|
}
|
3156
3270
|
|
3157
|
-
|
3158
|
-
if (maxEditsToConsider === undefined) {
|
3159
|
-
return ko.utils.compareArrays(oldArray, newArray, 1) // First consider likely case where there is at most one edit (very fast)
|
3160
|
-
|| ko.utils.compareArrays(oldArray, newArray, 10) // If that fails, account for a fair number of changes while still being fast
|
3161
|
-
|| ko.utils.compareArrays(oldArray, newArray, Number.MAX_VALUE); // Ultimately give the right answer, even though it may take a long time
|
3162
|
-
} else {
|
3163
|
-
oldArray = oldArray || [];
|
3164
|
-
newArray = newArray || [];
|
3165
|
-
var editDistanceMatrix = calculateEditDistanceMatrix(oldArray, newArray, maxEditsToConsider);
|
3166
|
-
return findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray);
|
3167
|
-
}
|
3168
|
-
};
|
3271
|
+
return compareArrays;
|
3169
3272
|
})();
|
3170
3273
|
|
3171
3274
|
ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
@@ -3181,13 +3284,26 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
3181
3284
|
// "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
|
3182
3285
|
// You can use this, for example, to activate bindings on those nodes.
|
3183
3286
|
|
3184
|
-
function
|
3185
|
-
//
|
3186
|
-
//
|
3187
|
-
//
|
3188
|
-
//
|
3189
|
-
|
3190
|
-
|
3287
|
+
function fixUpNodesToBeMovedOrRemoved(contiguousNodeArray) {
|
3288
|
+
// Before moving, deleting, or replacing a set of nodes that were previously outputted by the "map" function, we have to reconcile
|
3289
|
+
// them against what is in the DOM right now. It may be that some of the nodes have already been removed from the document,
|
3290
|
+
// or that new nodes might have been inserted in the middle, for example by a binding. Also, there may previously have been
|
3291
|
+
// leading comment nodes (created by rewritten string-based templates) that have since been removed during binding.
|
3292
|
+
// So, this function translates the old "map" output array into its best guess of what set of current DOM nodes should be removed.
|
3293
|
+
//
|
3294
|
+
// Rules:
|
3295
|
+
// [A] Any leading nodes that aren't in the document any more should be ignored
|
3296
|
+
// These most likely correspond to memoization nodes that were already removed during binding
|
3297
|
+
// See https://github.com/SteveSanderson/knockout/pull/440
|
3298
|
+
// [B] We want to output a contiguous series of nodes that are still in the document. So, ignore any nodes that
|
3299
|
+
// have already been removed, and include any nodes that have been inserted among the previous collection
|
3300
|
+
|
3301
|
+
// Rule [A]
|
3302
|
+
while (contiguousNodeArray.length && !ko.utils.domNodeIsAttachedToDocument(contiguousNodeArray[0]))
|
3303
|
+
contiguousNodeArray.splice(0, 1);
|
3304
|
+
|
3305
|
+
// Rule [B]
|
3306
|
+
if (contiguousNodeArray.length > 1) {
|
3191
3307
|
// Build up the actual new contiguous node set
|
3192
3308
|
var current = contiguousNodeArray[0], last = contiguousNodeArray[contiguousNodeArray.length - 1], newContiguousSet = [current];
|
3193
3309
|
while (current !== last) {
|
@@ -3201,6 +3317,7 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
3201
3317
|
// (The following line replaces the contents of contiguousNodeArray with newContiguousSet)
|
3202
3318
|
Array.prototype.splice.apply(contiguousNodeArray, [0, contiguousNodeArray.length].concat(newContiguousSet));
|
3203
3319
|
}
|
3320
|
+
return contiguousNodeArray;
|
3204
3321
|
}
|
3205
3322
|
|
3206
3323
|
function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
|
@@ -3211,18 +3328,17 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
3211
3328
|
|
3212
3329
|
// On subsequent evaluations, just replace the previously-inserted DOM nodes
|
3213
3330
|
if (mappedNodes.length > 0) {
|
3214
|
-
|
3215
|
-
ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
|
3331
|
+
ko.utils.replaceDomNodes(fixUpNodesToBeMovedOrRemoved(mappedNodes), newMappedNodes);
|
3216
3332
|
if (callbackAfterAddingNodes)
|
3217
|
-
callbackAfterAddingNodes
|
3333
|
+
ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]);
|
3218
3334
|
}
|
3219
3335
|
|
3220
3336
|
// Replace the contents of the mappedNodes array, thereby updating the record
|
3221
3337
|
// of which nodes would be deleted if valueToMap was itself later removed
|
3222
3338
|
mappedNodes.splice(0, mappedNodes.length);
|
3223
3339
|
ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
|
3224
|
-
}, null, {
|
3225
|
-
return { mappedNodes : mappedNodes, dependentObservable : dependentObservable };
|
3340
|
+
}, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return (mappedNodes.length == 0) || !ko.utils.domNodeIsAttachedToDocument(mappedNodes[0]) } });
|
3341
|
+
return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
|
3226
3342
|
}
|
3227
3343
|
|
3228
3344
|
var lastMappingResultDomDataKey = "setDomNodeChildrenFromArrayMapping_lastMappingResult";
|
@@ -3239,96 +3355,113 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
3239
3355
|
// Build the new mapping result
|
3240
3356
|
var newMappingResult = [];
|
3241
3357
|
var lastMappingResultIndex = 0;
|
3242
|
-
var nodesToDelete = [];
|
3243
3358
|
var newMappingResultIndex = 0;
|
3244
|
-
var nodesAdded = [];
|
3245
|
-
var insertAfterNode = null;
|
3246
|
-
for (var i = 0, j = editScript.length; i < j; i++) {
|
3247
|
-
switch (editScript[i].status) {
|
3248
|
-
case "retained":
|
3249
|
-
// Just keep the information - don't touch the nodes
|
3250
|
-
var dataToRetain = lastMappingResult[lastMappingResultIndex];
|
3251
|
-
dataToRetain.indexObservable(newMappingResultIndex);
|
3252
|
-
newMappingResultIndex = newMappingResult.push(dataToRetain);
|
3253
|
-
if (dataToRetain.domNodes.length > 0)
|
3254
|
-
insertAfterNode = dataToRetain.domNodes[dataToRetain.domNodes.length - 1];
|
3255
|
-
lastMappingResultIndex++;
|
3256
|
-
break;
|
3257
3359
|
|
3258
|
-
|
3259
|
-
|
3260
|
-
|
3261
|
-
|
3262
|
-
|
3263
|
-
|
3264
|
-
|
3265
|
-
|
3266
|
-
|
3267
|
-
|
3268
|
-
|
3360
|
+
var nodesToDelete = [];
|
3361
|
+
var itemsToProcess = [];
|
3362
|
+
var itemsForBeforeRemoveCallbacks = [];
|
3363
|
+
var itemsForMoveCallbacks = [];
|
3364
|
+
var itemsForAfterAddCallbacks = [];
|
3365
|
+
var mapData;
|
3366
|
+
|
3367
|
+
function itemMovedOrRetained(editScriptIndex, oldPosition) {
|
3368
|
+
mapData = lastMappingResult[oldPosition];
|
3369
|
+
if (newMappingResultIndex !== oldPosition)
|
3370
|
+
itemsForMoveCallbacks[editScriptIndex] = mapData;
|
3371
|
+
// Since updating the index might change the nodes, do so before calling fixUpNodesToBeMovedOrRemoved
|
3372
|
+
mapData.indexObservable(newMappingResultIndex++);
|
3373
|
+
fixUpNodesToBeMovedOrRemoved(mapData.mappedNodes);
|
3374
|
+
newMappingResult.push(mapData);
|
3375
|
+
itemsToProcess.push(mapData);
|
3376
|
+
}
|
3377
|
+
|
3378
|
+
function callCallback(callback, items) {
|
3379
|
+
if (callback) {
|
3380
|
+
for (var i = 0, n = items.length; i < n; i++) {
|
3381
|
+
if (items[i]) {
|
3382
|
+
ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
|
3383
|
+
callback(node, i, items[i].arrayEntry);
|
3269
3384
|
});
|
3270
|
-
|
3271
|
-
|
3385
|
+
}
|
3386
|
+
}
|
3387
|
+
}
|
3388
|
+
}
|
3389
|
+
|
3390
|
+
for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
|
3391
|
+
movedIndex = editScriptItem['moved'];
|
3392
|
+
switch (editScriptItem['status']) {
|
3393
|
+
case "deleted":
|
3394
|
+
if (movedIndex === undefined) {
|
3395
|
+
mapData = lastMappingResult[lastMappingResultIndex];
|
3396
|
+
|
3397
|
+
// Stop tracking changes to the mapping for these nodes
|
3398
|
+
if (mapData.dependentObservable)
|
3399
|
+
mapData.dependentObservable.dispose();
|
3400
|
+
|
3401
|
+
// Queue these nodes for later removal
|
3402
|
+
nodesToDelete.push.apply(nodesToDelete, fixUpNodesToBeMovedOrRemoved(mapData.mappedNodes));
|
3403
|
+
if (options['beforeRemove']) {
|
3404
|
+
itemsForBeforeRemoveCallbacks[i] = mapData;
|
3405
|
+
itemsToProcess.push(mapData);
|
3406
|
+
}
|
3407
|
+
}
|
3272
3408
|
lastMappingResultIndex++;
|
3273
3409
|
break;
|
3274
3410
|
|
3411
|
+
case "retained":
|
3412
|
+
itemMovedOrRetained(i, lastMappingResultIndex++);
|
3413
|
+
break;
|
3414
|
+
|
3275
3415
|
case "added":
|
3276
|
-
|
3277
|
-
|
3278
|
-
|
3279
|
-
|
3280
|
-
|
3281
|
-
|
3282
|
-
|
3283
|
-
|
3284
|
-
domNodes: mappedNodes,
|
3285
|
-
dependentObservable: mapData.dependentObservable,
|
3286
|
-
indexObservable: indexObservable
|
3287
|
-
});
|
3288
|
-
for (var nodeIndex = 0, nodeIndexMax = mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) {
|
3289
|
-
var node = mappedNodes[nodeIndex];
|
3290
|
-
nodesAdded.push({
|
3291
|
-
element: node,
|
3292
|
-
index: i,
|
3293
|
-
value: editScript[i].value
|
3294
|
-
});
|
3295
|
-
if (insertAfterNode == null) {
|
3296
|
-
// Insert "node" (the newly-created node) as domNode's first child
|
3297
|
-
ko.virtualElements.prepend(domNode, node);
|
3298
|
-
} else {
|
3299
|
-
// Insert "node" into "domNode" immediately after "insertAfterNode"
|
3300
|
-
ko.virtualElements.insertAfter(domNode, node, insertAfterNode);
|
3301
|
-
}
|
3302
|
-
insertAfterNode = node;
|
3416
|
+
if (movedIndex !== undefined) {
|
3417
|
+
itemMovedOrRetained(i, movedIndex);
|
3418
|
+
} else {
|
3419
|
+
mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
|
3420
|
+
newMappingResult.push(mapData);
|
3421
|
+
itemsToProcess.push(mapData);
|
3422
|
+
if (!isFirstExecution)
|
3423
|
+
itemsForAfterAddCallbacks[i] = mapData;
|
3303
3424
|
}
|
3304
|
-
if (callbackAfterAddingNodes)
|
3305
|
-
callbackAfterAddingNodes(valueToMap, mappedNodes, indexObservable);
|
3306
3425
|
break;
|
3307
3426
|
}
|
3308
3427
|
}
|
3309
3428
|
|
3310
|
-
|
3429
|
+
// Call beforeMove first before any changes have been made to the DOM
|
3430
|
+
callCallback(options['beforeMove'], itemsForMoveCallbacks);
|
3311
3431
|
|
3312
|
-
|
3313
|
-
|
3314
|
-
|
3315
|
-
|
3316
|
-
|
3317
|
-
|
3318
|
-
if (
|
3319
|
-
|
3320
|
-
|
3321
|
-
|
3432
|
+
// Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
|
3433
|
+
ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
|
3434
|
+
|
3435
|
+
// Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
|
3436
|
+
for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
|
3437
|
+
// Get nodes for newly added items
|
3438
|
+
if (!mapData.mappedNodes)
|
3439
|
+
ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
|
3440
|
+
|
3441
|
+
// Put nodes in the right place if they aren't there already
|
3442
|
+
for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
|
3443
|
+
if (node !== nextNode)
|
3444
|
+
ko.virtualElements.insertAfter(domNode, node, lastNode);
|
3322
3445
|
}
|
3323
|
-
|
3324
|
-
|
3325
|
-
|
3326
|
-
|
3327
|
-
|
3328
|
-
element.parentNode.removeChild(element);
|
3446
|
+
|
3447
|
+
// Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
|
3448
|
+
if (!mapData.initialized && callbackAfterAddingNodes) {
|
3449
|
+
callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
|
3450
|
+
mapData.initialized = true;
|
3329
3451
|
}
|
3330
3452
|
}
|
3331
3453
|
|
3454
|
+
// If there's a beforeRemove callback, call it after reordering.
|
3455
|
+
// Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
|
3456
|
+
// some sort of animation, which is why we first reorder the nodes that will be removed. If the
|
3457
|
+
// callback instead removes the nodes right away, it would be more efficient to skip reordering them.
|
3458
|
+
// Perhaps we'll make that change in the future if this scenario becomes more common.
|
3459
|
+
callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
|
3460
|
+
|
3461
|
+
// Finally call afterMove and afterAdd callbacks
|
3462
|
+
callCallback(options['afterMove'], itemsForMoveCallbacks);
|
3463
|
+
callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
|
3464
|
+
|
3332
3465
|
// Store a copy of the array items we just considered so we can difference it next time
|
3333
3466
|
ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
|
3334
3467
|
}
|
@@ -3440,4 +3573,5 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
|
3440
3573
|
ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
|
3441
3574
|
})();
|
3442
3575
|
});
|
3443
|
-
})(window,document,navigator);
|
3576
|
+
})(window,document,navigator,window["jQuery"]);
|
3577
|
+
})();
|