knockoutjs-rails 3.4.0.1 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/knockoutjs-rails/version.rb +1 -1
- data/vendor/assets/javascripts/knockout.js +1296 -610
- metadata +3 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c80fd2c172e365df64e874fcd409b69190dc7309f5b3a1989691db726583f40a
|
4
|
+
data.tar.gz: 320a5df4dfcca7859014148c3dfefab1f38a6e6865d7acda802643e54e680d64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e93090ba07bc76f7d773b8c8ddc4855a21def6f09ad97676e8888303a1ca8fb68e907103cc7b91011de51157c20e60c8f9b37fb445aaff5d40e08ba7e8a7ad74
|
7
|
+
data.tar.gz: 48b0eec9a8b53aac33ce85a26c66a3fbb51c5ee1216e97d282e409fdb887c2bc904b073e43548376307501cd344dd8441607459eace53419f055635bdf8d6419
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/*!
|
2
|
-
* Knockout JavaScript library v3.
|
3
|
-
* (c)
|
2
|
+
* Knockout JavaScript library v3.5.0
|
3
|
+
* (c) The Knockout.js team - http://knockoutjs.com/
|
4
4
|
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
5
5
|
*/
|
6
6
|
|
@@ -14,6 +14,10 @@ var DEBUG=true;
|
|
14
14
|
navigator = window['navigator'],
|
15
15
|
jQueryInstance = window["jQuery"],
|
16
16
|
JSON = window["JSON"];
|
17
|
+
|
18
|
+
if (!jQueryInstance && typeof jQuery !== "undefined") {
|
19
|
+
jQueryInstance = jQuery;
|
20
|
+
}
|
17
21
|
(function(factory) {
|
18
22
|
// Support three module loading scenarios
|
19
23
|
if (typeof define === 'function' && define['amd']) {
|
@@ -45,20 +49,23 @@ ko.exportSymbol = function(koPath, object) {
|
|
45
49
|
ko.exportProperty = function(owner, publicName, object) {
|
46
50
|
owner[publicName] = object;
|
47
51
|
};
|
48
|
-
ko.version = "3.
|
52
|
+
ko.version = "3.5.0";
|
49
53
|
|
50
54
|
ko.exportSymbol('version', ko.version);
|
51
55
|
// For any options that may affect various areas of Knockout and aren't directly associated with data binding.
|
52
56
|
ko.options = {
|
53
57
|
'deferUpdates': false,
|
54
|
-
'useOnlyNativeEvents': false
|
58
|
+
'useOnlyNativeEvents': false,
|
59
|
+
'foreachHidesDestroyed': false
|
55
60
|
};
|
56
61
|
|
57
62
|
//ko.exportSymbol('options', ko.options); // 'options' isn't minified
|
58
63
|
ko.utils = (function () {
|
64
|
+
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
65
|
+
|
59
66
|
function objectForEach(obj, action) {
|
60
67
|
for (var prop in obj) {
|
61
|
-
if (
|
68
|
+
if (hasOwnProperty.call(obj, prop)) {
|
62
69
|
action(prop, obj[prop]);
|
63
70
|
}
|
64
71
|
}
|
@@ -67,7 +74,7 @@ ko.utils = (function () {
|
|
67
74
|
function extend(target, source) {
|
68
75
|
if (source) {
|
69
76
|
for(var prop in source) {
|
70
|
-
if(
|
77
|
+
if(hasOwnProperty.call(source, prop)) {
|
71
78
|
target[prop] = source[prop];
|
72
79
|
}
|
73
80
|
}
|
@@ -124,6 +131,8 @@ ko.utils = (function () {
|
|
124
131
|
// see: https://github.com/knockout/knockout/issues/1597
|
125
132
|
var cssClassNameRegex = /\S+/g;
|
126
133
|
|
134
|
+
var jQueryEventAttachName;
|
135
|
+
|
127
136
|
function toggleDomNodeCssClass(node, classNames, shouldHaveClass) {
|
128
137
|
var addOrRemoveFn;
|
129
138
|
if (classNames) {
|
@@ -154,25 +163,30 @@ ko.utils = (function () {
|
|
154
163
|
return {
|
155
164
|
fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
|
156
165
|
|
157
|
-
arrayForEach: function (array, action) {
|
158
|
-
for (var i = 0, j = array.length; i < j; i++)
|
159
|
-
action(array[i], i);
|
166
|
+
arrayForEach: function (array, action, actionOwner) {
|
167
|
+
for (var i = 0, j = array.length; i < j; i++) {
|
168
|
+
action.call(actionOwner, array[i], i, array);
|
169
|
+
}
|
160
170
|
},
|
161
171
|
|
162
|
-
arrayIndexOf:
|
163
|
-
|
172
|
+
arrayIndexOf: typeof Array.prototype.indexOf == "function"
|
173
|
+
? function (array, item) {
|
164
174
|
return Array.prototype.indexOf.call(array, item);
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
175
|
+
}
|
176
|
+
: function (array, item) {
|
177
|
+
for (var i = 0, j = array.length; i < j; i++) {
|
178
|
+
if (array[i] === item)
|
179
|
+
return i;
|
180
|
+
}
|
181
|
+
return -1;
|
182
|
+
},
|
170
183
|
|
171
184
|
arrayFirst: function (array, predicate, predicateOwner) {
|
172
|
-
for (var i = 0, j = array.length; i < j; i++)
|
173
|
-
if (predicate.call(predicateOwner, array[i], i))
|
185
|
+
for (var i = 0, j = array.length; i < j; i++) {
|
186
|
+
if (predicate.call(predicateOwner, array[i], i, array))
|
174
187
|
return array[i];
|
175
|
-
|
188
|
+
}
|
189
|
+
return undefined;
|
176
190
|
},
|
177
191
|
|
178
192
|
arrayRemoveItem: function (array, itemToRemove) {
|
@@ -186,29 +200,32 @@ ko.utils = (function () {
|
|
186
200
|
},
|
187
201
|
|
188
202
|
arrayGetDistinctValues: function (array) {
|
189
|
-
array = array || [];
|
190
203
|
var result = [];
|
191
|
-
|
192
|
-
|
193
|
-
|
204
|
+
if (array) {
|
205
|
+
ko.utils.arrayForEach(array, function(item) {
|
206
|
+
if (ko.utils.arrayIndexOf(result, item) < 0)
|
207
|
+
result.push(item);
|
208
|
+
});
|
194
209
|
}
|
195
210
|
return result;
|
196
211
|
},
|
197
212
|
|
198
|
-
arrayMap: function (array, mapping) {
|
199
|
-
array = array || [];
|
213
|
+
arrayMap: function (array, mapping, mappingOwner) {
|
200
214
|
var result = [];
|
201
|
-
|
202
|
-
|
215
|
+
if (array) {
|
216
|
+
for (var i = 0, j = array.length; i < j; i++)
|
217
|
+
result.push(mapping.call(mappingOwner, array[i], i));
|
218
|
+
}
|
203
219
|
return result;
|
204
220
|
},
|
205
221
|
|
206
|
-
arrayFilter: function (array, predicate) {
|
207
|
-
array = array || [];
|
222
|
+
arrayFilter: function (array, predicate, predicateOwner) {
|
208
223
|
var result = [];
|
209
|
-
|
210
|
-
|
211
|
-
|
224
|
+
if (array) {
|
225
|
+
for (var i = 0, j = array.length; i < j; i++)
|
226
|
+
if (predicate.call(predicateOwner, array[i], i))
|
227
|
+
result.push(array[i]);
|
228
|
+
}
|
212
229
|
return result;
|
213
230
|
},
|
214
231
|
|
@@ -242,13 +259,13 @@ ko.utils = (function () {
|
|
242
259
|
|
243
260
|
objectForEach: objectForEach,
|
244
261
|
|
245
|
-
objectMap: function(source, mapping) {
|
262
|
+
objectMap: function(source, mapping, mappingOwner) {
|
246
263
|
if (!source)
|
247
264
|
return source;
|
248
265
|
var target = {};
|
249
266
|
for (var prop in source) {
|
250
|
-
if (
|
251
|
-
target[prop] = mapping(source[prop], prop, source);
|
267
|
+
if (hasOwnProperty.call(source, prop)) {
|
268
|
+
target[prop] = mapping.call(mappingOwner, source[prop], prop, source);
|
252
269
|
}
|
253
270
|
}
|
254
271
|
return target;
|
@@ -374,7 +391,7 @@ ko.utils = (function () {
|
|
374
391
|
if (node.nodeType === 11)
|
375
392
|
return false; // Fixes issue #1162 - can't use node.contains for document fragments on IE8
|
376
393
|
if (containedByNode.contains)
|
377
|
-
return containedByNode.contains(node.nodeType
|
394
|
+
return containedByNode.contains(node.nodeType !== 1 ? node.parentNode : node);
|
378
395
|
if (containedByNode.compareDocumentPosition)
|
379
396
|
return (containedByNode.compareDocumentPosition(node) & 16) == 16;
|
380
397
|
while (node && node != containedByNode) {
|
@@ -423,9 +440,12 @@ ko.utils = (function () {
|
|
423
440
|
registerEventHandler: function (element, eventType, handler) {
|
424
441
|
var wrappedHandler = ko.utils.catchFunctionErrors(handler);
|
425
442
|
|
426
|
-
var mustUseAttachEvent =
|
443
|
+
var mustUseAttachEvent = eventsThatMustBeRegisteredUsingAttachEvent[eventType];
|
427
444
|
if (!ko.options['useOnlyNativeEvents'] && !mustUseAttachEvent && jQueryInstance) {
|
428
|
-
|
445
|
+
if (!jQueryEventAttachName) {
|
446
|
+
jQueryEventAttachName = (typeof jQueryInstance(element)['on'] == 'function') ? 'on' : 'bind';
|
447
|
+
}
|
448
|
+
jQueryInstance(element)[jQueryEventAttachName](eventType, wrappedHandler);
|
429
449
|
} else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
|
430
450
|
element.addEventListener(eventType, wrappedHandler, false);
|
431
451
|
else if (typeof element.attachEvent != "undefined") {
|
@@ -508,7 +528,8 @@ ko.utils = (function () {
|
|
508
528
|
// - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
|
509
529
|
if (ieVersion <= 7) {
|
510
530
|
try {
|
511
|
-
element.
|
531
|
+
var escapedName = element.name.replace(/[&<>'"]/g, function(r){ return "&#" + r.charCodeAt(0) + ";"; });
|
532
|
+
element.mergeAttributes(document.createElement("<input name='" + escapedName + "'/>"), false);
|
512
533
|
}
|
513
534
|
catch(e) {} // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View"
|
514
535
|
}
|
@@ -644,9 +665,12 @@ ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
|
|
644
665
|
ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
|
645
666
|
ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
|
646
667
|
ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
|
668
|
+
ko.exportSymbol('utils.cloneNodes', ko.utils.cloneNodes);
|
669
|
+
ko.exportSymbol('utils.createSymbolOrString', ko.utils.createSymbolOrString);
|
647
670
|
ko.exportSymbol('utils.extend', ko.utils.extend);
|
648
671
|
ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
|
649
672
|
ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
|
673
|
+
ko.exportSymbol('utils.objectMap', ko.utils.objectMap);
|
650
674
|
ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable);
|
651
675
|
ko.exportSymbol('utils.postJson', ko.utils.postJson);
|
652
676
|
ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
|
@@ -686,33 +710,40 @@ ko.utils.domData = new (function () {
|
|
686
710
|
var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
|
687
711
|
var dataStore = {};
|
688
712
|
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
},
|
706
|
-
set: function (node, key, value) {
|
707
|
-
if (value === undefined) {
|
708
|
-
// Make sure we don't actually create a new domData key if we are actually deleting a value
|
709
|
-
if (getAll(node, false) === undefined)
|
710
|
-
return;
|
713
|
+
var getDataForNode, clear;
|
714
|
+
if (!ko.utils.ieVersion) {
|
715
|
+
// We considered using WeakMap, but it has a problem in IE 11 and Edge that prevents using
|
716
|
+
// it cross-window, so instead we just store the data directly on the node.
|
717
|
+
// See https://github.com/knockout/knockout/issues/2141
|
718
|
+
getDataForNode = function (node, createIfNotFound) {
|
719
|
+
var dataForNode = node[dataStoreKeyExpandoPropertyName];
|
720
|
+
if (!dataForNode && createIfNotFound) {
|
721
|
+
dataForNode = node[dataStoreKeyExpandoPropertyName] = {};
|
722
|
+
}
|
723
|
+
return dataForNode;
|
724
|
+
};
|
725
|
+
clear = function (node) {
|
726
|
+
if (node[dataStoreKeyExpandoPropertyName]) {
|
727
|
+
delete node[dataStoreKeyExpandoPropertyName];
|
728
|
+
return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
|
711
729
|
}
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
730
|
+
return false;
|
731
|
+
};
|
732
|
+
} else {
|
733
|
+
// Old IE versions have memory issues if you store objects on the node, so we use a
|
734
|
+
// separate data storage and link to it from the node using a string key.
|
735
|
+
getDataForNode = function (node, createIfNotFound) {
|
736
|
+
var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
|
737
|
+
var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];
|
738
|
+
if (!hasExistingDataStore) {
|
739
|
+
if (!createIfNotFound)
|
740
|
+
return undefined;
|
741
|
+
dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
|
742
|
+
dataStore[dataStoreKey] = {};
|
743
|
+
}
|
744
|
+
return dataStore[dataStoreKey];
|
745
|
+
};
|
746
|
+
clear = function (node) {
|
716
747
|
var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
|
717
748
|
if (dataStoreKey) {
|
718
749
|
delete dataStore[dataStoreKey];
|
@@ -720,7 +751,24 @@ ko.utils.domData = new (function () {
|
|
720
751
|
return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
|
721
752
|
}
|
722
753
|
return false;
|
754
|
+
};
|
755
|
+
}
|
756
|
+
|
757
|
+
return {
|
758
|
+
get: function (node, key) {
|
759
|
+
var dataForNode = getDataForNode(node, false);
|
760
|
+
return dataForNode && dataForNode[key];
|
761
|
+
},
|
762
|
+
set: function (node, key, value) {
|
763
|
+
// Make sure we don't actually create a new domData key if we are actually deleting a value
|
764
|
+
var dataForNode = getDataForNode(node, value !== undefined /* createIfNotFound */);
|
765
|
+
dataForNode && (dataForNode[key] = value);
|
766
|
+
},
|
767
|
+
getOrSet: function (node, key, value) {
|
768
|
+
var dataForNode = getDataForNode(node, true /* createIfNotFound */);
|
769
|
+
return dataForNode[key] || (dataForNode[key] = value);
|
723
770
|
},
|
771
|
+
clear: clear,
|
724
772
|
|
725
773
|
nextKey: function () {
|
726
774
|
return (uniqueId++) + dataStoreKeyExpandoPropertyName;
|
@@ -765,16 +813,20 @@ ko.utils.domNodeDisposal = new (function () {
|
|
765
813
|
|
766
814
|
// Clear any immediate-child comment nodes, as these wouldn't have been found by
|
767
815
|
// node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
|
768
|
-
if (cleanableNodeTypesWithDescendants[node.nodeType])
|
769
|
-
|
816
|
+
if (cleanableNodeTypesWithDescendants[node.nodeType]) {
|
817
|
+
cleanNodesInList(node.childNodes, true/*onlyComments*/);
|
818
|
+
}
|
770
819
|
}
|
771
820
|
|
772
|
-
function
|
773
|
-
var
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
821
|
+
function cleanNodesInList(nodeList, onlyComments) {
|
822
|
+
var cleanedNodes = [], lastCleanedNode;
|
823
|
+
for (var i = 0; i < nodeList.length; i++) {
|
824
|
+
if (!onlyComments || nodeList[i].nodeType === 8) {
|
825
|
+
cleanSingleNode(cleanedNodes[cleanedNodes.length] = lastCleanedNode = nodeList[i]);
|
826
|
+
if (nodeList[i] !== lastCleanedNode) {
|
827
|
+
while (i-- && ko.utils.arrayIndexOf(cleanedNodes, nodeList[i]) == -1) {}
|
828
|
+
}
|
829
|
+
}
|
778
830
|
}
|
779
831
|
}
|
780
832
|
|
@@ -801,11 +853,7 @@ ko.utils.domNodeDisposal = new (function () {
|
|
801
853
|
|
802
854
|
// ... then its descendants, where applicable
|
803
855
|
if (cleanableNodeTypesWithDescendants[node.nodeType]) {
|
804
|
-
|
805
|
-
var descendants = [];
|
806
|
-
ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
|
807
|
-
for (var i = 0, j = descendants.length; i < j; i++)
|
808
|
-
cleanSingleNode(descendants[i]);
|
856
|
+
cleanNodesInList(node.getElementsByTagName("*"));
|
809
857
|
}
|
810
858
|
}
|
811
859
|
return node;
|
@@ -854,7 +902,7 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
|
|
854
902
|
mayRequireCreateElementHack = ko.utils.ieVersion <= 8;
|
855
903
|
|
856
904
|
function getWrap(tags) {
|
857
|
-
var m = tags.match(
|
905
|
+
var m = tags.match(/^(?:<!--.*?-->\s*?)*?<([a-z]+)[\s>]/);
|
858
906
|
return (m && lookup[m[1]]) || none;
|
859
907
|
}
|
860
908
|
|
@@ -887,7 +935,7 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
|
|
887
935
|
if (mayRequireCreateElementHack) {
|
888
936
|
// The document.createElement('my-element') trick to enable custom elements in IE6-8
|
889
937
|
// only works if we assign innerHTML on an element associated with that document.
|
890
|
-
documentContext.appendChild(div);
|
938
|
+
documentContext.body.appendChild(div);
|
891
939
|
}
|
892
940
|
|
893
941
|
div.innerHTML = markup;
|
@@ -935,6 +983,11 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
|
|
935
983
|
simpleHtmlParse(html, documentContext); // ... otherwise, this simple logic will do in most common cases.
|
936
984
|
};
|
937
985
|
|
986
|
+
ko.utils.parseHtmlForTemplateNodes = function(html, documentContext) {
|
987
|
+
var nodes = ko.utils.parseHtmlFragment(html, documentContext);
|
988
|
+
return (nodes.length && nodes[0].parentElement) || ko.utils.moveCleanedNodesToContainerElement(nodes);
|
989
|
+
};
|
990
|
+
|
938
991
|
ko.utils.setHtml = function(node, html) {
|
939
992
|
ko.utils.emptyDomNode(node);
|
940
993
|
|
@@ -1175,9 +1228,9 @@ ko.extenders = {
|
|
1175
1228
|
// rateLimit supersedes deferred updates
|
1176
1229
|
target._deferUpdates = false;
|
1177
1230
|
|
1178
|
-
limitFunction = method == 'notifyWhenChangesStop' ? debounce : throttle;
|
1231
|
+
limitFunction = typeof method == 'function' ? method : method == 'notifyWhenChangesStop' ? debounce : throttle;
|
1179
1232
|
target.limit(function(callback) {
|
1180
|
-
return limitFunction(callback, timeout);
|
1233
|
+
return limitFunction(callback, timeout, options);
|
1181
1234
|
});
|
1182
1235
|
},
|
1183
1236
|
|
@@ -1189,11 +1242,20 @@ ko.extenders = {
|
|
1189
1242
|
if (!target._deferUpdates) {
|
1190
1243
|
target._deferUpdates = true;
|
1191
1244
|
target.limit(function (callback) {
|
1192
|
-
var handle
|
1245
|
+
var handle,
|
1246
|
+
ignoreUpdates = false;
|
1193
1247
|
return function () {
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1248
|
+
if (!ignoreUpdates) {
|
1249
|
+
ko.tasks.cancel(handle);
|
1250
|
+
handle = ko.tasks.schedule(callback);
|
1251
|
+
|
1252
|
+
try {
|
1253
|
+
ignoreUpdates = true;
|
1254
|
+
target['notifySubscribers'](undefined, 'dirty');
|
1255
|
+
} finally {
|
1256
|
+
ignoreUpdates = false;
|
1257
|
+
}
|
1258
|
+
}
|
1197
1259
|
};
|
1198
1260
|
});
|
1199
1261
|
}
|
@@ -1249,14 +1311,29 @@ ko.exportSymbol('extenders', ko.extenders);
|
|
1249
1311
|
|
1250
1312
|
ko.subscription = function (target, callback, disposeCallback) {
|
1251
1313
|
this._target = target;
|
1252
|
-
this.
|
1253
|
-
this.
|
1254
|
-
this.
|
1314
|
+
this._callback = callback;
|
1315
|
+
this._disposeCallback = disposeCallback;
|
1316
|
+
this._isDisposed = false;
|
1317
|
+
this._node = null;
|
1318
|
+
this._domNodeDisposalCallback = null;
|
1255
1319
|
ko.exportProperty(this, 'dispose', this.dispose);
|
1320
|
+
ko.exportProperty(this, 'disposeWhenNodeIsRemoved', this.disposeWhenNodeIsRemoved);
|
1256
1321
|
};
|
1257
1322
|
ko.subscription.prototype.dispose = function () {
|
1258
|
-
|
1259
|
-
|
1323
|
+
var self = this;
|
1324
|
+
if (!self._isDisposed) {
|
1325
|
+
if (self._domNodeDisposalCallback) {
|
1326
|
+
ko.utils.domNodeDisposal.removeDisposeCallback(self._node, self._domNodeDisposalCallback);
|
1327
|
+
}
|
1328
|
+
self._isDisposed = true;
|
1329
|
+
self._disposeCallback();
|
1330
|
+
|
1331
|
+
self._target = self._callback = self._disposeCallback = self._node = self._domNodeDisposalCallback = null;
|
1332
|
+
}
|
1333
|
+
};
|
1334
|
+
ko.subscription.prototype.disposeWhenNodeIsRemoved = function (node) {
|
1335
|
+
this._node = node;
|
1336
|
+
ko.utils.domNodeDisposal.addDisposeCallback(node, this._domNodeDisposalCallback = this.dispose.bind(this));
|
1260
1337
|
};
|
1261
1338
|
|
1262
1339
|
ko.subscribable = function () {
|
@@ -1279,7 +1356,7 @@ function limitNotifySubscribers(value, event) {
|
|
1279
1356
|
|
1280
1357
|
var ko_subscribable_fn = {
|
1281
1358
|
init: function(instance) {
|
1282
|
-
instance._subscriptions = {};
|
1359
|
+
instance._subscriptions = { "change": [] };
|
1283
1360
|
instance._versionNumber = 1;
|
1284
1361
|
},
|
1285
1362
|
|
@@ -1311,13 +1388,14 @@ var ko_subscribable_fn = {
|
|
1311
1388
|
this.updateVersion();
|
1312
1389
|
}
|
1313
1390
|
if (this.hasSubscriptionsForEvent(event)) {
|
1391
|
+
var subs = event === defaultEvent && this._changeSubscriptions || this._subscriptions[event].slice(0);
|
1314
1392
|
try {
|
1315
1393
|
ko.dependencyDetection.begin(); // Begin suppressing dependency detection (by setting the top frame to undefined)
|
1316
|
-
for (var
|
1394
|
+
for (var i = 0, subscription; subscription = subs[i]; ++i) {
|
1317
1395
|
// In case a subscription was disposed during the arrayForEach cycle, check
|
1318
1396
|
// for isDisposed on each subscription before invoking its callback
|
1319
|
-
if (!subscription.
|
1320
|
-
subscription.
|
1397
|
+
if (!subscription._isDisposed)
|
1398
|
+
subscription._callback(valueToNotify);
|
1321
1399
|
}
|
1322
1400
|
} finally {
|
1323
1401
|
ko.dependencyDetection.end(); // End suppressing dependency detection
|
@@ -1339,7 +1417,8 @@ var ko_subscribable_fn = {
|
|
1339
1417
|
|
1340
1418
|
limit: function(limitFunction) {
|
1341
1419
|
var self = this, selfIsObservable = ko.isObservable(self),
|
1342
|
-
ignoreBeforeChange, previousValue, pendingValue,
|
1420
|
+
ignoreBeforeChange, notifyNextChange, previousValue, pendingValue, didUpdate,
|
1421
|
+
beforeChange = 'beforeChange';
|
1343
1422
|
|
1344
1423
|
if (!self._origNotifySubscribers) {
|
1345
1424
|
self._origNotifySubscribers = self["notifySubscribers"];
|
@@ -1352,15 +1431,22 @@ var ko_subscribable_fn = {
|
|
1352
1431
|
// If an observable provided a reference to itself, access it to get the latest value.
|
1353
1432
|
// This allows computed observables to delay calculating their value until needed.
|
1354
1433
|
if (selfIsObservable && pendingValue === self) {
|
1355
|
-
pendingValue = self();
|
1434
|
+
pendingValue = self._evalIfChanged ? self._evalIfChanged() : self();
|
1356
1435
|
}
|
1357
|
-
|
1358
|
-
|
1436
|
+
var shouldNotify = notifyNextChange || (didUpdate && self.isDifferent(previousValue, pendingValue));
|
1437
|
+
|
1438
|
+
didUpdate = notifyNextChange = ignoreBeforeChange = false;
|
1439
|
+
|
1440
|
+
if (shouldNotify) {
|
1359
1441
|
self._origNotifySubscribers(previousValue = pendingValue);
|
1360
1442
|
}
|
1361
1443
|
});
|
1362
1444
|
|
1363
|
-
self._limitChange = function(value) {
|
1445
|
+
self._limitChange = function(value, isDirty) {
|
1446
|
+
if (!isDirty || !self._notificationIsPending) {
|
1447
|
+
didUpdate = !isDirty;
|
1448
|
+
}
|
1449
|
+
self._changeSubscriptions = self._subscriptions[defaultEvent].slice(0);
|
1364
1450
|
self._notificationIsPending = ignoreBeforeChange = true;
|
1365
1451
|
pendingValue = value;
|
1366
1452
|
finish();
|
@@ -1371,6 +1457,14 @@ var ko_subscribable_fn = {
|
|
1371
1457
|
self._origNotifySubscribers(value, beforeChange);
|
1372
1458
|
}
|
1373
1459
|
};
|
1460
|
+
self._recordUpdate = function() {
|
1461
|
+
didUpdate = true;
|
1462
|
+
};
|
1463
|
+
self._notifyNextChangeIfValueIsDifferent = function() {
|
1464
|
+
if (self.isDifferent(previousValue, self.peek(true /*evaluate*/))) {
|
1465
|
+
notifyNextChange = true;
|
1466
|
+
}
|
1467
|
+
};
|
1374
1468
|
},
|
1375
1469
|
|
1376
1470
|
hasSubscriptionsForEvent: function(event) {
|
@@ -1394,9 +1488,14 @@ var ko_subscribable_fn = {
|
|
1394
1488
|
return !this['equalityComparer'] || !this['equalityComparer'](oldValue, newValue);
|
1395
1489
|
},
|
1396
1490
|
|
1491
|
+
toString: function() {
|
1492
|
+
return '[object Object]'
|
1493
|
+
},
|
1494
|
+
|
1397
1495
|
extend: applyExtenders
|
1398
1496
|
};
|
1399
1497
|
|
1498
|
+
ko.exportProperty(ko_subscribable_fn, 'init', ko_subscribable_fn.init);
|
1400
1499
|
ko.exportProperty(ko_subscribable_fn, 'subscribe', ko_subscribable_fn.subscribe);
|
1401
1500
|
ko.exportProperty(ko_subscribable_fn, 'extend', ko_subscribable_fn.extend);
|
1402
1501
|
ko.exportProperty(ko_subscribable_fn, 'getSubscriptionsCount', ko_subscribable_fn.getSubscriptionsCount);
|
@@ -1469,16 +1568,28 @@ ko.computedContext = ko.dependencyDetection = (function () {
|
|
1469
1568
|
return currentFrame.computed.getDependenciesCount();
|
1470
1569
|
},
|
1471
1570
|
|
1571
|
+
getDependencies: function () {
|
1572
|
+
if (currentFrame)
|
1573
|
+
return currentFrame.computed.getDependencies();
|
1574
|
+
},
|
1575
|
+
|
1472
1576
|
isInitial: function() {
|
1473
1577
|
if (currentFrame)
|
1474
1578
|
return currentFrame.isInitial;
|
1579
|
+
},
|
1580
|
+
|
1581
|
+
computed: function() {
|
1582
|
+
if (currentFrame)
|
1583
|
+
return currentFrame.computed;
|
1475
1584
|
}
|
1476
1585
|
};
|
1477
1586
|
})();
|
1478
1587
|
|
1479
1588
|
ko.exportSymbol('computedContext', ko.computedContext);
|
1480
1589
|
ko.exportSymbol('computedContext.getDependenciesCount', ko.computedContext.getDependenciesCount);
|
1590
|
+
ko.exportSymbol('computedContext.getDependencies', ko.computedContext.getDependencies);
|
1481
1591
|
ko.exportSymbol('computedContext.isInitial', ko.computedContext.isInitial);
|
1592
|
+
ko.exportSymbol('computedContext.registerDependency', ko.computedContext.registerDependency);
|
1482
1593
|
|
1483
1594
|
ko.exportSymbol('ignoreDependencies', ko.ignoreDependencies = ko.dependencyDetection.ignore);
|
1484
1595
|
var observableLatestValue = ko.utils.createSymbolOrString('_latestValue');
|
@@ -1526,7 +1637,10 @@ ko.observable = function (initialValue) {
|
|
1526
1637
|
var observableFn = {
|
1527
1638
|
'equalityComparer': valuesArePrimitiveAndEqual,
|
1528
1639
|
peek: function() { return this[observableLatestValue]; },
|
1529
|
-
valueHasMutated: function () {
|
1640
|
+
valueHasMutated: function () {
|
1641
|
+
this['notifySubscribers'](this[observableLatestValue], 'spectate');
|
1642
|
+
this['notifySubscribers'](this[observableLatestValue]);
|
1643
|
+
},
|
1530
1644
|
valueWillMutate: function () { this['notifySubscribers'](this[observableLatestValue], 'beforeChange'); }
|
1531
1645
|
};
|
1532
1646
|
|
@@ -1539,25 +1653,19 @@ if (ko.utils.canSetPrototype) {
|
|
1539
1653
|
var protoProperty = ko.observable.protoProperty = '__ko_proto__';
|
1540
1654
|
observableFn[protoProperty] = ko.observable;
|
1541
1655
|
|
1542
|
-
ko.
|
1543
|
-
|
1544
|
-
if (
|
1545
|
-
|
1656
|
+
ko.isObservable = function (instance) {
|
1657
|
+
var proto = typeof instance == 'function' && instance[protoProperty];
|
1658
|
+
if (proto && proto !== observableFn[protoProperty] && proto !== ko.computed['fn'][protoProperty]) {
|
1659
|
+
throw Error("Invalid object that looks like an observable; possibly from another Knockout instance");
|
1660
|
+
}
|
1661
|
+
return !!proto;
|
1546
1662
|
};
|
1547
1663
|
|
1548
|
-
ko.isObservable = function (instance) {
|
1549
|
-
return ko.hasPrototype(instance, ko.observable);
|
1550
|
-
}
|
1551
1664
|
ko.isWriteableObservable = function (instance) {
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
if ((typeof instance == 'function') && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
|
1557
|
-
return true;
|
1558
|
-
// Anything else
|
1559
|
-
return false;
|
1560
|
-
}
|
1665
|
+
return (typeof instance == 'function' && (
|
1666
|
+
(instance[protoProperty] === observableFn[protoProperty]) || // Observable
|
1667
|
+
(instance[protoProperty] === ko.computed['fn'][protoProperty] && instance.hasWriteFunction))); // Writable computed observable
|
1668
|
+
};
|
1561
1669
|
|
1562
1670
|
ko.exportSymbol('observable', ko.observable);
|
1563
1671
|
ko.exportSymbol('isObservable', ko.isObservable);
|
@@ -1589,6 +1697,9 @@ ko.observableArray['fn'] = {
|
|
1589
1697
|
if (removedValues.length === 0) {
|
1590
1698
|
this.valueWillMutate();
|
1591
1699
|
}
|
1700
|
+
if (underlyingArray[i] !== value) {
|
1701
|
+
throw Error("Array modified during remove; cannot remove item");
|
1702
|
+
}
|
1592
1703
|
removedValues.push(value);
|
1593
1704
|
underlyingArray.splice(i, 1);
|
1594
1705
|
i--;
|
@@ -1625,7 +1736,7 @@ ko.observableArray['fn'] = {
|
|
1625
1736
|
for (var i = underlyingArray.length - 1; i >= 0; i--) {
|
1626
1737
|
var value = underlyingArray[i];
|
1627
1738
|
if (predicate(value))
|
1628
|
-
|
1739
|
+
value["_destroy"] = true;
|
1629
1740
|
}
|
1630
1741
|
this.valueHasMutated();
|
1631
1742
|
},
|
@@ -1655,6 +1766,15 @@ ko.observableArray['fn'] = {
|
|
1655
1766
|
this.peek()[index] = newItem;
|
1656
1767
|
this.valueHasMutated();
|
1657
1768
|
}
|
1769
|
+
},
|
1770
|
+
|
1771
|
+
'sorted': function (compareFunction) {
|
1772
|
+
var arrayCopy = this().slice(0);
|
1773
|
+
return compareFunction ? arrayCopy.sort(compareFunction) : arrayCopy.sort();
|
1774
|
+
},
|
1775
|
+
|
1776
|
+
'reversed': function () {
|
1777
|
+
return this().slice(0).reverse();
|
1658
1778
|
}
|
1659
1779
|
};
|
1660
1780
|
|
@@ -1689,7 +1809,14 @@ ko.utils.arrayForEach(["slice"], function (methodName) {
|
|
1689
1809
|
};
|
1690
1810
|
});
|
1691
1811
|
|
1812
|
+
ko.isObservableArray = function (instance) {
|
1813
|
+
return ko.isObservable(instance)
|
1814
|
+
&& typeof instance["remove"] == "function"
|
1815
|
+
&& typeof instance["push"] == "function";
|
1816
|
+
};
|
1817
|
+
|
1692
1818
|
ko.exportSymbol('observableArray', ko.observableArray);
|
1819
|
+
ko.exportSymbol('isObservableArray', ko.isObservableArray);
|
1693
1820
|
var arrayChangeEventName = 'arrayChange';
|
1694
1821
|
ko.extenders['trackArrayChanges'] = function(target, options) {
|
1695
1822
|
// Use the provided options--each call to trackArrayChanges overwrites the previously set options
|
@@ -1707,6 +1834,8 @@ ko.extenders['trackArrayChanges'] = function(target, options) {
|
|
1707
1834
|
cachedDiff = null,
|
1708
1835
|
arrayChangeSubscription,
|
1709
1836
|
pendingNotifications = 0,
|
1837
|
+
previousContents,
|
1838
|
+
underlyingNotifySubscribersFunction,
|
1710
1839
|
underlyingBeforeSubscriptionAddFunction = target.beforeSubscriptionAdd,
|
1711
1840
|
underlyingAfterSubscriptionRemoveFunction = target.afterSubscriptionRemove;
|
1712
1841
|
|
@@ -1723,21 +1852,31 @@ ko.extenders['trackArrayChanges'] = function(target, options) {
|
|
1723
1852
|
if (underlyingAfterSubscriptionRemoveFunction)
|
1724
1853
|
underlyingAfterSubscriptionRemoveFunction.call(target, event);
|
1725
1854
|
if (event === arrayChangeEventName && !target.hasSubscriptionsForEvent(arrayChangeEventName)) {
|
1726
|
-
|
1855
|
+
if (underlyingNotifySubscribersFunction) {
|
1856
|
+
target['notifySubscribers'] = underlyingNotifySubscribersFunction;
|
1857
|
+
underlyingNotifySubscribersFunction = undefined;
|
1858
|
+
}
|
1859
|
+
if (arrayChangeSubscription) {
|
1860
|
+
arrayChangeSubscription.dispose();
|
1861
|
+
}
|
1862
|
+
arrayChangeSubscription = null;
|
1727
1863
|
trackingChanges = false;
|
1864
|
+
previousContents = undefined;
|
1728
1865
|
}
|
1729
1866
|
};
|
1730
1867
|
|
1731
1868
|
function trackChanges() {
|
1732
|
-
// Calling 'trackChanges' multiple times is the same as calling it once
|
1733
1869
|
if (trackingChanges) {
|
1870
|
+
// Whenever there's a new subscription and there are pending notifications, make sure all previous
|
1871
|
+
// subscriptions are notified of the change so that all subscriptions are in sync.
|
1872
|
+
notifyChanges();
|
1734
1873
|
return;
|
1735
1874
|
}
|
1736
1875
|
|
1737
1876
|
trackingChanges = true;
|
1738
1877
|
|
1739
1878
|
// Intercept "notifySubscribers" to track how many times it was called.
|
1740
|
-
|
1879
|
+
underlyingNotifySubscribersFunction = target['notifySubscribers'];
|
1741
1880
|
target['notifySubscribers'] = function(valueToNotify, event) {
|
1742
1881
|
if (!event || event === defaultEvent) {
|
1743
1882
|
++pendingNotifications;
|
@@ -1747,26 +1886,30 @@ ko.extenders['trackArrayChanges'] = function(target, options) {
|
|
1747
1886
|
|
1748
1887
|
// Each time the array changes value, capture a clone so that on the next
|
1749
1888
|
// change it's possible to produce a diff
|
1750
|
-
|
1889
|
+
previousContents = [].concat(target.peek() || []);
|
1751
1890
|
cachedDiff = null;
|
1752
|
-
arrayChangeSubscription = target.subscribe(
|
1753
|
-
// Make a copy of the current contents and ensure it's an array
|
1754
|
-
currentContents = [].concat(currentContents || []);
|
1891
|
+
arrayChangeSubscription = target.subscribe(notifyChanges);
|
1755
1892
|
|
1756
|
-
|
1757
|
-
if (
|
1758
|
-
|
1759
|
-
|
1893
|
+
function notifyChanges() {
|
1894
|
+
if (pendingNotifications) {
|
1895
|
+
// Make a copy of the current contents and ensure it's an array
|
1896
|
+
var currentContents = [].concat(target.peek() || []);
|
1760
1897
|
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1898
|
+
// Compute the diff and issue notifications, but only if someone is listening
|
1899
|
+
if (target.hasSubscriptionsForEvent(arrayChangeEventName)) {
|
1900
|
+
var changes = getChanges(previousContents, currentContents);
|
1901
|
+
}
|
1765
1902
|
|
1766
|
-
|
1767
|
-
|
1903
|
+
// Eliminate references to the old, removed items, so they can be GCed
|
1904
|
+
previousContents = currentContents;
|
1905
|
+
cachedDiff = null;
|
1906
|
+
pendingNotifications = 0;
|
1907
|
+
|
1908
|
+
if (changes && changes.length) {
|
1909
|
+
target['notifySubscribers'](changes, arrayChangeEventName);
|
1910
|
+
}
|
1768
1911
|
}
|
1769
|
-
}
|
1912
|
+
}
|
1770
1913
|
}
|
1771
1914
|
|
1772
1915
|
function getChanges(previousContents, currentContents) {
|
@@ -1855,6 +1998,7 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1855
1998
|
var state = {
|
1856
1999
|
latestValue: undefined,
|
1857
2000
|
isStale: true,
|
2001
|
+
isDirty: true,
|
1858
2002
|
isBeingEvaluated: false,
|
1859
2003
|
suppressDisposalUntilDisposeWhenReturnsFalse: false,
|
1860
2004
|
isDisposed: false,
|
@@ -1881,8 +2025,10 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1881
2025
|
return this; // Permits chained assignments
|
1882
2026
|
} else {
|
1883
2027
|
// Reading the value
|
1884
|
-
|
1885
|
-
|
2028
|
+
if (!state.isDisposed) {
|
2029
|
+
ko.dependencyDetection.registerDependency(computedObservable);
|
2030
|
+
}
|
2031
|
+
if (state.isDirty || (state.isSleeping && computedObservable.haveDependenciesChanged())) {
|
1886
2032
|
computedObservable.evaluateImmediate();
|
1887
2033
|
}
|
1888
2034
|
return state.latestValue;
|
@@ -1972,6 +2118,10 @@ function computedBeginDependencyDetectionCallback(subscribable, id) {
|
|
1972
2118
|
// Brand new subscription - add it
|
1973
2119
|
computedObservable.addDependencyTracking(id, subscribable, state.isSleeping ? { _target: subscribable } : computedObservable.subscribeToDependency(subscribable));
|
1974
2120
|
}
|
2121
|
+
// If the observable we've accessed has a pending notification, ensure we get notified of the actual final value (bypass equality checks)
|
2122
|
+
if (subscribable._notificationIsPending) {
|
2123
|
+
subscribable._notifyNextChangeIfValueIsDifferent();
|
2124
|
+
}
|
1975
2125
|
}
|
1976
2126
|
}
|
1977
2127
|
|
@@ -1980,6 +2130,27 @@ var computedFn = {
|
|
1980
2130
|
getDependenciesCount: function () {
|
1981
2131
|
return this[computedState].dependenciesCount;
|
1982
2132
|
},
|
2133
|
+
getDependencies: function () {
|
2134
|
+
var dependencyTracking = this[computedState].dependencyTracking, dependentObservables = [];
|
2135
|
+
|
2136
|
+
ko.utils.objectForEach(dependencyTracking, function (id, dependency) {
|
2137
|
+
dependentObservables[dependency._order] = dependency._target;
|
2138
|
+
});
|
2139
|
+
|
2140
|
+
return dependentObservables;
|
2141
|
+
},
|
2142
|
+
hasAncestorDependency: function (obs) {
|
2143
|
+
if (!this[computedState].dependenciesCount) {
|
2144
|
+
return false;
|
2145
|
+
}
|
2146
|
+
var dependencies = this.getDependencies();
|
2147
|
+
if (ko.utils.arrayIndexOf(dependencies, obs) !== -1) {
|
2148
|
+
return true;
|
2149
|
+
}
|
2150
|
+
return !!ko.utils.arrayFirst(dependencies, function (dep) {
|
2151
|
+
return dep.hasAncestorDependency && dep.hasAncestorDependency(obs);
|
2152
|
+
});
|
2153
|
+
},
|
1983
2154
|
addDependencyTracking: function (id, target, trackingObj) {
|
1984
2155
|
if (this[computedState].pure && target === this) {
|
1985
2156
|
throw Error("A 'pure' computed must not be called recursively");
|
@@ -1992,9 +2163,9 @@ var computedFn = {
|
|
1992
2163
|
haveDependenciesChanged: function () {
|
1993
2164
|
var id, dependency, dependencyTracking = this[computedState].dependencyTracking;
|
1994
2165
|
for (id in dependencyTracking) {
|
1995
|
-
if (
|
2166
|
+
if (Object.prototype.hasOwnProperty.call(dependencyTracking, id)) {
|
1996
2167
|
dependency = dependencyTracking[id];
|
1997
|
-
if (dependency._target.hasChanged(dependency._version)) {
|
2168
|
+
if ((this._evalDelayed && dependency._target._notificationIsPending) || dependency._target.hasChanged(dependency._version)) {
|
1998
2169
|
return true;
|
1999
2170
|
}
|
2000
2171
|
}
|
@@ -2003,20 +2174,23 @@ var computedFn = {
|
|
2003
2174
|
markDirty: function () {
|
2004
2175
|
// Process "dirty" events if we can handle delayed notifications
|
2005
2176
|
if (this._evalDelayed && !this[computedState].isBeingEvaluated) {
|
2006
|
-
this._evalDelayed();
|
2177
|
+
this._evalDelayed(false /*isChange*/);
|
2007
2178
|
}
|
2008
2179
|
},
|
2009
2180
|
isActive: function () {
|
2010
|
-
|
2181
|
+
var state = this[computedState];
|
2182
|
+
return state.isDirty || state.dependenciesCount > 0;
|
2011
2183
|
},
|
2012
2184
|
respondToChange: function () {
|
2013
2185
|
// Ignore "change" events if we've already scheduled a delayed notification
|
2014
2186
|
if (!this._notificationIsPending) {
|
2015
2187
|
this.evaluatePossiblyAsync();
|
2188
|
+
} else if (this[computedState].isDirty) {
|
2189
|
+
this[computedState].isStale = true;
|
2016
2190
|
}
|
2017
2191
|
},
|
2018
2192
|
subscribeToDependency: function (target) {
|
2019
|
-
if (target._deferUpdates
|
2193
|
+
if (target._deferUpdates) {
|
2020
2194
|
var dirtySub = target.subscribe(this.markDirty, this, 'dirty'),
|
2021
2195
|
changeSub = target.subscribe(this.respondToChange, this);
|
2022
2196
|
return {
|
@@ -2039,7 +2213,7 @@ var computedFn = {
|
|
2039
2213
|
computedObservable.evaluateImmediate(true /*notifyChange*/);
|
2040
2214
|
}, throttleEvaluationTimeout);
|
2041
2215
|
} else if (computedObservable._evalDelayed) {
|
2042
|
-
computedObservable._evalDelayed();
|
2216
|
+
computedObservable._evalDelayed(true /*isChange*/);
|
2043
2217
|
} else {
|
2044
2218
|
computedObservable.evaluateImmediate(true /*notifyChange*/);
|
2045
2219
|
}
|
@@ -2047,7 +2221,8 @@ var computedFn = {
|
|
2047
2221
|
evaluateImmediate: function (notifyChange) {
|
2048
2222
|
var computedObservable = this,
|
2049
2223
|
state = computedObservable[computedState],
|
2050
|
-
disposeWhen = state.disposeWhen
|
2224
|
+
disposeWhen = state.disposeWhen,
|
2225
|
+
changed = false;
|
2051
2226
|
|
2052
2227
|
if (state.isBeingEvaluated) {
|
2053
2228
|
// If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
|
@@ -2075,14 +2250,12 @@ var computedFn = {
|
|
2075
2250
|
|
2076
2251
|
state.isBeingEvaluated = true;
|
2077
2252
|
try {
|
2078
|
-
this.evaluateImmediate_CallReadWithDependencyDetection(notifyChange);
|
2253
|
+
changed = this.evaluateImmediate_CallReadWithDependencyDetection(notifyChange);
|
2079
2254
|
} finally {
|
2080
2255
|
state.isBeingEvaluated = false;
|
2081
2256
|
}
|
2082
2257
|
|
2083
|
-
|
2084
|
-
computedObservable.dispose();
|
2085
|
-
}
|
2258
|
+
return changed;
|
2086
2259
|
},
|
2087
2260
|
evaluateImmediate_CallReadWithDependencyDetection: function (notifyChange) {
|
2088
2261
|
// This function is really just part of the evaluateImmediate logic. You would never call it from anywhere else.
|
@@ -2090,7 +2263,8 @@ var computedFn = {
|
|
2090
2263
|
// which contributes to saving about 40% off the CPU overhead of computed evaluation (on V8 at least).
|
2091
2264
|
|
2092
2265
|
var computedObservable = this,
|
2093
|
-
state = computedObservable[computedState]
|
2266
|
+
state = computedObservable[computedState],
|
2267
|
+
changed = false;
|
2094
2268
|
|
2095
2269
|
// Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
|
2096
2270
|
// Then, during evaluation, we cross off any that are in fact still being used.
|
@@ -2113,23 +2287,38 @@ var computedFn = {
|
|
2113
2287
|
|
2114
2288
|
var newValue = this.evaluateImmediate_CallReadThenEndDependencyDetection(state, dependencyDetectionContext);
|
2115
2289
|
|
2116
|
-
if (
|
2290
|
+
if (!state.dependenciesCount) {
|
2291
|
+
computedObservable.dispose();
|
2292
|
+
changed = true; // When evaluation causes a disposal, make sure all dependent computeds get notified so they'll see the new state
|
2293
|
+
} else {
|
2294
|
+
changed = computedObservable.isDifferent(state.latestValue, newValue);
|
2295
|
+
}
|
2296
|
+
|
2297
|
+
if (changed) {
|
2117
2298
|
if (!state.isSleeping) {
|
2118
2299
|
computedObservable["notifySubscribers"](state.latestValue, "beforeChange");
|
2300
|
+
} else {
|
2301
|
+
computedObservable.updateVersion();
|
2119
2302
|
}
|
2120
2303
|
|
2121
2304
|
state.latestValue = newValue;
|
2305
|
+
if (DEBUG) computedObservable._latestValue = newValue;
|
2122
2306
|
|
2123
|
-
|
2124
|
-
|
2125
|
-
|
2307
|
+
computedObservable["notifySubscribers"](state.latestValue, "spectate");
|
2308
|
+
|
2309
|
+
if (!state.isSleeping && notifyChange) {
|
2126
2310
|
computedObservable["notifySubscribers"](state.latestValue);
|
2127
2311
|
}
|
2312
|
+
if (computedObservable._recordUpdate) {
|
2313
|
+
computedObservable._recordUpdate();
|
2314
|
+
}
|
2128
2315
|
}
|
2129
2316
|
|
2130
2317
|
if (isInitial) {
|
2131
2318
|
computedObservable["notifySubscribers"](state.latestValue, "awake");
|
2132
2319
|
}
|
2320
|
+
|
2321
|
+
return changed;
|
2133
2322
|
},
|
2134
2323
|
evaluateImmediate_CallReadThenEndDependencyDetection: function (state, dependencyDetectionContext) {
|
2135
2324
|
// This function is really part of the evaluateImmediate_CallReadWithDependencyDetection logic.
|
@@ -2148,13 +2337,14 @@ var computedFn = {
|
|
2148
2337
|
ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback);
|
2149
2338
|
}
|
2150
2339
|
|
2151
|
-
state.isStale = false;
|
2340
|
+
state.isStale = state.isDirty = false;
|
2152
2341
|
}
|
2153
2342
|
},
|
2154
|
-
peek: function () {
|
2155
|
-
//
|
2343
|
+
peek: function (evaluate) {
|
2344
|
+
// By default, peek won't re-evaluate, except while the computed is sleeping or to get the initial value when "deferEvaluation" is set.
|
2345
|
+
// Pass in true to evaluate if needed.
|
2156
2346
|
var state = this[computedState];
|
2157
|
-
if ((state.
|
2347
|
+
if ((state.isDirty && (evaluate || !state.dependenciesCount)) || (state.isSleeping && this.haveDependenciesChanged())) {
|
2158
2348
|
this.evaluateImmediate();
|
2159
2349
|
}
|
2160
2350
|
return state.latestValue;
|
@@ -2162,15 +2352,29 @@ var computedFn = {
|
|
2162
2352
|
limit: function (limitFunction) {
|
2163
2353
|
// Override the limit function with one that delays evaluation as well
|
2164
2354
|
ko.subscribable['fn'].limit.call(this, limitFunction);
|
2165
|
-
this.
|
2355
|
+
this._evalIfChanged = function () {
|
2356
|
+
if (!this[computedState].isSleeping) {
|
2357
|
+
if (this[computedState].isStale) {
|
2358
|
+
this.evaluateImmediate();
|
2359
|
+
} else {
|
2360
|
+
this[computedState].isDirty = false;
|
2361
|
+
}
|
2362
|
+
}
|
2363
|
+
return this[computedState].latestValue;
|
2364
|
+
};
|
2365
|
+
this._evalDelayed = function (isChange) {
|
2166
2366
|
this._limitBeforeChange(this[computedState].latestValue);
|
2167
2367
|
|
2168
|
-
|
2368
|
+
// Mark as dirty
|
2369
|
+
this[computedState].isDirty = true;
|
2370
|
+
if (isChange) {
|
2371
|
+
this[computedState].isStale = true;
|
2372
|
+
}
|
2169
2373
|
|
2170
|
-
// Pass the observable to the "limit" code, which will
|
2374
|
+
// Pass the observable to the "limit" code, which will evaluate it when
|
2171
2375
|
// it's time to do the notification.
|
2172
|
-
this._limitChange(this);
|
2173
|
-
}
|
2376
|
+
this._limitChange(this, !isChange /* isDirty */);
|
2377
|
+
};
|
2174
2378
|
},
|
2175
2379
|
dispose: function () {
|
2176
2380
|
var state = this[computedState];
|
@@ -2183,12 +2387,18 @@ var computedFn = {
|
|
2183
2387
|
if (state.disposeWhenNodeIsRemoved && state.domNodeDisposalCallback) {
|
2184
2388
|
ko.utils.domNodeDisposal.removeDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback);
|
2185
2389
|
}
|
2186
|
-
state.dependencyTracking =
|
2390
|
+
state.dependencyTracking = undefined;
|
2187
2391
|
state.dependenciesCount = 0;
|
2188
2392
|
state.isDisposed = true;
|
2189
2393
|
state.isStale = false;
|
2394
|
+
state.isDirty = false;
|
2190
2395
|
state.isSleeping = false;
|
2191
|
-
state.disposeWhenNodeIsRemoved =
|
2396
|
+
state.disposeWhenNodeIsRemoved = undefined;
|
2397
|
+
state.disposeWhen = undefined;
|
2398
|
+
state.readFunction = undefined;
|
2399
|
+
if (!this.hasWriteFunction) {
|
2400
|
+
state.evaluatorFunctionTarget = undefined;
|
2401
|
+
}
|
2192
2402
|
}
|
2193
2403
|
};
|
2194
2404
|
|
@@ -2202,23 +2412,31 @@ var pureComputedOverrides = {
|
|
2202
2412
|
if (state.isStale || computedObservable.haveDependenciesChanged()) {
|
2203
2413
|
state.dependencyTracking = null;
|
2204
2414
|
state.dependenciesCount = 0;
|
2205
|
-
|
2206
|
-
|
2415
|
+
if (computedObservable.evaluateImmediate()) {
|
2416
|
+
computedObservable.updateVersion();
|
2417
|
+
}
|
2207
2418
|
} else {
|
2208
2419
|
// First put the dependencies in order
|
2209
|
-
var
|
2420
|
+
var dependenciesOrder = [];
|
2210
2421
|
ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
|
2211
|
-
|
2422
|
+
dependenciesOrder[dependency._order] = id;
|
2212
2423
|
});
|
2213
2424
|
// Next, subscribe to each one
|
2214
|
-
ko.utils.arrayForEach(
|
2425
|
+
ko.utils.arrayForEach(dependenciesOrder, function (id, order) {
|
2215
2426
|
var dependency = state.dependencyTracking[id],
|
2216
2427
|
subscription = computedObservable.subscribeToDependency(dependency._target);
|
2217
2428
|
subscription._order = order;
|
2218
2429
|
subscription._version = dependency._version;
|
2219
2430
|
state.dependencyTracking[id] = subscription;
|
2220
2431
|
});
|
2432
|
+
// Waking dependencies may have triggered effects
|
2433
|
+
if (computedObservable.haveDependenciesChanged()) {
|
2434
|
+
if (computedObservable.evaluateImmediate()) {
|
2435
|
+
computedObservable.updateVersion();
|
2436
|
+
}
|
2437
|
+
}
|
2221
2438
|
}
|
2439
|
+
|
2222
2440
|
if (!state.isDisposed) { // test since evaluating could trigger disposal
|
2223
2441
|
computedObservable["notifySubscribers"](state.latestValue, "awake");
|
2224
2442
|
}
|
@@ -2268,18 +2486,16 @@ if (ko.utils.canSetPrototype) {
|
|
2268
2486
|
ko.utils.setPrototypeOf(computedFn, ko.subscribable['fn']);
|
2269
2487
|
}
|
2270
2488
|
|
2271
|
-
// Set the proto
|
2489
|
+
// Set the proto values for ko.computed
|
2272
2490
|
var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
|
2273
|
-
ko.computed[protoProp] = ko.observable;
|
2274
2491
|
computedFn[protoProp] = ko.computed;
|
2275
2492
|
|
2276
2493
|
ko.isComputed = function (instance) {
|
2277
|
-
return
|
2494
|
+
return (typeof instance == 'function' && instance[protoProp] === computedFn[protoProp]);
|
2278
2495
|
};
|
2279
2496
|
|
2280
2497
|
ko.isPureComputed = function (instance) {
|
2281
|
-
return ko.
|
2282
|
-
&& instance[computedState] && instance[computedState].pure;
|
2498
|
+
return ko.isComputed(instance) && instance[computedState] && instance[computedState].pure;
|
2283
2499
|
};
|
2284
2500
|
|
2285
2501
|
ko.exportSymbol('computed', ko.computed);
|
@@ -2291,6 +2507,7 @@ ko.exportProperty(computedFn, 'peek', computedFn.peek);
|
|
2291
2507
|
ko.exportProperty(computedFn, 'dispose', computedFn.dispose);
|
2292
2508
|
ko.exportProperty(computedFn, 'isActive', computedFn.isActive);
|
2293
2509
|
ko.exportProperty(computedFn, 'getDependenciesCount', computedFn.getDependenciesCount);
|
2510
|
+
ko.exportProperty(computedFn, 'getDependencies', computedFn.getDependencies);
|
2294
2511
|
|
2295
2512
|
ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget) {
|
2296
2513
|
if (typeof evaluatorFunctionOrOptions === 'function') {
|
@@ -2304,7 +2521,7 @@ ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget)
|
|
2304
2521
|
ko.exportSymbol('pureComputed', ko.pureComputed);
|
2305
2522
|
|
2306
2523
|
(function() {
|
2307
|
-
var maxNestedObservableDepth = 10; // Escape the (unlikely)
|
2524
|
+
var maxNestedObservableDepth = 10; // Escape the (unlikely) pathological case where an observable's current value is itself (or similar reference cycle)
|
2308
2525
|
|
2309
2526
|
ko.toJS = function(rootObject) {
|
2310
2527
|
if (arguments.length == 0)
|
@@ -2398,6 +2615,28 @@ ko.exportSymbol('pureComputed', ko.pureComputed);
|
|
2398
2615
|
|
2399
2616
|
ko.exportSymbol('toJS', ko.toJS);
|
2400
2617
|
ko.exportSymbol('toJSON', ko.toJSON);
|
2618
|
+
ko.when = function(predicate, callback, context) {
|
2619
|
+
function kowhen (resolve) {
|
2620
|
+
var observable = ko.pureComputed(predicate, context).extend({notify:'always'});
|
2621
|
+
var subscription = observable.subscribe(function(value) {
|
2622
|
+
if (value) {
|
2623
|
+
subscription.dispose();
|
2624
|
+
resolve(value);
|
2625
|
+
}
|
2626
|
+
});
|
2627
|
+
// In case the initial value is true, process it right away
|
2628
|
+
observable['notifySubscribers'](observable.peek());
|
2629
|
+
|
2630
|
+
return subscription;
|
2631
|
+
}
|
2632
|
+
if (typeof Promise === "function" && !callback) {
|
2633
|
+
return new Promise(kowhen);
|
2634
|
+
} else {
|
2635
|
+
return kowhen(callback.bind(context));
|
2636
|
+
}
|
2637
|
+
};
|
2638
|
+
|
2639
|
+
ko.exportSymbol('when', ko.when);
|
2401
2640
|
(function () {
|
2402
2641
|
var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
|
2403
2642
|
|
@@ -2423,22 +2662,20 @@ ko.exportSymbol('toJSON', ko.toJSON);
|
|
2423
2662
|
writeValue: function(element, value, allowUnset) {
|
2424
2663
|
switch (ko.utils.tagNameLower(element)) {
|
2425
2664
|
case 'option':
|
2426
|
-
|
2427
|
-
|
2428
|
-
|
2429
|
-
|
2430
|
-
|
2431
|
-
|
2432
|
-
|
2433
|
-
|
2434
|
-
|
2435
|
-
|
2436
|
-
|
2437
|
-
element[hasDomDataExpandoProperty] = true;
|
2665
|
+
if (typeof value === "string") {
|
2666
|
+
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
|
2667
|
+
if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
|
2668
|
+
delete element[hasDomDataExpandoProperty];
|
2669
|
+
}
|
2670
|
+
element.value = value;
|
2671
|
+
}
|
2672
|
+
else {
|
2673
|
+
// Store arbitrary object using DomData
|
2674
|
+
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
|
2675
|
+
element[hasDomDataExpandoProperty] = true;
|
2438
2676
|
|
2439
|
-
|
2440
|
-
|
2441
|
-
break;
|
2677
|
+
// Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
|
2678
|
+
element.value = typeof value === "number" ? value : "";
|
2442
2679
|
}
|
2443
2680
|
break;
|
2444
2681
|
case 'select':
|
@@ -2448,13 +2685,21 @@ ko.exportSymbol('toJSON', ko.toJSON);
|
|
2448
2685
|
for (var i = 0, n = element.options.length, optionValue; i < n; ++i) {
|
2449
2686
|
optionValue = ko.selectExtensions.readValue(element.options[i]);
|
2450
2687
|
// Include special check to handle selecting a caption with a blank string value
|
2451
|
-
if (optionValue == value || (optionValue
|
2688
|
+
if (optionValue == value || (optionValue === "" && value === undefined)) {
|
2452
2689
|
selection = i;
|
2453
2690
|
break;
|
2454
2691
|
}
|
2455
2692
|
}
|
2456
2693
|
if (allowUnset || selection >= 0 || (value === undefined && element.size > 1)) {
|
2457
2694
|
element.selectedIndex = selection;
|
2695
|
+
if (ko.utils.ieVersion === 6) {
|
2696
|
+
// Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
|
2697
|
+
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
|
2698
|
+
// to apply the value as well.
|
2699
|
+
ko.utils.setTimeout(function () {
|
2700
|
+
element.selectedIndex = selection;
|
2701
|
+
}, 0);
|
2702
|
+
}
|
2458
2703
|
}
|
2459
2704
|
break;
|
2460
2705
|
default:
|
@@ -2487,26 +2732,29 @@ ko.expressionRewriting = (function () {
|
|
2487
2732
|
|
2488
2733
|
// The following regular expressions will be used to split an object-literal string into tokens
|
2489
2734
|
|
2490
|
-
|
2491
|
-
|
2492
|
-
|
2493
|
-
|
2494
|
-
|
2495
|
-
|
2496
|
-
|
2497
|
-
|
2498
|
-
|
2499
|
-
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
2503
|
-
|
2504
|
-
|
2505
|
-
|
2506
|
-
|
2507
|
-
|
2508
|
-
|
2509
|
-
|
2735
|
+
var specials = ',"\'`{}()/:[\\]', // These characters have special meaning to the parser and must not appear in the middle of a token, except as part of a string.
|
2736
|
+
// Create the actual regular expression by or-ing the following regex strings. The order is important.
|
2737
|
+
bindingToken = RegExp([
|
2738
|
+
// These match strings, either with double quotes, single quotes, or backticks
|
2739
|
+
'"(?:\\\\.|[^"])*"',
|
2740
|
+
"'(?:\\\\.|[^'])*'",
|
2741
|
+
"`(?:\\\\.|[^`])*`",
|
2742
|
+
// Match C style comments
|
2743
|
+
"/\\*(?:[^*]|\\*+[^*/])*\\*+/",
|
2744
|
+
// Match C++ style comments
|
2745
|
+
"//.*\n",
|
2746
|
+
// Match a regular expression (text enclosed by slashes), but will also match sets of divisions
|
2747
|
+
// as a regular expression (this is handled by the parsing loop below).
|
2748
|
+
'/(?:\\\\.|[^/])+/\w*',
|
2749
|
+
// Match text (at least two characters) that does not contain any of the above special characters,
|
2750
|
+
// although some of the special characters are allowed to start it (all but the colon and comma).
|
2751
|
+
// The text can contain spaces, but leading or trailing spaces are skipped.
|
2752
|
+
'[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']',
|
2753
|
+
// Match any non-space character not matched already. This will match colons and commas, since they're
|
2754
|
+
// not matched by "everyThingElse", but will also match any other single character that wasn't already
|
2755
|
+
// matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace).
|
2756
|
+
'[^\\s]'
|
2757
|
+
].join('|'), 'g'),
|
2510
2758
|
|
2511
2759
|
// Match end of previous token to determine whether a slash is a division or regex.
|
2512
2760
|
divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/,
|
@@ -2519,13 +2767,14 @@ ko.expressionRewriting = (function () {
|
|
2519
2767
|
// Trim braces '{' surrounding the whole object literal
|
2520
2768
|
if (str.charCodeAt(0) === 123) str = str.slice(1, -1);
|
2521
2769
|
|
2770
|
+
// Add a newline to correctly match a C++ style comment at the end of the string and
|
2771
|
+
// add a comma so that we don't need a separate code block to deal with the last item
|
2772
|
+
str += "\n,";
|
2773
|
+
|
2522
2774
|
// Split into tokens
|
2523
2775
|
var result = [], toks = str.match(bindingToken), key, values = [], depth = 0;
|
2524
2776
|
|
2525
|
-
if (toks) {
|
2526
|
-
// Append a comma so that we don't need a separate code block to deal with the last item
|
2527
|
-
toks.push(',');
|
2528
|
-
|
2777
|
+
if (toks.length > 1) {
|
2529
2778
|
for (var i = 0, tok; tok = toks[i]; ++i) {
|
2530
2779
|
var c = tok.charCodeAt(0);
|
2531
2780
|
// A comma signals the end of a key/value pair if depth is zero
|
@@ -2542,6 +2791,9 @@ ko.expressionRewriting = (function () {
|
|
2542
2791
|
key = values.pop();
|
2543
2792
|
continue;
|
2544
2793
|
}
|
2794
|
+
// Comments: skip them
|
2795
|
+
} else if (c === 47 && tok.length > 1 && (tok.charCodeAt(1) === 47 || tok.charCodeAt(1) === 42)) { // "//" or "/*"
|
2796
|
+
continue;
|
2545
2797
|
// A set of slashes is initially matched as a regular expression, but could be division
|
2546
2798
|
} else if (c === 47 && i && tok.length > 1) { // "/"
|
2547
2799
|
// Look at the end of the previous token to determine if the slash is actually division
|
@@ -2550,7 +2802,6 @@ ko.expressionRewriting = (function () {
|
|
2550
2802
|
// The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash)
|
2551
2803
|
str = str.substr(str.indexOf(tok) + 1);
|
2552
2804
|
toks = str.match(bindingToken);
|
2553
|
-
toks.push(',');
|
2554
2805
|
i = -1;
|
2555
2806
|
// Continue with just the slash
|
2556
2807
|
tok = '/';
|
@@ -2566,6 +2817,9 @@ ko.expressionRewriting = (function () {
|
|
2566
2817
|
}
|
2567
2818
|
values.push(tok);
|
2568
2819
|
}
|
2820
|
+
if (depth > 0) {
|
2821
|
+
throw Error("Unbalanced parentheses, braces, or brackets");
|
2822
|
+
}
|
2569
2823
|
}
|
2570
2824
|
return result;
|
2571
2825
|
}
|
@@ -2588,7 +2842,8 @@ ko.expressionRewriting = (function () {
|
|
2588
2842
|
if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
|
2589
2843
|
// For two-way bindings, provide a write method in case the value
|
2590
2844
|
// isn't a writable observable.
|
2591
|
-
|
2845
|
+
var writeKey = typeof twoWayBindings[key] == 'string' ? twoWayBindings[key] : key;
|
2846
|
+
propertyAccessorResultStrings.push("'" + writeKey + "':function(_z){" + writableVal + "=_z}");
|
2592
2847
|
}
|
2593
2848
|
}
|
2594
2849
|
// Values are wrapped in a function so that each value can be accessed independently
|
@@ -2696,12 +2951,19 @@ ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.ex
|
|
2696
2951
|
return (node.nodeType == 8) && endCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue);
|
2697
2952
|
}
|
2698
2953
|
|
2954
|
+
function isUnmatchedEndComment(node) {
|
2955
|
+
return isEndComment(node) && !(ko.utils.domData.get(node, matchedEndCommentDataKey));
|
2956
|
+
}
|
2957
|
+
|
2958
|
+
var matchedEndCommentDataKey = "__ko_matchedEndComment__"
|
2959
|
+
|
2699
2960
|
function getVirtualChildren(startComment, allowUnbalanced) {
|
2700
2961
|
var currentNode = startComment;
|
2701
2962
|
var depth = 1;
|
2702
2963
|
var children = [];
|
2703
2964
|
while (currentNode = currentNode.nextSibling) {
|
2704
2965
|
if (isEndComment(currentNode)) {
|
2966
|
+
ko.utils.domData.set(currentNode, matchedEndCommentDataKey, true);
|
2705
2967
|
depth--;
|
2706
2968
|
if (depth === 0)
|
2707
2969
|
return children;
|
@@ -2805,19 +3067,32 @@ ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.ex
|
|
2805
3067
|
},
|
2806
3068
|
|
2807
3069
|
firstChild: function(node) {
|
2808
|
-
if (!isStartComment(node))
|
3070
|
+
if (!isStartComment(node)) {
|
3071
|
+
if (node.firstChild && isEndComment(node.firstChild)) {
|
3072
|
+
throw new Error("Found invalid end comment, as the first child of " + node);
|
3073
|
+
}
|
2809
3074
|
return node.firstChild;
|
2810
|
-
if (!node.nextSibling || isEndComment(node.nextSibling))
|
3075
|
+
} else if (!node.nextSibling || isEndComment(node.nextSibling)) {
|
2811
3076
|
return null;
|
2812
|
-
|
3077
|
+
} else {
|
3078
|
+
return node.nextSibling;
|
3079
|
+
}
|
2813
3080
|
},
|
2814
3081
|
|
2815
3082
|
nextSibling: function(node) {
|
2816
|
-
if (isStartComment(node))
|
3083
|
+
if (isStartComment(node)) {
|
2817
3084
|
node = getMatchingEndComment(node);
|
2818
|
-
|
2819
|
-
|
2820
|
-
|
3085
|
+
}
|
3086
|
+
|
3087
|
+
if (node.nextSibling && isEndComment(node.nextSibling)) {
|
3088
|
+
if (isUnmatchedEndComment(node.nextSibling)) {
|
3089
|
+
throw Error("Found end comment without a matching opening comment, as child of " + node);
|
3090
|
+
} else {
|
3091
|
+
return null;
|
3092
|
+
}
|
3093
|
+
} else {
|
3094
|
+
return node.nextSibling;
|
3095
|
+
}
|
2821
3096
|
},
|
2822
3097
|
|
2823
3098
|
hasBindingValue: isStartComment,
|
@@ -2939,6 +3214,11 @@ ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomN
|
|
2939
3214
|
|
2940
3215
|
ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
2941
3216
|
(function () {
|
3217
|
+
// Hide or don't minify context properties, see https://github.com/knockout/knockout/issues/2294
|
3218
|
+
var contextSubscribable = ko.utils.createSymbolOrString('_subscribable');
|
3219
|
+
var contextAncestorBindingInfo = ko.utils.createSymbolOrString('_ancestorBindingInfo');
|
3220
|
+
var contextDataDependency = ko.utils.createSymbolOrString('_dataDependency');
|
3221
|
+
|
2942
3222
|
ko.bindingHandlers = {};
|
2943
3223
|
|
2944
3224
|
// The following element types will not be recursed into during binding.
|
@@ -2953,14 +3233,16 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2953
3233
|
'template': true
|
2954
3234
|
};
|
2955
3235
|
|
2956
|
-
// Use an overridable method for retrieving binding handlers so that
|
3236
|
+
// Use an overridable method for retrieving binding handlers so that plugins may support dynamically created handlers
|
2957
3237
|
ko['getBindingHandler'] = function(bindingKey) {
|
2958
3238
|
return ko.bindingHandlers[bindingKey];
|
2959
3239
|
};
|
2960
3240
|
|
3241
|
+
var inheritParentVm = {};
|
3242
|
+
|
2961
3243
|
// The ko.bindingContext constructor is only called directly to create the root context. For child
|
2962
3244
|
// contexts, use bindingContext.createChildContext or bindingContext.extend.
|
2963
|
-
ko.bindingContext = function(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback) {
|
3245
|
+
ko.bindingContext = function(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback, options) {
|
2964
3246
|
|
2965
3247
|
// The binding context object includes static properties for the current, parent, and root view models.
|
2966
3248
|
// If a view model is actually stored in an observable, the corresponding binding context object, and
|
@@ -2970,22 +3252,16 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2970
3252
|
// we call the function to retrieve the view model. If the function accesses any observables or returns
|
2971
3253
|
// an observable, the dependency is tracked, and those observables can later cause the binding
|
2972
3254
|
// context to be updated.
|
2973
|
-
var dataItemOrObservable = isFunc ?
|
3255
|
+
var dataItemOrObservable = isFunc ? realDataItemOrAccessor() : realDataItemOrAccessor,
|
2974
3256
|
dataItem = ko.utils.unwrapObservable(dataItemOrObservable);
|
2975
3257
|
|
2976
3258
|
if (parentContext) {
|
2977
|
-
// When a "parent" context is given, register a dependency on the parent context. Thus whenever the
|
2978
|
-
// parent context is updated, this context will also be updated.
|
2979
|
-
if (parentContext._subscribable)
|
2980
|
-
parentContext._subscribable();
|
2981
|
-
|
2982
3259
|
// Copy $root and any custom properties from the parent context
|
2983
3260
|
ko.utils.extend(self, parentContext);
|
2984
3261
|
|
2985
|
-
//
|
2986
|
-
|
2987
|
-
|
2988
|
-
self._subscribable = subscribable;
|
3262
|
+
// Copy Symbol properties
|
3263
|
+
if (contextAncestorBindingInfo in parentContext) {
|
3264
|
+
self[contextAncestorBindingInfo] = parentContext[contextAncestorBindingInfo];
|
2989
3265
|
}
|
2990
3266
|
} else {
|
2991
3267
|
self['$parents'] = [];
|
@@ -2996,8 +3272,16 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2996
3272
|
// See https://github.com/SteveSanderson/knockout/issues/490
|
2997
3273
|
self['ko'] = ko;
|
2998
3274
|
}
|
2999
|
-
|
3000
|
-
self[
|
3275
|
+
|
3276
|
+
self[contextSubscribable] = subscribable;
|
3277
|
+
|
3278
|
+
if (shouldInheritData) {
|
3279
|
+
dataItem = self['$data'];
|
3280
|
+
} else {
|
3281
|
+
self['$rawData'] = dataItemOrObservable;
|
3282
|
+
self['$data'] = dataItem;
|
3283
|
+
}
|
3284
|
+
|
3001
3285
|
if (dataItemAlias)
|
3002
3286
|
self[dataItemAlias] = dataItem;
|
3003
3287
|
|
@@ -3007,44 +3291,45 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3007
3291
|
if (extendCallback)
|
3008
3292
|
extendCallback(self, parentContext, dataItem);
|
3009
3293
|
|
3294
|
+
// When a "parent" context is given and we don't already have a dependency on its context, register a dependency on it.
|
3295
|
+
// Thus whenever the parent context is updated, this context will also be updated.
|
3296
|
+
if (parentContext && parentContext[contextSubscribable] && !ko.computedContext.computed().hasAncestorDependency(parentContext[contextSubscribable])) {
|
3297
|
+
parentContext[contextSubscribable]();
|
3298
|
+
}
|
3299
|
+
|
3300
|
+
if (dataDependency) {
|
3301
|
+
self[contextDataDependency] = dataDependency;
|
3302
|
+
}
|
3303
|
+
|
3010
3304
|
return self['$data'];
|
3011
3305
|
}
|
3012
|
-
function disposeWhen() {
|
3013
|
-
return nodes && !ko.utils.anyDomNodeIsAttachedToDocument(nodes);
|
3014
|
-
}
|
3015
3306
|
|
3016
3307
|
var self = this,
|
3017
|
-
|
3308
|
+
shouldInheritData = dataItemOrAccessor === inheritParentVm,
|
3309
|
+
realDataItemOrAccessor = shouldInheritData ? undefined : dataItemOrAccessor,
|
3310
|
+
isFunc = typeof(realDataItemOrAccessor) == "function" && !ko.isObservable(realDataItemOrAccessor),
|
3018
3311
|
nodes,
|
3019
|
-
subscribable
|
3020
|
-
|
3021
|
-
|
3022
|
-
|
3023
|
-
|
3024
|
-
|
3025
|
-
|
3026
|
-
|
3027
|
-
|
3028
|
-
|
3029
|
-
|
3030
|
-
|
3031
|
-
//
|
3032
|
-
//
|
3033
|
-
//
|
3034
|
-
|
3035
|
-
|
3036
|
-
|
3037
|
-
|
3038
|
-
|
3039
|
-
|
3040
|
-
ko.utils.domNodeDisposal.addDisposeCallback(node, function(node) {
|
3041
|
-
ko.utils.arrayRemoveItem(nodes, node);
|
3042
|
-
if (!nodes.length) {
|
3043
|
-
subscribable.dispose();
|
3044
|
-
self._subscribable = subscribable = undefined;
|
3045
|
-
}
|
3046
|
-
});
|
3047
|
-
};
|
3312
|
+
subscribable,
|
3313
|
+
dataDependency = options && options['dataDependency'];
|
3314
|
+
|
3315
|
+
if (options && options['exportDependencies']) {
|
3316
|
+
// The "exportDependencies" option means that the calling code will track any dependencies and re-create
|
3317
|
+
// the binding context when they change.
|
3318
|
+
updateContext();
|
3319
|
+
} else {
|
3320
|
+
subscribable = ko.pureComputed(updateContext);
|
3321
|
+
subscribable.peek();
|
3322
|
+
|
3323
|
+
// At this point, the binding context has been initialized, and the "subscribable" computed observable is
|
3324
|
+
// subscribed to any observables that were accessed in the process. If there is nothing to track, the
|
3325
|
+
// computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in
|
3326
|
+
// the context object.
|
3327
|
+
if (subscribable.isActive()) {
|
3328
|
+
// Always notify because even if the model ($data) hasn't changed, other context properties might have changed
|
3329
|
+
subscribable['equalityComparer'] = null;
|
3330
|
+
} else {
|
3331
|
+
self[contextSubscribable] = undefined;
|
3332
|
+
}
|
3048
3333
|
}
|
3049
3334
|
}
|
3050
3335
|
|
@@ -3053,8 +3338,23 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3053
3338
|
// But this does not mean that the $data value of the child context will also get updated. If the child
|
3054
3339
|
// view model also depends on the parent view model, you must provide a function that returns the correct
|
3055
3340
|
// view model on each update.
|
3056
|
-
ko.bindingContext.prototype['createChildContext'] = function (dataItemOrAccessor, dataItemAlias, extendCallback) {
|
3057
|
-
|
3341
|
+
ko.bindingContext.prototype['createChildContext'] = function (dataItemOrAccessor, dataItemAlias, extendCallback, options) {
|
3342
|
+
if (!options && dataItemAlias && typeof dataItemAlias == "object") {
|
3343
|
+
options = dataItemAlias;
|
3344
|
+
dataItemAlias = options['as'];
|
3345
|
+
extendCallback = options['extend'];
|
3346
|
+
}
|
3347
|
+
|
3348
|
+
if (dataItemAlias && options && options['noChildContext']) {
|
3349
|
+
var isFunc = typeof(dataItemOrAccessor) == "function" && !ko.isObservable(dataItemOrAccessor);
|
3350
|
+
return new ko.bindingContext(inheritParentVm, this, null, function (self) {
|
3351
|
+
if (extendCallback)
|
3352
|
+
extendCallback(self);
|
3353
|
+
self[dataItemAlias] = isFunc ? dataItemOrAccessor() : dataItemOrAccessor;
|
3354
|
+
}, options);
|
3355
|
+
}
|
3356
|
+
|
3357
|
+
return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, function (self, parentContext) {
|
3058
3358
|
// Extend the context hierarchy by setting the appropriate pointers
|
3059
3359
|
self['$parentContext'] = parentContext;
|
3060
3360
|
self['$parent'] = parentContext['$data'];
|
@@ -3062,24 +3362,113 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3062
3362
|
self['$parents'].unshift(self['$parent']);
|
3063
3363
|
if (extendCallback)
|
3064
3364
|
extendCallback(self);
|
3065
|
-
});
|
3365
|
+
}, options);
|
3066
3366
|
};
|
3067
3367
|
|
3068
3368
|
// Extend the binding context with new custom properties. This doesn't change the context hierarchy.
|
3069
3369
|
// Similarly to "child" contexts, provide a function here to make sure that the correct values are set
|
3070
3370
|
// when an observable view model is updated.
|
3071
|
-
ko.bindingContext.prototype['extend'] = function(properties) {
|
3072
|
-
|
3073
|
-
|
3074
|
-
|
3075
|
-
|
3076
|
-
|
3077
|
-
|
3078
|
-
|
3079
|
-
|
3371
|
+
ko.bindingContext.prototype['extend'] = function(properties, options) {
|
3372
|
+
return new ko.bindingContext(inheritParentVm, this, null, function(self, parentContext) {
|
3373
|
+
ko.utils.extend(self, typeof(properties) == "function" ? properties(self) : properties);
|
3374
|
+
}, options);
|
3375
|
+
};
|
3376
|
+
|
3377
|
+
var boundElementDomDataKey = ko.utils.domData.nextKey();
|
3378
|
+
|
3379
|
+
function asyncContextDispose(node) {
|
3380
|
+
var bindingInfo = ko.utils.domData.get(node, boundElementDomDataKey),
|
3381
|
+
asyncContext = bindingInfo && bindingInfo.asyncContext;
|
3382
|
+
if (asyncContext) {
|
3383
|
+
bindingInfo.asyncContext = null;
|
3384
|
+
asyncContext.notifyAncestor();
|
3385
|
+
}
|
3386
|
+
}
|
3387
|
+
function AsyncCompleteContext(node, bindingInfo, ancestorBindingInfo) {
|
3388
|
+
this.node = node;
|
3389
|
+
this.bindingInfo = bindingInfo;
|
3390
|
+
this.asyncDescendants = [];
|
3391
|
+
this.childrenComplete = false;
|
3392
|
+
|
3393
|
+
if (!bindingInfo.asyncContext) {
|
3394
|
+
ko.utils.domNodeDisposal.addDisposeCallback(node, asyncContextDispose);
|
3395
|
+
}
|
3396
|
+
|
3397
|
+
if (ancestorBindingInfo && ancestorBindingInfo.asyncContext) {
|
3398
|
+
ancestorBindingInfo.asyncContext.asyncDescendants.push(node);
|
3399
|
+
this.ancestorBindingInfo = ancestorBindingInfo;
|
3400
|
+
}
|
3401
|
+
}
|
3402
|
+
AsyncCompleteContext.prototype.notifyAncestor = function () {
|
3403
|
+
if (this.ancestorBindingInfo && this.ancestorBindingInfo.asyncContext) {
|
3404
|
+
this.ancestorBindingInfo.asyncContext.descendantComplete(this.node);
|
3405
|
+
}
|
3406
|
+
};
|
3407
|
+
AsyncCompleteContext.prototype.descendantComplete = function (node) {
|
3408
|
+
ko.utils.arrayRemoveItem(this.asyncDescendants, node);
|
3409
|
+
if (!this.asyncDescendants.length && this.childrenComplete) {
|
3410
|
+
this.completeChildren();
|
3411
|
+
}
|
3412
|
+
};
|
3413
|
+
AsyncCompleteContext.prototype.completeChildren = function () {
|
3414
|
+
this.childrenComplete = true;
|
3415
|
+
if (this.bindingInfo.asyncContext && !this.asyncDescendants.length) {
|
3416
|
+
this.bindingInfo.asyncContext = null;
|
3417
|
+
ko.utils.domNodeDisposal.removeDisposeCallback(this.node, asyncContextDispose);
|
3418
|
+
ko.bindingEvent.notify(this.node, ko.bindingEvent.descendantsComplete);
|
3419
|
+
this.notifyAncestor();
|
3420
|
+
}
|
3421
|
+
};
|
3422
|
+
|
3423
|
+
ko.bindingEvent = {
|
3424
|
+
childrenComplete: "childrenComplete",
|
3425
|
+
descendantsComplete : "descendantsComplete",
|
3426
|
+
|
3427
|
+
subscribe: function (node, event, callback, context) {
|
3428
|
+
var bindingInfo = ko.utils.domData.getOrSet(node, boundElementDomDataKey, {});
|
3429
|
+
if (!bindingInfo.eventSubscribable) {
|
3430
|
+
bindingInfo.eventSubscribable = new ko.subscribable;
|
3431
|
+
}
|
3432
|
+
return bindingInfo.eventSubscribable.subscribe(callback, context, event);
|
3433
|
+
},
|
3434
|
+
|
3435
|
+
notify: function (node, event) {
|
3436
|
+
var bindingInfo = ko.utils.domData.get(node, boundElementDomDataKey);
|
3437
|
+
if (bindingInfo) {
|
3438
|
+
if (bindingInfo.eventSubscribable) {
|
3439
|
+
bindingInfo.eventSubscribable['notifySubscribers'](node, event);
|
3440
|
+
}
|
3441
|
+
if (event == ko.bindingEvent.childrenComplete) {
|
3442
|
+
if (bindingInfo.asyncContext) {
|
3443
|
+
bindingInfo.asyncContext.completeChildren();
|
3444
|
+
} else if (bindingInfo.asyncContext === undefined && bindingInfo.eventSubscribable && bindingInfo.eventSubscribable.hasSubscriptionsForEvent(ko.bindingEvent.descendantsComplete)) {
|
3445
|
+
// It's currently an error to register a descendantsComplete handler for a node that was never registered as completing asynchronously.
|
3446
|
+
// That's because without the asyncContext, we don't have a way to know that all descendants have completed.
|
3447
|
+
throw new Error("descendantsComplete event not supported for bindings on this node");
|
3448
|
+
}
|
3449
|
+
}
|
3450
|
+
}
|
3451
|
+
},
|
3452
|
+
|
3453
|
+
startPossiblyAsyncContentBinding: function (node, bindingContext) {
|
3454
|
+
var bindingInfo = ko.utils.domData.getOrSet(node, boundElementDomDataKey, {});
|
3455
|
+
|
3456
|
+
if (!bindingInfo.asyncContext) {
|
3457
|
+
bindingInfo.asyncContext = new AsyncCompleteContext(node, bindingInfo, bindingContext[contextAncestorBindingInfo]);
|
3458
|
+
}
|
3459
|
+
|
3460
|
+
// If the provided context was already extended with this node's binding info, just return the extended context
|
3461
|
+
if (bindingContext[contextAncestorBindingInfo] == bindingInfo) {
|
3462
|
+
return bindingContext;
|
3463
|
+
}
|
3464
|
+
|
3465
|
+
return bindingContext['extend'](function (ctx) {
|
3466
|
+
ctx[contextAncestorBindingInfo] = bindingInfo;
|
3467
|
+
});
|
3468
|
+
}
|
3080
3469
|
};
|
3081
3470
|
|
3082
|
-
// Returns the
|
3471
|
+
// Returns the valueAccessor function for a binding value
|
3083
3472
|
function makeValueAccessor(value) {
|
3084
3473
|
return function() {
|
3085
3474
|
return value;
|
@@ -3125,62 +3514,55 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3125
3514
|
throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
|
3126
3515
|
}
|
3127
3516
|
|
3128
|
-
function applyBindingsToDescendantsInternal
|
3129
|
-
var
|
3130
|
-
|
3131
|
-
|
3132
|
-
|
3517
|
+
function applyBindingsToDescendantsInternal(bindingContext, elementOrVirtualElement) {
|
3518
|
+
var nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
|
3519
|
+
|
3520
|
+
if (nextInQueue) {
|
3521
|
+
var currentChild,
|
3522
|
+
provider = ko.bindingProvider['instance'],
|
3523
|
+
preprocessNode = provider['preprocessNode'];
|
3524
|
+
|
3525
|
+
// Preprocessing allows a binding provider to mutate a node before bindings are applied to it. For example it's
|
3526
|
+
// possible to insert new siblings after it, and/or replace the node with a different one. This can be used to
|
3527
|
+
// implement custom binding syntaxes, such as {{ value }} for string interpolation, or custom element types that
|
3528
|
+
// trigger insertion of <template> contents at that point in the document.
|
3529
|
+
if (preprocessNode) {
|
3530
|
+
while (currentChild = nextInQueue) {
|
3531
|
+
nextInQueue = ko.virtualElements.nextSibling(currentChild);
|
3532
|
+
preprocessNode.call(provider, currentChild);
|
3533
|
+
}
|
3534
|
+
// Reset nextInQueue for the next loop
|
3535
|
+
nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
|
3536
|
+
}
|
3133
3537
|
|
3134
|
-
// Preprocessing allows a binding provider to mutate a node before bindings are applied to it. For example it's
|
3135
|
-
// possible to insert new siblings after it, and/or replace the node with a different one. This can be used to
|
3136
|
-
// implement custom binding syntaxes, such as {{ value }} for string interpolation, or custom element types that
|
3137
|
-
// trigger insertion of <template> contents at that point in the document.
|
3138
|
-
if (preprocessNode) {
|
3139
3538
|
while (currentChild = nextInQueue) {
|
3539
|
+
// Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
|
3140
3540
|
nextInQueue = ko.virtualElements.nextSibling(currentChild);
|
3141
|
-
|
3541
|
+
applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild);
|
3142
3542
|
}
|
3143
|
-
// Reset nextInQueue for the next loop
|
3144
|
-
nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
|
3145
|
-
}
|
3146
|
-
|
3147
|
-
while (currentChild = nextInQueue) {
|
3148
|
-
// Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
|
3149
|
-
nextInQueue = ko.virtualElements.nextSibling(currentChild);
|
3150
|
-
applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild, bindingContextsMayDifferFromDomParentElement);
|
3151
3543
|
}
|
3544
|
+
ko.bindingEvent.notify(elementOrVirtualElement, ko.bindingEvent.childrenComplete);
|
3152
3545
|
}
|
3153
3546
|
|
3154
|
-
function applyBindingsToNodeAndDescendantsInternal
|
3155
|
-
var
|
3547
|
+
function applyBindingsToNodeAndDescendantsInternal(bindingContext, nodeVerified) {
|
3548
|
+
var bindingContextForDescendants = bindingContext;
|
3156
3549
|
|
3157
|
-
// Perf optimisation: Apply bindings only if...
|
3158
|
-
// (1) We need to store the binding context on this node (because it may differ from the DOM parent node's binding context)
|
3159
|
-
// Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those
|
3160
|
-
// (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
|
3161
3550
|
var isElement = (nodeVerified.nodeType === 1);
|
3162
3551
|
if (isElement) // Workaround IE <= 8 HTML parsing weirdness
|
3163
3552
|
ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
|
3164
3553
|
|
3165
|
-
|
3166
|
-
|
3554
|
+
// Perf optimisation: Apply bindings only if...
|
3555
|
+
// (1) We need to store the binding info for the node (all element nodes)
|
3556
|
+
// (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
|
3557
|
+
var shouldApplyBindings = isElement || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified);
|
3167
3558
|
if (shouldApplyBindings)
|
3168
|
-
|
3559
|
+
bindingContextForDescendants = applyBindingsToNodeInternal(nodeVerified, null, bindingContext)['bindingContextForDescendants'];
|
3169
3560
|
|
3170
|
-
if (
|
3171
|
-
|
3172
|
-
// * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
|
3173
|
-
// hence bindingContextsMayDifferFromDomParentElement is false
|
3174
|
-
// * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
|
3175
|
-
// skip over any number of intermediate virtual elements, any of which might define a custom binding context,
|
3176
|
-
// hence bindingContextsMayDifferFromDomParentElement is true
|
3177
|
-
applyBindingsToDescendantsInternal(bindingContext, nodeVerified, /* bindingContextsMayDifferFromDomParentElement: */ !isElement);
|
3561
|
+
if (bindingContextForDescendants && !bindingDoesNotRecurseIntoElementTypes[ko.utils.tagNameLower(nodeVerified)]) {
|
3562
|
+
applyBindingsToDescendantsInternal(bindingContextForDescendants, nodeVerified);
|
3178
3563
|
}
|
3179
3564
|
}
|
3180
3565
|
|
3181
|
-
var boundElementDomDataKey = ko.utils.domData.nextKey();
|
3182
|
-
|
3183
|
-
|
3184
3566
|
function topologicalSortBindings(bindings) {
|
3185
3567
|
// Depth-first sort
|
3186
3568
|
var result = [], // The list of key/handler pairs that we will return
|
@@ -3214,21 +3596,20 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3214
3596
|
return result;
|
3215
3597
|
}
|
3216
3598
|
|
3217
|
-
function applyBindingsToNodeInternal(node, sourceBindings, bindingContext
|
3599
|
+
function applyBindingsToNodeInternal(node, sourceBindings, bindingContext) {
|
3600
|
+
var bindingInfo = ko.utils.domData.getOrSet(node, boundElementDomDataKey, {});
|
3601
|
+
|
3218
3602
|
// Prevent multiple applyBindings calls for the same node, except when a binding value is specified
|
3219
|
-
var alreadyBound =
|
3603
|
+
var alreadyBound = bindingInfo.alreadyBound;
|
3220
3604
|
if (!sourceBindings) {
|
3221
3605
|
if (alreadyBound) {
|
3222
3606
|
throw Error("You cannot apply bindings multiple times to the same element.");
|
3223
3607
|
}
|
3224
|
-
|
3608
|
+
bindingInfo.alreadyBound = true;
|
3609
|
+
}
|
3610
|
+
if (!alreadyBound) {
|
3611
|
+
bindingInfo.context = bindingContext;
|
3225
3612
|
}
|
3226
|
-
|
3227
|
-
// Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
|
3228
|
-
// we can easily recover it just by scanning up the node's ancestors in the DOM
|
3229
|
-
// (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
|
3230
|
-
if (!alreadyBound && bindingContextMayDifferFromDomParentElement)
|
3231
|
-
ko.storedBindingContextForNode(node, bindingContext);
|
3232
3613
|
|
3233
3614
|
// Use bindings if given, otherwise fall back on asking the bindings provider to give us some bindings
|
3234
3615
|
var bindings;
|
@@ -3244,8 +3625,14 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3244
3625
|
function() {
|
3245
3626
|
bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext);
|
3246
3627
|
// Register a dependency on the binding context to support observable view models.
|
3247
|
-
if (bindings
|
3248
|
-
bindingContext
|
3628
|
+
if (bindings) {
|
3629
|
+
if (bindingContext[contextSubscribable]) {
|
3630
|
+
bindingContext[contextSubscribable]();
|
3631
|
+
}
|
3632
|
+
if (bindingContext[contextDataDependency]) {
|
3633
|
+
bindingContext[contextDataDependency]();
|
3634
|
+
}
|
3635
|
+
}
|
3249
3636
|
return bindings;
|
3250
3637
|
},
|
3251
3638
|
null, { disposeWhenNodeIsRemoved: node }
|
@@ -3255,6 +3642,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3255
3642
|
bindingsUpdater = null;
|
3256
3643
|
}
|
3257
3644
|
|
3645
|
+
var contextToExtend = bindingContext;
|
3258
3646
|
var bindingHandlerThatControlsDescendantBindings;
|
3259
3647
|
if (bindings) {
|
3260
3648
|
// Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding
|
@@ -3281,6 +3669,28 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3281
3669
|
return key in bindings;
|
3282
3670
|
};
|
3283
3671
|
|
3672
|
+
if (ko.bindingEvent.childrenComplete in bindings) {
|
3673
|
+
ko.bindingEvent.subscribe(node, ko.bindingEvent.childrenComplete, function () {
|
3674
|
+
var callback = evaluateValueAccessor(bindings[ko.bindingEvent.childrenComplete]);
|
3675
|
+
if (callback) {
|
3676
|
+
var nodes = ko.virtualElements.childNodes(node);
|
3677
|
+
if (nodes.length) {
|
3678
|
+
callback(nodes, ko.dataFor(nodes[0]));
|
3679
|
+
}
|
3680
|
+
}
|
3681
|
+
});
|
3682
|
+
}
|
3683
|
+
|
3684
|
+
if (ko.bindingEvent.descendantsComplete in bindings) {
|
3685
|
+
contextToExtend = ko.bindingEvent.startPossiblyAsyncContentBinding(node, bindingContext);
|
3686
|
+
ko.bindingEvent.subscribe(node, ko.bindingEvent.descendantsComplete, function () {
|
3687
|
+
var callback = evaluateValueAccessor(bindings[ko.bindingEvent.descendantsComplete]);
|
3688
|
+
if (callback && ko.virtualElements.firstChild(node)) {
|
3689
|
+
callback(node);
|
3690
|
+
}
|
3691
|
+
});
|
3692
|
+
}
|
3693
|
+
|
3284
3694
|
// First put the bindings into the right order
|
3285
3695
|
var orderedBindings = topologicalSortBindings(bindings);
|
3286
3696
|
|
@@ -3300,7 +3710,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3300
3710
|
// Run init, ignoring any dependencies
|
3301
3711
|
if (typeof handlerInitFn == "function") {
|
3302
3712
|
ko.dependencyDetection.ignore(function() {
|
3303
|
-
var initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings,
|
3713
|
+
var initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend['$data'], contextToExtend);
|
3304
3714
|
|
3305
3715
|
// If this binding handler claims to control descendant bindings, make a note of this
|
3306
3716
|
if (initResult && initResult['controlsDescendantBindings']) {
|
@@ -3315,7 +3725,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3315
3725
|
if (typeof handlerUpdateFn == "function") {
|
3316
3726
|
ko.dependentObservable(
|
3317
3727
|
function() {
|
3318
|
-
handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings,
|
3728
|
+
handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend['$data'], contextToExtend);
|
3319
3729
|
},
|
3320
3730
|
null,
|
3321
3731
|
{ disposeWhenNodeIsRemoved: node }
|
@@ -3328,32 +3738,28 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3328
3738
|
});
|
3329
3739
|
}
|
3330
3740
|
|
3741
|
+
var shouldBindDescendants = bindingHandlerThatControlsDescendantBindings === undefined;
|
3331
3742
|
return {
|
3332
|
-
'shouldBindDescendants':
|
3743
|
+
'shouldBindDescendants': shouldBindDescendants,
|
3744
|
+
'bindingContextForDescendants': shouldBindDescendants && contextToExtend
|
3333
3745
|
};
|
3334
3746
|
};
|
3335
3747
|
|
3336
|
-
|
3337
|
-
|
3338
|
-
|
3339
|
-
ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
|
3340
|
-
if (bindingContext._subscribable)
|
3341
|
-
bindingContext._subscribable._addNode(node);
|
3342
|
-
} else {
|
3343
|
-
return ko.utils.domData.get(node, storedBindingContextDomDataKey);
|
3344
|
-
}
|
3748
|
+
ko.storedBindingContextForNode = function (node) {
|
3749
|
+
var bindingInfo = ko.utils.domData.get(node, boundElementDomDataKey);
|
3750
|
+
return bindingInfo && bindingInfo.context;
|
3345
3751
|
}
|
3346
3752
|
|
3347
|
-
function getBindingContext(viewModelOrBindingContext) {
|
3753
|
+
function getBindingContext(viewModelOrBindingContext, extendContextCallback) {
|
3348
3754
|
return viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
|
3349
3755
|
? viewModelOrBindingContext
|
3350
|
-
: new ko.bindingContext(viewModelOrBindingContext);
|
3756
|
+
: new ko.bindingContext(viewModelOrBindingContext, undefined, undefined, extendContextCallback);
|
3351
3757
|
}
|
3352
3758
|
|
3353
3759
|
ko.applyBindingAccessorsToNode = function (node, bindings, viewModelOrBindingContext) {
|
3354
3760
|
if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
|
3355
3761
|
ko.virtualElements.normaliseVirtualElementDomStructure(node);
|
3356
|
-
return applyBindingsToNodeInternal(node, bindings, getBindingContext(viewModelOrBindingContext)
|
3762
|
+
return applyBindingsToNodeInternal(node, bindings, getBindingContext(viewModelOrBindingContext));
|
3357
3763
|
};
|
3358
3764
|
|
3359
3765
|
ko.applyBindingsToNode = function (node, bindings, viewModelOrBindingContext) {
|
@@ -3363,32 +3769,32 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3363
3769
|
|
3364
3770
|
ko.applyBindingsToDescendants = function(viewModelOrBindingContext, rootNode) {
|
3365
3771
|
if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
|
3366
|
-
applyBindingsToDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode
|
3772
|
+
applyBindingsToDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode);
|
3367
3773
|
};
|
3368
3774
|
|
3369
|
-
ko.applyBindings = function (viewModelOrBindingContext, rootNode) {
|
3775
|
+
ko.applyBindings = function (viewModelOrBindingContext, rootNode, extendContextCallback) {
|
3370
3776
|
// If jQuery is loaded after Knockout, we won't initially have access to it. So save it here.
|
3371
3777
|
if (!jQueryInstance && window['jQuery']) {
|
3372
3778
|
jQueryInstance = window['jQuery'];
|
3373
3779
|
}
|
3374
3780
|
|
3375
|
-
if (
|
3376
|
-
|
3377
|
-
|
3781
|
+
if (arguments.length < 2) {
|
3782
|
+
rootNode = document.body;
|
3783
|
+
if (!rootNode) {
|
3784
|
+
throw Error("ko.applyBindings: could not find document.body; has the document been loaded?");
|
3785
|
+
}
|
3786
|
+
} else if (!rootNode || (rootNode.nodeType !== 1 && rootNode.nodeType !== 8)) {
|
3787
|
+
throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
|
3788
|
+
}
|
3378
3789
|
|
3379
|
-
applyBindingsToNodeAndDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode
|
3790
|
+
applyBindingsToNodeAndDescendantsInternal(getBindingContext(viewModelOrBindingContext, extendContextCallback), rootNode);
|
3380
3791
|
};
|
3381
3792
|
|
3382
3793
|
// Retrieving binding context from arbitrary nodes
|
3383
3794
|
ko.contextFor = function(node) {
|
3384
3795
|
// We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
|
3385
|
-
|
3386
|
-
|
3387
|
-
case 8:
|
3388
|
-
var context = ko.storedBindingContextForNode(node);
|
3389
|
-
if (context) return context;
|
3390
|
-
if (node.parentNode) return ko.contextFor(node.parentNode);
|
3391
|
-
break;
|
3796
|
+
if (node && (node.nodeType === 1 || node.nodeType === 8)) {
|
3797
|
+
return ko.storedBindingContextForNode(node);
|
3392
3798
|
}
|
3393
3799
|
return undefined;
|
3394
3800
|
};
|
@@ -3398,6 +3804,9 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3398
3804
|
};
|
3399
3805
|
|
3400
3806
|
ko.exportSymbol('bindingHandlers', ko.bindingHandlers);
|
3807
|
+
ko.exportSymbol('bindingEvent', ko.bindingEvent);
|
3808
|
+
ko.exportSymbol('bindingEvent.subscribe', ko.bindingEvent.subscribe);
|
3809
|
+
ko.exportSymbol('bindingEvent.startPossiblyAsyncContentBinding', ko.bindingEvent.startPossiblyAsyncContentBinding);
|
3401
3810
|
ko.exportSymbol('applyBindings', ko.applyBindings);
|
3402
3811
|
ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants);
|
3403
3812
|
ko.exportSymbol('applyBindingAccessorsToNode', ko.applyBindingAccessorsToNode);
|
@@ -3437,7 +3846,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3437
3846
|
};
|
3438
3847
|
|
3439
3848
|
function getObjectOwnProperty(obj, propName) {
|
3440
|
-
return
|
3849
|
+
return Object.prototype.hasOwnProperty.call(obj, propName) ? obj[propName] : undefined;
|
3441
3850
|
}
|
3442
3851
|
|
3443
3852
|
function loadComponentAndNotify(componentName, callback) {
|
@@ -3574,7 +3983,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3574
3983
|
};
|
3575
3984
|
|
3576
3985
|
ko.components.isRegistered = function(componentName) {
|
3577
|
-
return
|
3986
|
+
return Object.prototype.hasOwnProperty.call(defaultConfigRegistry, componentName);
|
3578
3987
|
};
|
3579
3988
|
|
3580
3989
|
ko.components.unregister = function(componentName) {
|
@@ -3584,7 +3993,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3584
3993
|
|
3585
3994
|
ko.components.defaultLoader = {
|
3586
3995
|
'getConfig': function(componentName, callback) {
|
3587
|
-
var result =
|
3996
|
+
var result = ko.components.isRegistered(componentName)
|
3588
3997
|
? defaultConfigRegistry[componentName]
|
3589
3998
|
: null;
|
3590
3999
|
callback(result);
|
@@ -3847,7 +4256,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3847
4256
|
// Give access to the raw computeds, as long as that wouldn't overwrite any custom param also called '$raw'
|
3848
4257
|
// This is in case the developer wants to react to outer (binding) observability separately from inner
|
3849
4258
|
// (model value) observability, or in case the model value observable has subobservables.
|
3850
|
-
if (!
|
4259
|
+
if (!Object.prototype.hasOwnProperty.call(result, '$raw')) {
|
3851
4260
|
result['$raw'] = rawParamComputedValues;
|
3852
4261
|
}
|
3853
4262
|
|
@@ -3879,7 +4288,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3879
4288
|
var newDocFrag = originalFunction(),
|
3880
4289
|
allComponents = ko.components._allRegisteredComponents;
|
3881
4290
|
for (var componentName in allComponents) {
|
3882
|
-
if (
|
4291
|
+
if (Object.prototype.hasOwnProperty.call(allComponents, componentName)) {
|
3883
4292
|
newDocFrag.createElement(componentName);
|
3884
4293
|
}
|
3885
4294
|
}
|
@@ -3888,24 +4297,29 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3888
4297
|
})(document.createDocumentFragment);
|
3889
4298
|
}
|
3890
4299
|
})();(function(undefined) {
|
3891
|
-
|
3892
4300
|
var componentLoadingOperationUniqueId = 0;
|
3893
4301
|
|
3894
4302
|
ko.bindingHandlers['component'] = {
|
3895
4303
|
'init': function(element, valueAccessor, ignored1, ignored2, bindingContext) {
|
3896
4304
|
var currentViewModel,
|
3897
4305
|
currentLoadingOperationId,
|
4306
|
+
afterRenderSub,
|
3898
4307
|
disposeAssociatedComponentViewModel = function () {
|
3899
4308
|
var currentViewModelDispose = currentViewModel && currentViewModel['dispose'];
|
3900
4309
|
if (typeof currentViewModelDispose === 'function') {
|
3901
4310
|
currentViewModelDispose.call(currentViewModel);
|
3902
4311
|
}
|
4312
|
+
if (afterRenderSub) {
|
4313
|
+
afterRenderSub.dispose();
|
4314
|
+
}
|
4315
|
+
afterRenderSub = null;
|
3903
4316
|
currentViewModel = null;
|
3904
4317
|
// Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
|
3905
4318
|
currentLoadingOperationId = null;
|
3906
4319
|
},
|
3907
4320
|
originalChildNodes = ko.utils.makeArray(ko.virtualElements.childNodes(element));
|
3908
4321
|
|
4322
|
+
ko.virtualElements.emptyNode(element);
|
3909
4323
|
ko.utils.domNodeDisposal.addDisposeCallback(element, disposeAssociatedComponentViewModel);
|
3910
4324
|
|
3911
4325
|
ko.computed(function () {
|
@@ -3923,6 +4337,8 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3923
4337
|
throw new Error('No component name specified');
|
3924
4338
|
}
|
3925
4339
|
|
4340
|
+
var asyncContext = ko.bindingEvent.startPossiblyAsyncContentBinding(element, bindingContext);
|
4341
|
+
|
3926
4342
|
var loadingOperationId = currentLoadingOperationId = ++componentLoadingOperationUniqueId;
|
3927
4343
|
ko.components.get(componentName, function(componentDefinition) {
|
3928
4344
|
// If this is not the current load operation for this element, ignore it.
|
@@ -3938,11 +4354,24 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3938
4354
|
throw new Error('Unknown component \'' + componentName + '\'');
|
3939
4355
|
}
|
3940
4356
|
cloneTemplateIntoElement(componentName, componentDefinition, element);
|
3941
|
-
|
3942
|
-
|
3943
|
-
|
3944
|
-
|
4357
|
+
|
4358
|
+
var componentInfo = {
|
4359
|
+
'element': element,
|
4360
|
+
'templateNodes': originalChildNodes
|
4361
|
+
};
|
4362
|
+
|
4363
|
+
var componentViewModel = createViewModel(componentDefinition, componentParams, componentInfo),
|
4364
|
+
childBindingContext = asyncContext['createChildContext'](componentViewModel, {
|
4365
|
+
'extend': function(ctx) {
|
4366
|
+
ctx['$component'] = componentViewModel;
|
4367
|
+
ctx['$componentTemplateNodes'] = originalChildNodes;
|
4368
|
+
}
|
3945
4369
|
});
|
4370
|
+
|
4371
|
+
if (componentViewModel && componentViewModel['koDescendantsComplete']) {
|
4372
|
+
afterRenderSub = ko.bindingEvent.subscribe(element, ko.bindingEvent.descendantsComplete, componentViewModel['koDescendantsComplete'], componentViewModel);
|
4373
|
+
}
|
4374
|
+
|
3946
4375
|
currentViewModel = componentViewModel;
|
3947
4376
|
ko.applyBindingsToDescendants(childBindingContext, element);
|
3948
4377
|
});
|
@@ -3964,40 +4393,47 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
3964
4393
|
ko.virtualElements.setDomNodeChildren(element, clonedNodesArray);
|
3965
4394
|
}
|
3966
4395
|
|
3967
|
-
function createViewModel(componentDefinition,
|
4396
|
+
function createViewModel(componentDefinition, componentParams, componentInfo) {
|
3968
4397
|
var componentViewModelFactory = componentDefinition['createViewModel'];
|
3969
4398
|
return componentViewModelFactory
|
3970
|
-
? componentViewModelFactory.call(componentDefinition, componentParams,
|
4399
|
+
? componentViewModelFactory.call(componentDefinition, componentParams, componentInfo)
|
3971
4400
|
: componentParams; // Template-only component
|
3972
4401
|
}
|
3973
4402
|
|
3974
4403
|
})();
|
3975
|
-
var
|
4404
|
+
var attrHtmlToJavaScriptMap = { 'class': 'className', 'for': 'htmlFor' };
|
3976
4405
|
ko.bindingHandlers['attr'] = {
|
3977
4406
|
'update': function(element, valueAccessor, allBindings) {
|
3978
4407
|
var value = ko.utils.unwrapObservable(valueAccessor()) || {};
|
3979
4408
|
ko.utils.objectForEach(value, function(attrName, attrValue) {
|
3980
4409
|
attrValue = ko.utils.unwrapObservable(attrValue);
|
3981
4410
|
|
4411
|
+
// Find the namespace of this attribute, if any.
|
4412
|
+
var prefixLen = attrName.indexOf(':');
|
4413
|
+
var namespace = "lookupNamespaceURI" in element && prefixLen > 0 && element.lookupNamespaceURI(attrName.substr(0, prefixLen));
|
4414
|
+
|
3982
4415
|
// To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
|
3983
4416
|
// when someProp is a "no value"-like value (strictly null, false, or undefined)
|
3984
4417
|
// (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
|
3985
4418
|
var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
|
3986
|
-
if (toRemove)
|
3987
|
-
element.removeAttribute(attrName);
|
4419
|
+
if (toRemove) {
|
4420
|
+
namespace ? element.removeAttributeNS(namespace, attrName) : element.removeAttribute(attrName);
|
4421
|
+
} else {
|
4422
|
+
attrValue = attrValue.toString();
|
4423
|
+
}
|
3988
4424
|
|
3989
|
-
// In IE <= 7 and IE8 Quirks Mode, you have to use the
|
4425
|
+
// In IE <= 7 and IE8 Quirks Mode, you have to use the JavaScript property name instead of the
|
3990
4426
|
// HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
|
3991
|
-
// but instead of figuring out the mode, we'll just set the attribute through the
|
4427
|
+
// but instead of figuring out the mode, we'll just set the attribute through the JavaScript
|
3992
4428
|
// property for IE <= 8.
|
3993
|
-
if (ko.utils.ieVersion <= 8 && attrName in
|
3994
|
-
attrName =
|
4429
|
+
if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavaScriptMap) {
|
4430
|
+
attrName = attrHtmlToJavaScriptMap[attrName];
|
3995
4431
|
if (toRemove)
|
3996
4432
|
element.removeAttribute(attrName);
|
3997
4433
|
else
|
3998
4434
|
element[attrName] = attrValue;
|
3999
4435
|
} else if (!toRemove) {
|
4000
|
-
element.
|
4436
|
+
namespace ? element.setAttributeNS(namespace, attrName, attrValue) : element.setAttribute(attrName, attrValue);
|
4001
4437
|
}
|
4002
4438
|
|
4003
4439
|
// Treat "name" specially - although you can think of it as an attribute, it also needs
|
@@ -4005,7 +4441,7 @@ ko.bindingHandlers['attr'] = {
|
|
4005
4441
|
// Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
|
4006
4442
|
// entirely, and there's no strong reason to allow for such casing in HTML.
|
4007
4443
|
if (attrName === "name") {
|
4008
|
-
ko.utils.setElementName(element, toRemove ? "" : attrValue
|
4444
|
+
ko.utils.setElementName(element, toRemove ? "" : attrValue);
|
4009
4445
|
}
|
4010
4446
|
});
|
4011
4447
|
}
|
@@ -4019,18 +4455,20 @@ ko.bindingHandlers['checked'] = {
|
|
4019
4455
|
// Treat "value" like "checkedValue" when it is included with "checked" binding
|
4020
4456
|
if (allBindings['has']('checkedValue')) {
|
4021
4457
|
return ko.utils.unwrapObservable(allBindings.get('checkedValue'));
|
4022
|
-
} else if (
|
4023
|
-
|
4458
|
+
} else if (useElementValue) {
|
4459
|
+
if (allBindings['has']('value')) {
|
4460
|
+
return ko.utils.unwrapObservable(allBindings.get('value'));
|
4461
|
+
} else {
|
4462
|
+
return element.value;
|
4463
|
+
}
|
4024
4464
|
}
|
4025
|
-
|
4026
|
-
return element.value;
|
4027
4465
|
});
|
4028
4466
|
|
4029
4467
|
function updateModel() {
|
4030
4468
|
// This updates the model value from the view value.
|
4031
4469
|
// It runs in response to DOM events (click) and changes in checkedValue.
|
4032
4470
|
var isChecked = element.checked,
|
4033
|
-
elemValue =
|
4471
|
+
elemValue = checkedValue();
|
4034
4472
|
|
4035
4473
|
// When we're first setting up this computed, don't change any model state.
|
4036
4474
|
if (ko.computedContext.isInitial()) {
|
@@ -4038,33 +4476,43 @@ ko.bindingHandlers['checked'] = {
|
|
4038
4476
|
}
|
4039
4477
|
|
4040
4478
|
// We can ignore unchecked radio buttons, because some other radio
|
4041
|
-
// button will be
|
4042
|
-
|
4479
|
+
// button will be checked, and that one can take care of updating state.
|
4480
|
+
// Also ignore value changes to an already unchecked checkbox.
|
4481
|
+
if (!isChecked && (isRadio || ko.computedContext.getDependenciesCount())) {
|
4043
4482
|
return;
|
4044
4483
|
}
|
4045
4484
|
|
4046
4485
|
var modelValue = ko.dependencyDetection.ignore(valueAccessor);
|
4047
4486
|
if (valueIsArray) {
|
4048
|
-
var writableValue = rawValueIsNonArrayObservable ? modelValue.peek() : modelValue
|
4049
|
-
|
4487
|
+
var writableValue = rawValueIsNonArrayObservable ? modelValue.peek() : modelValue,
|
4488
|
+
saveOldValue = oldElemValue;
|
4489
|
+
oldElemValue = elemValue;
|
4490
|
+
|
4491
|
+
if (saveOldValue !== elemValue) {
|
4050
4492
|
// When we're responding to the checkedValue changing, and the element is
|
4051
4493
|
// currently checked, replace the old elem value with the new elem value
|
4052
4494
|
// in the model array.
|
4053
4495
|
if (isChecked) {
|
4054
4496
|
ko.utils.addOrRemoveItem(writableValue, elemValue, true);
|
4055
|
-
ko.utils.addOrRemoveItem(writableValue,
|
4497
|
+
ko.utils.addOrRemoveItem(writableValue, saveOldValue, false);
|
4056
4498
|
}
|
4057
|
-
|
4058
|
-
oldElemValue = elemValue;
|
4059
4499
|
} else {
|
4060
4500
|
// When we're responding to the user having checked/unchecked a checkbox,
|
4061
4501
|
// add/remove the element value to the model array.
|
4062
4502
|
ko.utils.addOrRemoveItem(writableValue, elemValue, isChecked);
|
4063
4503
|
}
|
4504
|
+
|
4064
4505
|
if (rawValueIsNonArrayObservable && ko.isWriteableObservable(modelValue)) {
|
4065
4506
|
modelValue(writableValue);
|
4066
4507
|
}
|
4067
4508
|
} else {
|
4509
|
+
if (isCheckbox) {
|
4510
|
+
if (elemValue === undefined) {
|
4511
|
+
elemValue = isChecked;
|
4512
|
+
} else if (!isChecked) {
|
4513
|
+
elemValue = undefined;
|
4514
|
+
}
|
4515
|
+
}
|
4068
4516
|
ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'checked', elemValue, true);
|
4069
4517
|
}
|
4070
4518
|
};
|
@@ -4072,16 +4520,19 @@ ko.bindingHandlers['checked'] = {
|
|
4072
4520
|
function updateView() {
|
4073
4521
|
// This updates the view value from the model value.
|
4074
4522
|
// It runs in response to changes in the bound (checked) value.
|
4075
|
-
var modelValue = ko.utils.unwrapObservable(valueAccessor())
|
4523
|
+
var modelValue = ko.utils.unwrapObservable(valueAccessor()),
|
4524
|
+
elemValue = checkedValue();
|
4076
4525
|
|
4077
4526
|
if (valueIsArray) {
|
4078
4527
|
// When a checkbox is bound to an array, being checked represents its value being present in that array
|
4079
|
-
element.checked = ko.utils.arrayIndexOf(modelValue,
|
4080
|
-
|
4081
|
-
|
4082
|
-
|
4528
|
+
element.checked = ko.utils.arrayIndexOf(modelValue, elemValue) >= 0;
|
4529
|
+
oldElemValue = elemValue;
|
4530
|
+
} else if (isCheckbox && elemValue === undefined) {
|
4531
|
+
// When a checkbox is bound to any other value (not an array) and "checkedValue" is not defined,
|
4532
|
+
// being checked represents the value being trueish
|
4533
|
+
element.checked = !!modelValue;
|
4083
4534
|
} else {
|
4084
|
-
//
|
4535
|
+
// Otherwise, being checked means that the checkbox or radio button's value corresponds to the model value
|
4085
4536
|
element.checked = (checkedValue() === modelValue);
|
4086
4537
|
}
|
4087
4538
|
};
|
@@ -4097,8 +4548,8 @@ ko.bindingHandlers['checked'] = {
|
|
4097
4548
|
var rawValue = valueAccessor(),
|
4098
4549
|
valueIsArray = isCheckbox && (ko.utils.unwrapObservable(rawValue) instanceof Array),
|
4099
4550
|
rawValueIsNonArrayObservable = !(valueIsArray && rawValue.push && rawValue.splice),
|
4100
|
-
|
4101
|
-
|
4551
|
+
useElementValue = isRadio || valueIsArray,
|
4552
|
+
oldElemValue = valueIsArray ? checkedValue() : undefined;
|
4102
4553
|
|
4103
4554
|
// IE 6 won't allow radio buttons to be selected unless they have a name
|
4104
4555
|
if (isRadio && !element.name)
|
@@ -4125,6 +4576,15 @@ ko.bindingHandlers['checkedValue'] = {
|
|
4125
4576
|
};
|
4126
4577
|
|
4127
4578
|
})();var classesWrittenByBindingKey = '__ko__cssValue';
|
4579
|
+
ko.bindingHandlers['class'] = {
|
4580
|
+
'update': function (element, valueAccessor) {
|
4581
|
+
var value = ko.utils.stringTrim(ko.utils.unwrapObservable(valueAccessor()));
|
4582
|
+
ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
|
4583
|
+
element[classesWrittenByBindingKey] = value;
|
4584
|
+
ko.utils.toggleDomNodeCssClass(element, value, true);
|
4585
|
+
}
|
4586
|
+
};
|
4587
|
+
|
4128
4588
|
ko.bindingHandlers['css'] = {
|
4129
4589
|
'update': function (element, valueAccessor) {
|
4130
4590
|
var value = ko.utils.unwrapObservable(valueAccessor());
|
@@ -4134,10 +4594,7 @@ ko.bindingHandlers['css'] = {
|
|
4134
4594
|
ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
|
4135
4595
|
});
|
4136
4596
|
} else {
|
4137
|
-
|
4138
|
-
ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
|
4139
|
-
element[classesWrittenByBindingKey] = value;
|
4140
|
-
ko.utils.toggleDomNodeCssClass(element, value, true);
|
4597
|
+
ko.bindingHandlers['class']['update'](element, valueAccessor);
|
4141
4598
|
}
|
4142
4599
|
}
|
4143
4600
|
};
|
@@ -4227,6 +4684,7 @@ ko.bindingHandlers['foreach'] = {
|
|
4227
4684
|
return {
|
4228
4685
|
'foreach': unwrappedValue['data'],
|
4229
4686
|
'as': unwrappedValue['as'],
|
4687
|
+
'noChildContext': unwrappedValue['noChildContext'],
|
4230
4688
|
'includeDestroyed': unwrappedValue['includeDestroyed'],
|
4231
4689
|
'afterAdd': unwrappedValue['afterAdd'],
|
4232
4690
|
'beforeRemove': unwrappedValue['beforeRemove'],
|
@@ -4283,6 +4741,9 @@ ko.bindingHandlers['hasfocus'] = {
|
|
4283
4741
|
ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE
|
4284
4742
|
ko.utils.registerEventHandler(element, "blur", handleElementFocusOut);
|
4285
4743
|
ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
|
4744
|
+
|
4745
|
+
// Assume element is not focused (prevents "blur" being called initially)
|
4746
|
+
element[hasfocusLastValue] = false;
|
4286
4747
|
},
|
4287
4748
|
'update': function(element, valueAccessor) {
|
4288
4749
|
var value = !!ko.utils.unwrapObservable(valueAccessor());
|
@@ -4305,7 +4766,7 @@ ko.bindingHandlers['hasfocus'] = {
|
|
4305
4766
|
ko.expressionRewriting.twoWayBindings['hasfocus'] = true;
|
4306
4767
|
|
4307
4768
|
ko.bindingHandlers['hasFocus'] = ko.bindingHandlers['hasfocus']; // Make "hasFocus" an alias
|
4308
|
-
ko.expressionRewriting.twoWayBindings['hasFocus'] =
|
4769
|
+
ko.expressionRewriting.twoWayBindings['hasFocus'] = 'hasfocus';
|
4309
4770
|
ko.bindingHandlers['html'] = {
|
4310
4771
|
'init': function() {
|
4311
4772
|
// Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
|
@@ -4316,36 +4777,74 @@ ko.bindingHandlers['html'] = {
|
|
4316
4777
|
ko.utils.setHtml(element, valueAccessor());
|
4317
4778
|
}
|
4318
4779
|
};
|
4780
|
+
(function () {
|
4781
|
+
|
4319
4782
|
// Makes a binding like with or if
|
4320
|
-
function makeWithIfBinding(bindingKey, isWith, isNot
|
4783
|
+
function makeWithIfBinding(bindingKey, isWith, isNot) {
|
4321
4784
|
ko.bindingHandlers[bindingKey] = {
|
4322
4785
|
'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
4323
|
-
var didDisplayOnLastUpdate,
|
4324
|
-
|
4786
|
+
var didDisplayOnLastUpdate, savedNodes, contextOptions = {}, completeOnRender, needAsyncContext, renderOnEveryChange;
|
4787
|
+
|
4788
|
+
if (isWith) {
|
4789
|
+
var as = allBindings.get('as'), noChildContext = allBindings.get('noChildContext');
|
4790
|
+
renderOnEveryChange = !(as && noChildContext);
|
4791
|
+
contextOptions = { 'as': as, 'noChildContext': noChildContext, 'exportDependencies': renderOnEveryChange };
|
4792
|
+
}
|
4793
|
+
|
4794
|
+
completeOnRender = allBindings.get("completeOn") == "render";
|
4795
|
+
needAsyncContext = completeOnRender || allBindings['has'](ko.bindingEvent.descendantsComplete);
|
4796
|
+
|
4325
4797
|
ko.computed(function() {
|
4326
|
-
var
|
4327
|
-
shouldDisplay = !isNot !== !
|
4328
|
-
|
4329
|
-
|
4330
|
-
|
4331
|
-
if (
|
4332
|
-
|
4333
|
-
|
4334
|
-
|
4798
|
+
var value = ko.utils.unwrapObservable(valueAccessor()),
|
4799
|
+
shouldDisplay = !isNot !== !value, // equivalent to isNot ? !value : !!value,
|
4800
|
+
isInitial = !savedNodes,
|
4801
|
+
childContext;
|
4802
|
+
|
4803
|
+
if (!renderOnEveryChange && shouldDisplay === didDisplayOnLastUpdate) {
|
4804
|
+
return;
|
4805
|
+
}
|
4806
|
+
|
4807
|
+
if (needAsyncContext) {
|
4808
|
+
bindingContext = ko.bindingEvent.startPossiblyAsyncContentBinding(element, bindingContext);
|
4809
|
+
}
|
4810
|
+
|
4811
|
+
if (shouldDisplay) {
|
4812
|
+
if (!isWith || renderOnEveryChange) {
|
4813
|
+
contextOptions['dataDependency'] = ko.computedContext.computed();
|
4335
4814
|
}
|
4336
4815
|
|
4337
|
-
if (
|
4338
|
-
|
4339
|
-
|
4340
|
-
|
4341
|
-
ko.applyBindingsToDescendants(makeContextCallback ? makeContextCallback(bindingContext, dataValue) : bindingContext, element);
|
4816
|
+
if (isWith) {
|
4817
|
+
childContext = bindingContext['createChildContext'](typeof value == "function" ? value : valueAccessor, contextOptions);
|
4818
|
+
} else if (ko.computedContext.getDependenciesCount()) {
|
4819
|
+
childContext = bindingContext['extend'](null, contextOptions);
|
4342
4820
|
} else {
|
4343
|
-
|
4821
|
+
childContext = bindingContext;
|
4344
4822
|
}
|
4823
|
+
}
|
4824
|
+
|
4825
|
+
// Save a copy of the inner nodes on the initial update, but only if we have dependencies.
|
4826
|
+
if (isInitial && ko.computedContext.getDependenciesCount()) {
|
4827
|
+
savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */);
|
4828
|
+
}
|
4829
|
+
|
4830
|
+
if (shouldDisplay) {
|
4831
|
+
if (!isInitial) {
|
4832
|
+
ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(savedNodes));
|
4833
|
+
}
|
4834
|
+
|
4835
|
+
ko.applyBindingsToDescendants(childContext, element);
|
4836
|
+
} else {
|
4837
|
+
ko.virtualElements.emptyNode(element);
|
4345
4838
|
|
4346
|
-
|
4839
|
+
if (!completeOnRender) {
|
4840
|
+
ko.bindingEvent.notify(element, ko.bindingEvent.childrenComplete);
|
4841
|
+
}
|
4347
4842
|
}
|
4843
|
+
|
4844
|
+
didDisplayOnLastUpdate = shouldDisplay;
|
4845
|
+
|
4348
4846
|
}, null, { disposeWhenNodeIsRemoved: element });
|
4847
|
+
|
4349
4848
|
return { 'controlsDescendantBindings': true };
|
4350
4849
|
}
|
4351
4850
|
};
|
@@ -4356,11 +4855,18 @@ function makeWithIfBinding(bindingKey, isWith, isNot, makeContextCallback) {
|
|
4356
4855
|
// Construct the actual binding handlers
|
4357
4856
|
makeWithIfBinding('if');
|
4358
4857
|
makeWithIfBinding('ifnot', false /* isWith */, true /* isNot */);
|
4359
|
-
makeWithIfBinding('with', true /* isWith
|
4360
|
-
|
4361
|
-
|
4858
|
+
makeWithIfBinding('with', true /* isWith */);
|
4859
|
+
|
4860
|
+
})();ko.bindingHandlers['let'] = {
|
4861
|
+
'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
4862
|
+
// Make a modified binding context, with extra properties, and apply it to descendant elements
|
4863
|
+
var innerContext = bindingContext['extend'](valueAccessor);
|
4864
|
+
ko.applyBindingsToDescendants(innerContext, element);
|
4865
|
+
|
4866
|
+
return { 'controlsDescendantBindings': true };
|
4362
4867
|
}
|
4363
|
-
|
4868
|
+
};
|
4869
|
+
ko.virtualElements.allowedBindings['let'] = true;
|
4364
4870
|
var captionPlaceholder = {};
|
4365
4871
|
ko.bindingHandlers['options'] = {
|
4366
4872
|
'init': function(element) {
|
@@ -4570,7 +5076,23 @@ ko.bindingHandlers['style'] = {
|
|
4570
5076
|
styleValue = "";
|
4571
5077
|
}
|
4572
5078
|
|
4573
|
-
|
5079
|
+
if (jQueryInstance) {
|
5080
|
+
jQueryInstance(element)['css'](styleName, styleValue);
|
5081
|
+
} else if (/^--/.test(styleName)) {
|
5082
|
+
// Is styleName a custom CSS property?
|
5083
|
+
element.style.setProperty(styleName, styleValue);
|
5084
|
+
} else {
|
5085
|
+
styleName = styleName.replace(/-(\w)/g, function (all, letter) {
|
5086
|
+
return letter.toUpperCase();
|
5087
|
+
});
|
5088
|
+
|
5089
|
+
var previousStyle = element.style[styleName];
|
5090
|
+
element.style[styleName] = styleValue;
|
5091
|
+
|
5092
|
+
if (styleValue !== previousStyle && element.style[styleName] == previousStyle && !isNaN(styleValue)) {
|
5093
|
+
element.style[styleName] = styleValue + "px";
|
5094
|
+
}
|
5095
|
+
}
|
4574
5096
|
});
|
4575
5097
|
}
|
4576
5098
|
};
|
@@ -4614,10 +5136,16 @@ if (window && window.navigator) {
|
|
4614
5136
|
};
|
4615
5137
|
|
4616
5138
|
// Detect various browser versions because some old versions don't fully support the 'input' event
|
4617
|
-
var
|
4618
|
-
|
4619
|
-
|
4620
|
-
|
5139
|
+
var userAgent = window.navigator.userAgent,
|
5140
|
+
operaVersion, chromeVersion, safariVersion, firefoxVersion, ieVersion, edgeVersion;
|
5141
|
+
|
5142
|
+
(operaVersion = window.opera && window.opera.version && parseInt(window.opera.version()))
|
5143
|
+
|| (edgeVersion = parseVersion(userAgent.match(/Edge\/([^ ]+)$/)))
|
5144
|
+
|| (chromeVersion = parseVersion(userAgent.match(/Chrome\/([^ ]+)/)))
|
5145
|
+
|| (safariVersion = parseVersion(userAgent.match(/Version\/([^ ]+) Safari/)))
|
5146
|
+
|| (firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]+)/)))
|
5147
|
+
|| (ieVersion = ko.utils.ieVersion || parseVersion(userAgent.match(/MSIE ([^ ]+)/))) // Detects up to IE 10
|
5148
|
+
|| (ieVersion = parseVersion(userAgent.match(/rv:([^ )]+)/))); // Detects IE 11
|
4621
5149
|
}
|
4622
5150
|
|
4623
5151
|
// IE 8 and 9 have bugs that prevent the normal events from firing when the value changes.
|
@@ -4626,7 +5154,7 @@ if (window && window.navigator) {
|
|
4626
5154
|
// fired at the document level only and doesn't directly indicate which element changed. We
|
4627
5155
|
// set up just one event handler for the document and use 'activeElement' to determine which
|
4628
5156
|
// element was changed.
|
4629
|
-
if (
|
5157
|
+
if (ieVersion >= 8 && ieVersion < 10) {
|
4630
5158
|
var selectionChangeRegisteredName = ko.utils.domData.nextKey(),
|
4631
5159
|
selectionChangeHandlerName = ko.utils.domData.nextKey();
|
4632
5160
|
var selectionChangeHandler = function(event) {
|
@@ -4680,7 +5208,8 @@ ko.bindingHandlers['textInput'] = {
|
|
4680
5208
|
|
4681
5209
|
// IE9 will mess up the DOM if you handle events synchronously which results in DOM changes (such as other bindings);
|
4682
5210
|
// so we'll make sure all updates are asynchronous
|
4683
|
-
var ieUpdateModel = ko.utils.ieVersion == 9 ? deferUpdateModel : updateModel
|
5211
|
+
var ieUpdateModel = ko.utils.ieVersion == 9 ? deferUpdateModel : updateModel,
|
5212
|
+
ourUpdate = false;
|
4684
5213
|
|
4685
5214
|
var updateView = function () {
|
4686
5215
|
var modelValue = ko.utils.unwrapObservable(valueAccessor());
|
@@ -4697,8 +5226,10 @@ ko.bindingHandlers['textInput'] = {
|
|
4697
5226
|
// Update the element only if the element and model are different. On some browsers, updating the value
|
4698
5227
|
// will move the cursor to the end of the input, which would be bad while the user is typing.
|
4699
5228
|
if (element.value !== modelValue) {
|
4700
|
-
|
5229
|
+
ourUpdate = true; // Make sure we ignore events (propertychange) that result from updating the value
|
4701
5230
|
element.value = modelValue;
|
5231
|
+
ourUpdate = false;
|
5232
|
+
previousElementValue = element.value; // In case the browser changes the value (see #2281)
|
4702
5233
|
}
|
4703
5234
|
};
|
4704
5235
|
|
@@ -4716,62 +5247,74 @@ ko.bindingHandlers['textInput'] = {
|
|
4716
5247
|
}
|
4717
5248
|
});
|
4718
5249
|
} else {
|
4719
|
-
if (
|
5250
|
+
if (ieVersion) {
|
5251
|
+
// All versions (including 11) of Internet Explorer have a bug that they don't generate an input or propertychange event when ESC is pressed
|
5252
|
+
onEvent('keypress', updateModel);
|
5253
|
+
}
|
5254
|
+
if (ieVersion < 11) {
|
4720
5255
|
// Internet Explorer <= 8 doesn't support the 'input' event, but does include 'propertychange' that fires whenever
|
4721
5256
|
// any property of an element changes. Unlike 'input', it also fires if a property is changed from JavaScript code,
|
4722
|
-
// but that's an acceptable compromise for this binding. IE 9
|
4723
|
-
// when using autocomplete, we'll use 'propertychange' for
|
5257
|
+
// but that's an acceptable compromise for this binding. IE 9 and 10 support 'input', but since they don't always
|
5258
|
+
// fire it when using autocomplete, we'll use 'propertychange' for them also.
|
4724
5259
|
onEvent('propertychange', function(event) {
|
4725
|
-
if (event.propertyName === 'value') {
|
5260
|
+
if (!ourUpdate && event.propertyName === 'value') {
|
4726
5261
|
ieUpdateModel(event);
|
4727
5262
|
}
|
4728
5263
|
});
|
5264
|
+
}
|
5265
|
+
if (ieVersion == 8) {
|
5266
|
+
// IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from
|
5267
|
+
// JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following
|
5268
|
+
// events too.
|
5269
|
+
onEvent('keyup', updateModel); // A single keystoke
|
5270
|
+
onEvent('keydown', updateModel); // The first character when a key is held down
|
5271
|
+
}
|
5272
|
+
if (registerForSelectionChangeEvent) {
|
5273
|
+
// Internet Explorer 9 doesn't fire the 'input' event when deleting text, including using
|
5274
|
+
// the backspace, delete, or ctrl-x keys, clicking the 'x' to clear the input, dragging text
|
5275
|
+
// out of the field, and cutting or deleting text using the context menu. 'selectionchange'
|
5276
|
+
// can detect all of those except dragging text out of the field, for which we use 'dragend'.
|
5277
|
+
// These are also needed in IE8 because of the bug described above.
|
5278
|
+
registerForSelectionChangeEvent(element, ieUpdateModel); // 'selectionchange' covers cut, paste, drop, delete, etc.
|
5279
|
+
onEvent('dragend', deferUpdateModel);
|
5280
|
+
}
|
4729
5281
|
|
4730
|
-
|
4731
|
-
// IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from
|
4732
|
-
// JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following
|
4733
|
-
// events too.
|
4734
|
-
onEvent('keyup', updateModel); // A single keystoke
|
4735
|
-
onEvent('keydown', updateModel); // The first character when a key is held down
|
4736
|
-
}
|
4737
|
-
if (ko.utils.ieVersion >= 8) {
|
4738
|
-
// Internet Explorer 9 doesn't fire the 'input' event when deleting text, including using
|
4739
|
-
// the backspace, delete, or ctrl-x keys, clicking the 'x' to clear the input, dragging text
|
4740
|
-
// out of the field, and cutting or deleting text using the context menu. 'selectionchange'
|
4741
|
-
// can detect all of those except dragging text out of the field, for which we use 'dragend'.
|
4742
|
-
// These are also needed in IE8 because of the bug described above.
|
4743
|
-
registerForSelectionChangeEvent(element, ieUpdateModel); // 'selectionchange' covers cut, paste, drop, delete, etc.
|
4744
|
-
onEvent('dragend', deferUpdateModel);
|
4745
|
-
}
|
4746
|
-
} else {
|
5282
|
+
if (!ieVersion || ieVersion >= 9) {
|
4747
5283
|
// All other supported browsers support the 'input' event, which fires whenever the content of the element is changed
|
4748
5284
|
// through the user interface.
|
4749
|
-
onEvent('input',
|
4750
|
-
|
4751
|
-
|
4752
|
-
|
4753
|
-
|
4754
|
-
|
4755
|
-
|
4756
|
-
|
4757
|
-
|
4758
|
-
|
4759
|
-
|
4760
|
-
|
4761
|
-
|
4762
|
-
|
4763
|
-
|
4764
|
-
|
4765
|
-
|
4766
|
-
|
4767
|
-
|
4768
|
-
|
5285
|
+
onEvent('input', ieUpdateModel);
|
5286
|
+
}
|
5287
|
+
|
5288
|
+
if (safariVersion < 5 && ko.utils.tagNameLower(element) === "textarea") {
|
5289
|
+
// Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput'
|
5290
|
+
// but only when typing). So we'll just catch as much as we can with keydown, cut, and paste.
|
5291
|
+
onEvent('keydown', deferUpdateModel);
|
5292
|
+
onEvent('paste', deferUpdateModel);
|
5293
|
+
onEvent('cut', deferUpdateModel);
|
5294
|
+
} else if (operaVersion < 11) {
|
5295
|
+
// Opera 10 doesn't always fire the 'input' event for cut, paste, undo & drop operations.
|
5296
|
+
// We can try to catch some of those using 'keydown'.
|
5297
|
+
onEvent('keydown', deferUpdateModel);
|
5298
|
+
} else if (firefoxVersion < 4.0) {
|
5299
|
+
// Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
|
5300
|
+
onEvent('DOMAutoComplete', updateModel);
|
5301
|
+
|
5302
|
+
// Firefox <=3.5 doesn't fire the 'input' event when text is dropped into the input.
|
5303
|
+
onEvent('dragdrop', updateModel); // <3.5
|
5304
|
+
onEvent('drop', updateModel); // 3.5
|
5305
|
+
} else if (edgeVersion && element.type === "number") {
|
5306
|
+
// Microsoft Edge doesn't fire 'input' or 'change' events for number inputs when
|
5307
|
+
// the value is changed via the up / down arrow keys
|
5308
|
+
onEvent('keydown', deferUpdateModel);
|
4769
5309
|
}
|
4770
5310
|
}
|
4771
5311
|
|
4772
5312
|
// Bind to the change event so that we can catch programmatic updates of the value that fire this event.
|
4773
5313
|
onEvent('change', updateModel);
|
4774
5314
|
|
5315
|
+
// To deal with browsers that don't notify any kind of event for some changes (IE, Safari, etc.)
|
5316
|
+
onEvent('blur', updateModel);
|
5317
|
+
|
4775
5318
|
ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
|
4776
5319
|
}
|
4777
5320
|
};
|
@@ -4794,11 +5337,29 @@ ko.bindingHandlers['textinput'] = {
|
|
4794
5337
|
}
|
4795
5338
|
};
|
4796
5339
|
ko.bindingHandlers['uniqueName'].currentIndex = 0;
|
5340
|
+
ko.bindingHandlers['using'] = {
|
5341
|
+
'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
5342
|
+
var options;
|
5343
|
+
|
5344
|
+
if (allBindings['has']('as')) {
|
5345
|
+
options = { 'as': allBindings.get('as'), 'noChildContext': allBindings.get('noChildContext') };
|
5346
|
+
}
|
5347
|
+
|
5348
|
+
var innerContext = bindingContext['createChildContext'](valueAccessor, options);
|
5349
|
+
ko.applyBindingsToDescendants(innerContext, element);
|
5350
|
+
|
5351
|
+
return { 'controlsDescendantBindings': true };
|
5352
|
+
}
|
5353
|
+
};
|
5354
|
+
ko.virtualElements.allowedBindings['using'] = true;
|
4797
5355
|
ko.bindingHandlers['value'] = {
|
4798
5356
|
'after': ['options', 'foreach'],
|
4799
5357
|
'init': function (element, valueAccessor, allBindings) {
|
5358
|
+
var tagName = ko.utils.tagNameLower(element),
|
5359
|
+
isInputElement = tagName == "input";
|
5360
|
+
|
4800
5361
|
// If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
|
4801
|
-
if (
|
5362
|
+
if (isInputElement && (element.type == "checkbox" || element.type == "radio")) {
|
4802
5363
|
ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor });
|
4803
5364
|
return;
|
4804
5365
|
}
|
@@ -4826,7 +5387,7 @@ ko.bindingHandlers['value'] = {
|
|
4826
5387
|
|
4827
5388
|
// Workaround for https://github.com/SteveSanderson/knockout/issues/122
|
4828
5389
|
// IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
|
4829
|
-
var ieAutoCompleteHackNeeded = ko.utils.ieVersion &&
|
5390
|
+
var ieAutoCompleteHackNeeded = ko.utils.ieVersion && isInputElement && element.type == "text"
|
4830
5391
|
&& element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
|
4831
5392
|
if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
|
4832
5393
|
ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
|
@@ -4860,40 +5421,45 @@ ko.bindingHandlers['value'] = {
|
|
4860
5421
|
ko.utils.registerEventHandler(element, eventName, handler);
|
4861
5422
|
});
|
4862
5423
|
|
4863
|
-
var updateFromModel
|
4864
|
-
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
4865
|
-
var elementValue = ko.selectExtensions.readValue(element);
|
5424
|
+
var updateFromModel;
|
4866
5425
|
|
4867
|
-
|
4868
|
-
|
4869
|
-
|
5426
|
+
if (isInputElement && element.type == "file") {
|
5427
|
+
// For file input elements, can only write the empty string
|
5428
|
+
updateFromModel = function () {
|
5429
|
+
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
5430
|
+
if (newValue === null || newValue === undefined || newValue === "") {
|
5431
|
+
element.value = "";
|
5432
|
+
} else {
|
5433
|
+
ko.dependencyDetection.ignore(valueUpdateHandler); // reset the model to match the element
|
5434
|
+
}
|
4870
5435
|
}
|
5436
|
+
} else {
|
5437
|
+
updateFromModel = function () {
|
5438
|
+
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
5439
|
+
var elementValue = ko.selectExtensions.readValue(element);
|
4871
5440
|
|
4872
|
-
|
5441
|
+
if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
|
5442
|
+
ko.utils.setTimeout(updateFromModel, 0);
|
5443
|
+
return;
|
5444
|
+
}
|
4873
5445
|
|
4874
|
-
|
4875
|
-
if (ko.utils.tagNameLower(element) === "select") {
|
4876
|
-
var allowUnset = allBindings.get('valueAllowUnset');
|
4877
|
-
var applyValueAction = function () {
|
4878
|
-
ko.selectExtensions.writeValue(element, newValue, allowUnset);
|
4879
|
-
};
|
4880
|
-
applyValueAction();
|
5446
|
+
var valueHasChanged = newValue !== elementValue;
|
4881
5447
|
|
4882
|
-
|
4883
|
-
|
4884
|
-
|
4885
|
-
ko.
|
5448
|
+
if (valueHasChanged || elementValue === undefined) {
|
5449
|
+
if (tagName === "select") {
|
5450
|
+
var allowUnset = allBindings.get('valueAllowUnset');
|
5451
|
+
ko.selectExtensions.writeValue(element, newValue, allowUnset);
|
5452
|
+
if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
|
5453
|
+
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
|
5454
|
+
// because you're not allowed to have a model value that disagrees with a visible UI selection.
|
5455
|
+
ko.dependencyDetection.ignore(valueUpdateHandler);
|
5456
|
+
}
|
4886
5457
|
} else {
|
4887
|
-
|
4888
|
-
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
|
4889
|
-
// to apply the value as well.
|
4890
|
-
ko.utils.setTimeout(applyValueAction, 0);
|
5458
|
+
ko.selectExtensions.writeValue(element, newValue);
|
4891
5459
|
}
|
4892
|
-
} else {
|
4893
|
-
ko.selectExtensions.writeValue(element, newValue);
|
4894
5460
|
}
|
4895
|
-
}
|
4896
|
-
}
|
5461
|
+
};
|
5462
|
+
}
|
4897
5463
|
|
4898
5464
|
ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element });
|
4899
5465
|
},
|
@@ -4910,6 +5476,12 @@ ko.bindingHandlers['visible'] = {
|
|
4910
5476
|
element.style.display = "none";
|
4911
5477
|
}
|
4912
5478
|
};
|
5479
|
+
|
5480
|
+
ko.bindingHandlers['hidden'] = {
|
5481
|
+
'update': function (element, valueAccessor) {
|
5482
|
+
ko.bindingHandlers['visible']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
|
5483
|
+
}
|
5484
|
+
};
|
4913
5485
|
// 'click' is just a shorthand for the usual full-length event:{click:handler}
|
4914
5486
|
makeEventHandlerShortcut('click');
|
4915
5487
|
// If you want to make a custom template engine,
|
@@ -4993,7 +5565,7 @@ ko.templateRewriting = (function () {
|
|
4993
5565
|
var allValidators = ko.expressionRewriting.bindingRewriteValidators;
|
4994
5566
|
for (var i = 0; i < keyValueArray.length; i++) {
|
4995
5567
|
var key = keyValueArray[i]['key'];
|
4996
|
-
if (
|
5568
|
+
if (Object.prototype.hasOwnProperty.call(allValidators, key)) {
|
4997
5569
|
var validator = allValidators[key];
|
4998
5570
|
|
4999
5571
|
if (typeof validator === "function") {
|
@@ -5136,11 +5708,22 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
5136
5708
|
var element = this.domElement;
|
5137
5709
|
if (arguments.length == 0) {
|
5138
5710
|
var templateData = getTemplateDomData(element),
|
5139
|
-
|
5140
|
-
|
5141
|
-
|
5142
|
-
|
5143
|
-
|
5711
|
+
nodes = templateData.containerData || (
|
5712
|
+
this.templateType === templateTemplate ? element.content :
|
5713
|
+
this.templateType === templateElement ? element :
|
5714
|
+
undefined);
|
5715
|
+
if (!nodes || templateData.alwaysCheckText) {
|
5716
|
+
// If the template is associated with an element that stores the template as text,
|
5717
|
+
// parse and cache the nodes whenever there's new text content available. This allows
|
5718
|
+
// the user to update the template content by updating the text of template node.
|
5719
|
+
var text = this['text']();
|
5720
|
+
if (text) {
|
5721
|
+
nodes = ko.utils.parseHtmlForTemplateNodes(text, element.ownerDocument);
|
5722
|
+
this['text'](""); // clear the text from the node
|
5723
|
+
setTemplateDomData(element, {containerData: nodes, alwaysCheckText: true});
|
5724
|
+
}
|
5725
|
+
}
|
5726
|
+
return nodes;
|
5144
5727
|
} else {
|
5145
5728
|
var valueToWrite = arguments[0];
|
5146
5729
|
setTemplateDomData(element, {containerData: valueToWrite});
|
@@ -5281,8 +5864,12 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
5281
5864
|
|
5282
5865
|
if (haveAddedNodesToParent) {
|
5283
5866
|
activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
|
5284
|
-
if (options['afterRender'])
|
5285
|
-
ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext['$data']]);
|
5867
|
+
if (options['afterRender']) {
|
5868
|
+
ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext[options['as'] || '$data']]);
|
5869
|
+
}
|
5870
|
+
if (renderMode == "replaceChildren") {
|
5871
|
+
ko.bindingEvent.notify(targetNodeOrNodeArray, ko.bindingEvent.childrenComplete);
|
5872
|
+
}
|
5286
5873
|
}
|
5287
5874
|
|
5288
5875
|
return renderedNodesArray;
|
@@ -5319,7 +5906,7 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
5319
5906
|
// Ensure we've got a proper binding context to work with
|
5320
5907
|
var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
|
5321
5908
|
? dataOrBindingContext
|
5322
|
-
: new ko.bindingContext(
|
5909
|
+
: new ko.bindingContext(dataOrBindingContext, null, null, null, { "exportDependencies": true });
|
5323
5910
|
|
5324
5911
|
var templateName = resolveTemplateName(template, bindingContext['$data'], bindingContext),
|
5325
5912
|
renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
|
@@ -5343,18 +5930,25 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
5343
5930
|
ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
|
5344
5931
|
// Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
|
5345
5932
|
// activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
|
5346
|
-
var arrayItemContext;
|
5933
|
+
var arrayItemContext, asName = options['as'];
|
5347
5934
|
|
5348
5935
|
// This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
|
5349
5936
|
var executeTemplateForArrayItem = function (arrayValue, index) {
|
5350
5937
|
// Support selecting template as a function of the data being rendered
|
5351
|
-
arrayItemContext = parentBindingContext['createChildContext'](arrayValue,
|
5352
|
-
|
5938
|
+
arrayItemContext = parentBindingContext['createChildContext'](arrayValue, {
|
5939
|
+
'as': asName,
|
5940
|
+
'noChildContext': options['noChildContext'],
|
5941
|
+
'extend': function(context) {
|
5942
|
+
context['$index'] = index;
|
5943
|
+
if (asName) {
|
5944
|
+
context[asName + "Index"] = index;
|
5945
|
+
}
|
5946
|
+
}
|
5353
5947
|
});
|
5354
5948
|
|
5355
5949
|
var templateName = resolveTemplateName(template, arrayValue, arrayItemContext);
|
5356
|
-
return executeTemplate(
|
5357
|
-
}
|
5950
|
+
return executeTemplate(targetNode, "ignoreTargetNode", templateName, arrayItemContext, options);
|
5951
|
+
};
|
5358
5952
|
|
5359
5953
|
// This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
|
5360
5954
|
var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
|
@@ -5367,21 +5961,40 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
5367
5961
|
arrayItemContext = null;
|
5368
5962
|
};
|
5369
5963
|
|
5370
|
-
|
5371
|
-
var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
|
5372
|
-
if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
|
5373
|
-
unwrappedArray = [unwrappedArray];
|
5374
|
-
|
5375
|
-
// Filter out any entries marked as destroyed
|
5376
|
-
var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
|
5377
|
-
return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
|
5378
|
-
});
|
5379
|
-
|
5964
|
+
var setDomNodeChildrenFromArrayMapping = function (newArray, changeList) {
|
5380
5965
|
// Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
|
5381
5966
|
// If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
|
5382
|
-
ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode,
|
5967
|
+
ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, newArray, executeTemplateForArrayItem, options, activateBindingsCallback, changeList]);
|
5968
|
+
ko.bindingEvent.notify(targetNode, ko.bindingEvent.childrenComplete);
|
5969
|
+
};
|
5970
|
+
|
5971
|
+
var shouldHideDestroyed = (options['includeDestroyed'] === false) || (ko.options['foreachHidesDestroyed'] && !options['includeDestroyed']);
|
5383
5972
|
|
5384
|
-
|
5973
|
+
if (!shouldHideDestroyed && !options['beforeRemove'] && ko.isObservableArray(arrayOrObservableArray)) {
|
5974
|
+
setDomNodeChildrenFromArrayMapping(arrayOrObservableArray.peek());
|
5975
|
+
|
5976
|
+
var subscription = arrayOrObservableArray.subscribe(function (changeList) {
|
5977
|
+
setDomNodeChildrenFromArrayMapping(arrayOrObservableArray(), changeList);
|
5978
|
+
}, null, "arrayChange");
|
5979
|
+
subscription.disposeWhenNodeIsRemoved(targetNode);
|
5980
|
+
|
5981
|
+
return subscription;
|
5982
|
+
} else {
|
5983
|
+
return ko.dependentObservable(function () {
|
5984
|
+
var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
|
5985
|
+
if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
|
5986
|
+
unwrappedArray = [unwrappedArray];
|
5987
|
+
|
5988
|
+
if (shouldHideDestroyed) {
|
5989
|
+
// Filter out any entries marked as destroyed
|
5990
|
+
unwrappedArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
|
5991
|
+
return item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
|
5992
|
+
});
|
5993
|
+
}
|
5994
|
+
setDomNodeChildrenFromArrayMapping(unwrappedArray);
|
5995
|
+
|
5996
|
+
}, null, { disposeWhenNodeIsRemoved: targetNode });
|
5997
|
+
}
|
5385
5998
|
};
|
5386
5999
|
|
5387
6000
|
var templateComputedDomDataKey = ko.utils.domData.nextKey();
|
@@ -5389,9 +6002,10 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
5389
6002
|
var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
|
5390
6003
|
if (oldComputed && (typeof(oldComputed.dispose) == 'function'))
|
5391
6004
|
oldComputed.dispose();
|
5392
|
-
ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
|
6005
|
+
ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && (!newComputed.isActive || newComputed.isActive())) ? newComputed : undefined);
|
5393
6006
|
}
|
5394
6007
|
|
6008
|
+
var cleanContainerDomDataKey = ko.utils.domData.nextKey();
|
5395
6009
|
ko.bindingHandlers['template'] = {
|
5396
6010
|
'init': function(element, valueAccessor) {
|
5397
6011
|
// Support anonymous templates
|
@@ -5408,19 +6022,30 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
5408
6022
|
if (ko.isObservable(nodes)) {
|
5409
6023
|
throw new Error('The "nodes" option must be a plain, non-observable array.');
|
5410
6024
|
}
|
5411
|
-
|
6025
|
+
|
6026
|
+
// If the nodes are already attached to a KO-generated container, we reuse that container without moving the
|
6027
|
+
// elements to a new one (we check only the first node, as the nodes are always moved together)
|
6028
|
+
var container = nodes[0] && nodes[0].parentNode;
|
6029
|
+
if (!container || !ko.utils.domData.get(container, cleanContainerDomDataKey)) {
|
6030
|
+
container = ko.utils.moveCleanedNodesToContainerElement(nodes);
|
6031
|
+
ko.utils.domData.set(container, cleanContainerDomDataKey, true);
|
6032
|
+
}
|
6033
|
+
|
5412
6034
|
new ko.templateSources.anonymousTemplate(element)['nodes'](container);
|
5413
6035
|
} else {
|
5414
6036
|
// It's an anonymous template - store the element contents, then clear the element
|
5415
|
-
var templateNodes = ko.virtualElements.childNodes(element)
|
5416
|
-
|
5417
|
-
|
6037
|
+
var templateNodes = ko.virtualElements.childNodes(element);
|
6038
|
+
if (templateNodes.length > 0) {
|
6039
|
+
var container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
|
6040
|
+
new ko.templateSources.anonymousTemplate(element)['nodes'](container);
|
6041
|
+
} else {
|
6042
|
+
throw new Error("Anonymous template defined, but no template content was provided");
|
6043
|
+
}
|
5418
6044
|
}
|
5419
6045
|
return { 'controlsDescendantBindings': true };
|
5420
6046
|
},
|
5421
6047
|
'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
|
5422
6048
|
var value = valueAccessor(),
|
5423
|
-
dataValue,
|
5424
6049
|
options = ko.utils.unwrapObservable(value),
|
5425
6050
|
shouldDisplay = true,
|
5426
6051
|
templateComputed = null,
|
@@ -5437,8 +6062,6 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
5437
6062
|
shouldDisplay = ko.utils.unwrapObservable(options['if']);
|
5438
6063
|
if (shouldDisplay && 'ifnot' in options)
|
5439
6064
|
shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);
|
5440
|
-
|
5441
|
-
dataValue = ko.utils.unwrapObservable(options['data']);
|
5442
6065
|
}
|
5443
6066
|
|
5444
6067
|
if ('foreach' in options) {
|
@@ -5449,9 +6072,14 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
5449
6072
|
ko.virtualElements.emptyNode(element);
|
5450
6073
|
} else {
|
5451
6074
|
// Render once for this single data point (or use the viewModel if no data was provided)
|
5452
|
-
var innerBindingContext =
|
5453
|
-
|
5454
|
-
|
6075
|
+
var innerBindingContext = bindingContext;
|
6076
|
+
if ('data' in options) {
|
6077
|
+
innerBindingContext = bindingContext['createChildContext'](options['data'], {
|
6078
|
+
'as': options['as'],
|
6079
|
+
'noChildContext': options['noChildContext'],
|
6080
|
+
'exportDependencies': true
|
6081
|
+
});
|
6082
|
+
}
|
5455
6083
|
templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
|
5456
6084
|
}
|
5457
6085
|
|
@@ -5614,98 +6242,126 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
5614
6242
|
var lastMappingResultDomDataKey = ko.utils.domData.nextKey(),
|
5615
6243
|
deletedItemDummyValue = ko.utils.domData.nextKey();
|
5616
6244
|
|
5617
|
-
ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
|
5618
|
-
// Compare the provided array against the previous one
|
6245
|
+
ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes, editScript) {
|
5619
6246
|
array = array || [];
|
6247
|
+
if (typeof array.length == "undefined") // Coerce single value into array
|
6248
|
+
array = [array];
|
6249
|
+
|
5620
6250
|
options = options || {};
|
5621
|
-
var
|
5622
|
-
var
|
5623
|
-
var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
|
5624
|
-
var editScript = ko.utils.compareArrays(lastArray, array, options['dontLimitMoves']);
|
6251
|
+
var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey);
|
6252
|
+
var isFirstExecution = !lastMappingResult;
|
5625
6253
|
|
5626
6254
|
// Build the new mapping result
|
5627
6255
|
var newMappingResult = [];
|
5628
6256
|
var lastMappingResultIndex = 0;
|
5629
|
-
var
|
6257
|
+
var currentArrayIndex = 0;
|
5630
6258
|
|
5631
6259
|
var nodesToDelete = [];
|
5632
|
-
var
|
6260
|
+
var itemsToMoveFirstIndexes = [];
|
5633
6261
|
var itemsForBeforeRemoveCallbacks = [];
|
5634
6262
|
var itemsForMoveCallbacks = [];
|
5635
6263
|
var itemsForAfterAddCallbacks = [];
|
5636
6264
|
var mapData;
|
6265
|
+
var countWaitingForRemove = 0;
|
6266
|
+
|
6267
|
+
function itemAdded(value) {
|
6268
|
+
mapData = { arrayEntry: value, indexObservable: ko.observable(currentArrayIndex++) };
|
6269
|
+
newMappingResult.push(mapData);
|
6270
|
+
if (!isFirstExecution) {
|
6271
|
+
itemsForAfterAddCallbacks.push(mapData);
|
6272
|
+
}
|
6273
|
+
}
|
5637
6274
|
|
5638
|
-
function itemMovedOrRetained(
|
6275
|
+
function itemMovedOrRetained(oldPosition) {
|
5639
6276
|
mapData = lastMappingResult[oldPosition];
|
5640
|
-
if (
|
5641
|
-
itemsForMoveCallbacks
|
6277
|
+
if (currentArrayIndex !== mapData.indexObservable.peek())
|
6278
|
+
itemsForMoveCallbacks.push(mapData);
|
5642
6279
|
// Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
|
5643
|
-
mapData.indexObservable(
|
6280
|
+
mapData.indexObservable(currentArrayIndex++);
|
5644
6281
|
ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
|
5645
6282
|
newMappingResult.push(mapData);
|
5646
|
-
itemsToProcess.push(mapData);
|
5647
6283
|
}
|
5648
6284
|
|
5649
6285
|
function callCallback(callback, items) {
|
5650
6286
|
if (callback) {
|
5651
6287
|
for (var i = 0, n = items.length; i < n; i++) {
|
5652
|
-
|
5653
|
-
|
5654
|
-
|
5655
|
-
});
|
5656
|
-
}
|
6288
|
+
ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
|
6289
|
+
callback(node, i, items[i].arrayEntry);
|
6290
|
+
});
|
5657
6291
|
}
|
5658
6292
|
}
|
5659
6293
|
}
|
5660
6294
|
|
5661
|
-
|
5662
|
-
|
5663
|
-
|
5664
|
-
|
5665
|
-
|
5666
|
-
|
6295
|
+
if (isFirstExecution) {
|
6296
|
+
ko.utils.arrayForEach(array, itemAdded);
|
6297
|
+
} else {
|
6298
|
+
if (!editScript || (lastMappingResult && lastMappingResult['_countWaitingForRemove'])) {
|
6299
|
+
// Compare the provided array against the previous one
|
6300
|
+
var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; }),
|
6301
|
+
compareOptions = {
|
6302
|
+
'dontLimitMoves': options['dontLimitMoves'],
|
6303
|
+
'sparse': true
|
6304
|
+
};
|
6305
|
+
editScript = ko.utils.compareArrays(lastArray, array, compareOptions);
|
6306
|
+
}
|
5667
6307
|
|
5668
|
-
|
5669
|
-
|
5670
|
-
|
5671
|
-
|
6308
|
+
for (var i = 0, editScriptItem, movedIndex, itemIndex; editScriptItem = editScript[i]; i++) {
|
6309
|
+
movedIndex = editScriptItem['moved'];
|
6310
|
+
itemIndex = editScriptItem['index'];
|
6311
|
+
switch (editScriptItem['status']) {
|
6312
|
+
case "deleted":
|
6313
|
+
while (lastMappingResultIndex < itemIndex) {
|
6314
|
+
itemMovedOrRetained(lastMappingResultIndex++);
|
5672
6315
|
}
|
6316
|
+
if (movedIndex === undefined) {
|
6317
|
+
mapData = lastMappingResult[lastMappingResultIndex];
|
5673
6318
|
|
5674
|
-
|
5675
|
-
|
5676
|
-
|
5677
|
-
|
5678
|
-
itemsToProcess.push(mapData);
|
5679
|
-
if (mapData.arrayEntry === deletedItemDummyValue) {
|
5680
|
-
mapData = null;
|
5681
|
-
} else {
|
5682
|
-
itemsForBeforeRemoveCallbacks[i] = mapData;
|
5683
|
-
}
|
6319
|
+
// Stop tracking changes to the mapping for these nodes
|
6320
|
+
if (mapData.dependentObservable) {
|
6321
|
+
mapData.dependentObservable.dispose();
|
6322
|
+
mapData.dependentObservable = undefined;
|
5684
6323
|
}
|
5685
|
-
|
5686
|
-
|
6324
|
+
|
6325
|
+
// Queue these nodes for later removal
|
6326
|
+
if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) {
|
6327
|
+
if (options['beforeRemove']) {
|
6328
|
+
newMappingResult.push(mapData);
|
6329
|
+
countWaitingForRemove++;
|
6330
|
+
if (mapData.arrayEntry === deletedItemDummyValue) {
|
6331
|
+
mapData = null;
|
6332
|
+
} else {
|
6333
|
+
itemsForBeforeRemoveCallbacks.push(mapData);
|
6334
|
+
}
|
6335
|
+
}
|
6336
|
+
if (mapData) {
|
6337
|
+
nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
|
6338
|
+
}
|
5687
6339
|
}
|
5688
6340
|
}
|
5689
|
-
|
5690
|
-
|
5691
|
-
break;
|
6341
|
+
lastMappingResultIndex++;
|
6342
|
+
break;
|
5692
6343
|
|
5693
|
-
|
5694
|
-
|
5695
|
-
|
6344
|
+
case "added":
|
6345
|
+
while (currentArrayIndex < itemIndex) {
|
6346
|
+
itemMovedOrRetained(lastMappingResultIndex++);
|
6347
|
+
}
|
6348
|
+
if (movedIndex !== undefined) {
|
6349
|
+
itemsToMoveFirstIndexes.push(newMappingResult.length);
|
6350
|
+
itemMovedOrRetained(movedIndex);
|
6351
|
+
} else {
|
6352
|
+
itemAdded(editScriptItem['value']);
|
6353
|
+
}
|
6354
|
+
break;
|
6355
|
+
}
|
6356
|
+
}
|
5696
6357
|
|
5697
|
-
|
5698
|
-
|
5699
|
-
itemMovedOrRetained(i, movedIndex);
|
5700
|
-
} else {
|
5701
|
-
mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
|
5702
|
-
newMappingResult.push(mapData);
|
5703
|
-
itemsToProcess.push(mapData);
|
5704
|
-
if (!isFirstExecution)
|
5705
|
-
itemsForAfterAddCallbacks[i] = mapData;
|
5706
|
-
}
|
5707
|
-
break;
|
6358
|
+
while (currentArrayIndex < array.length) {
|
6359
|
+
itemMovedOrRetained(lastMappingResultIndex++);
|
5708
6360
|
}
|
6361
|
+
|
6362
|
+
// Record that the current view may still contain deleted items
|
6363
|
+
// because it means we won't be able to use a provided editScript.
|
6364
|
+
newMappingResult['_countWaitingForRemove'] = countWaitingForRemove;
|
5709
6365
|
}
|
5710
6366
|
|
5711
6367
|
// Store a copy of the array items we just considered so we can difference it next time
|
@@ -5717,25 +6373,57 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
5717
6373
|
// Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
|
5718
6374
|
ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
|
5719
6375
|
|
6376
|
+
var i, j, nextNodeInDom, lastNode, nodeToInsert, mappedNodes, activeElement;
|
6377
|
+
|
6378
|
+
// Since most browsers remove the focus from an element when it's moved to another location,
|
6379
|
+
// save the focused element and try to restore it later.
|
6380
|
+
try {
|
6381
|
+
activeElement = domNode.ownerDocument.activeElement;
|
6382
|
+
} catch(e) {
|
6383
|
+
// IE9 throws if you access activeElement during page load (see issue #703)
|
6384
|
+
}
|
6385
|
+
|
6386
|
+
// Try to reduce overall moved nodes by first moving the ones that were marked as moved by the edit script
|
6387
|
+
if (itemsToMoveFirstIndexes.length) {
|
6388
|
+
while ((i = itemsToMoveFirstIndexes.shift()) != undefined) {
|
6389
|
+
mapData = newMappingResult[i];
|
6390
|
+
for (lastNode = undefined; i; ) {
|
6391
|
+
if ((mappedNodes = newMappingResult[--i].mappedNodes) && mappedNodes.length) {
|
6392
|
+
lastNode = mappedNodes[mappedNodes.length-1];
|
6393
|
+
break;
|
6394
|
+
}
|
6395
|
+
}
|
6396
|
+
for (j = 0; nodeToInsert = mapData.mappedNodes[j]; lastNode = nodeToInsert, j++) {
|
6397
|
+
ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode);
|
6398
|
+
}
|
6399
|
+
}
|
6400
|
+
}
|
6401
|
+
|
5720
6402
|
// Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
|
5721
|
-
for (
|
6403
|
+
for (i = 0, nextNodeInDom = ko.virtualElements.firstChild(domNode); mapData = newMappingResult[i]; i++) {
|
5722
6404
|
// Get nodes for newly added items
|
5723
6405
|
if (!mapData.mappedNodes)
|
5724
6406
|
ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
|
5725
6407
|
|
5726
6408
|
// Put nodes in the right place if they aren't there already
|
5727
|
-
for (
|
5728
|
-
if (
|
5729
|
-
ko.virtualElements.insertAfter(domNode,
|
6409
|
+
for (j = 0; nodeToInsert = mapData.mappedNodes[j]; nextNodeInDom = nodeToInsert.nextSibling, lastNode = nodeToInsert, j++) {
|
6410
|
+
if (nodeToInsert !== nextNodeInDom)
|
6411
|
+
ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode);
|
5730
6412
|
}
|
5731
6413
|
|
5732
6414
|
// Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
|
5733
6415
|
if (!mapData.initialized && callbackAfterAddingNodes) {
|
5734
6416
|
callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
|
5735
6417
|
mapData.initialized = true;
|
6418
|
+
lastNode = mapData.mappedNodes[mapData.mappedNodes.length - 1]; // get the last node again since it may have been changed by a preprocessor
|
5736
6419
|
}
|
5737
6420
|
}
|
5738
6421
|
|
6422
|
+
// Restore the focused element if it had lost focus
|
6423
|
+
if (activeElement && domNode.ownerDocument.activeElement != activeElement) {
|
6424
|
+
activeElement.focus();
|
6425
|
+
}
|
6426
|
+
|
5739
6427
|
// If there's a beforeRemove callback, call it after reordering.
|
5740
6428
|
// Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
|
5741
6429
|
// some sort of animation, which is why we first reorder the nodes that will be removed. If the
|
@@ -5747,9 +6435,7 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
5747
6435
|
// as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
|
5748
6436
|
// with an actual item in the array and appear as "retained" or "moved".
|
5749
6437
|
for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) {
|
5750
|
-
|
5751
|
-
itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
|
5752
|
-
}
|
6438
|
+
itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
|
5753
6439
|
}
|
5754
6440
|
|
5755
6441
|
// Finally call afterMove and afterAdd callbacks
|