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