knockoutjs-rails 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
})();
|