knockoutjs-rails 3.1.0.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/knockoutjs-rails/version.rb +1 -1
- data/vendor/assets/javascripts/knockout.js +1002 -172
- metadata +2 -2
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
/*!
|
2
|
+
* Knockout JavaScript library v3.2.0
|
3
|
+
* (c) Steven Sanderson - http://knockoutjs.com/
|
4
|
+
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
5
|
+
*/
|
4
6
|
|
5
7
|
(function(){
|
6
8
|
var DEBUG=true;
|
@@ -10,41 +12,41 @@ var DEBUG=true;
|
|
10
12
|
var window = this || (0, eval)('this'),
|
11
13
|
document = window['document'],
|
12
14
|
navigator = window['navigator'],
|
13
|
-
|
15
|
+
jQueryInstance = window["jQuery"],
|
14
16
|
JSON = window["JSON"];
|
15
17
|
(function(factory) {
|
16
18
|
// Support three module loading scenarios
|
17
19
|
if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
|
18
20
|
// [1] CommonJS/Node.js
|
19
21
|
var target = module['exports'] || exports; // module.exports is for Node.js
|
20
|
-
factory(target);
|
22
|
+
factory(target, require);
|
21
23
|
} else if (typeof define === 'function' && define['amd']) {
|
22
24
|
// [2] AMD anonymous module
|
23
|
-
define(['exports'], factory);
|
25
|
+
define(['exports', 'require'], factory);
|
24
26
|
} else {
|
25
27
|
// [3] No module loader (plain <script> tag) - put directly in global namespace
|
26
28
|
factory(window['ko'] = {});
|
27
29
|
}
|
28
|
-
}(function(koExports){
|
30
|
+
}(function(koExports, require){
|
29
31
|
// Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
|
30
32
|
// In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
|
31
33
|
var ko = typeof koExports !== 'undefined' ? koExports : {};
|
32
34
|
// Google Closure Compiler helpers (used only to make the minified file smaller)
|
33
35
|
ko.exportSymbol = function(koPath, object) {
|
34
|
-
|
36
|
+
var tokens = koPath.split(".");
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
// In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable)
|
39
|
+
// At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko)
|
40
|
+
var target = ko;
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
for (var i = 0; i < tokens.length - 1; i++)
|
43
|
+
target = target[tokens[i]];
|
44
|
+
target[tokens[tokens.length - 1]] = object;
|
43
45
|
};
|
44
46
|
ko.exportProperty = function(owner, publicName, object) {
|
45
|
-
|
47
|
+
owner[publicName] = object;
|
46
48
|
};
|
47
|
-
ko.version = "3.
|
49
|
+
ko.version = "3.2.0";
|
48
50
|
|
49
51
|
ko.exportSymbol('version', ko.version);
|
50
52
|
ko.utils = (function () {
|
@@ -315,17 +317,6 @@ ko.utils = (function () {
|
|
315
317
|
string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
|
316
318
|
},
|
317
319
|
|
318
|
-
stringTokenize: function (string, delimiter) {
|
319
|
-
var result = [];
|
320
|
-
var tokens = (string || "").split(delimiter);
|
321
|
-
for (var i = 0, j = tokens.length; i < j; i++) {
|
322
|
-
var trimmed = ko.utils.stringTrim(tokens[i]);
|
323
|
-
if (trimmed !== "")
|
324
|
-
result.push(trimmed);
|
325
|
-
}
|
326
|
-
return result;
|
327
|
-
},
|
328
|
-
|
329
320
|
stringStartsWith: function (string, startsWith) {
|
330
321
|
string = string || "";
|
331
322
|
if (startsWith.length > string.length)
|
@@ -365,8 +356,8 @@ ko.utils = (function () {
|
|
365
356
|
|
366
357
|
registerEventHandler: function (element, eventType, handler) {
|
367
358
|
var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
|
368
|
-
if (!mustUseAttachEvent &&
|
369
|
-
|
359
|
+
if (!mustUseAttachEvent && jQueryInstance) {
|
360
|
+
jQueryInstance(element)['bind'](eventType, handler);
|
370
361
|
} else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
|
371
362
|
element.addEventListener(eventType, handler, false);
|
372
363
|
else if (typeof element.attachEvent != "undefined") {
|
@@ -393,8 +384,8 @@ ko.utils = (function () {
|
|
393
384
|
// In both cases, we'll use the click method instead.
|
394
385
|
var useClickWorkaround = isClickOnCheckableElement(element, eventType);
|
395
386
|
|
396
|
-
if (
|
397
|
-
|
387
|
+
if (jQueryInstance && !useClickWorkaround) {
|
388
|
+
jQueryInstance(element)['trigger'](eventType);
|
398
389
|
} else if (typeof document.createEvent == "function") {
|
399
390
|
if (typeof element.dispatchEvent == "function") {
|
400
391
|
var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
|
@@ -562,12 +553,14 @@ ko.utils = (function () {
|
|
562
553
|
for (var key in data) {
|
563
554
|
// Since 'data' this is a model object, we include all properties including those inherited from its prototype
|
564
555
|
var input = document.createElement("input");
|
556
|
+
input.type = "hidden";
|
565
557
|
input.name = key;
|
566
558
|
input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
|
567
559
|
form.appendChild(input);
|
568
560
|
}
|
569
561
|
objectForEach(params, function(key, value) {
|
570
562
|
var input = document.createElement("input");
|
563
|
+
input.type = "hidden";
|
571
564
|
input.name = key;
|
572
565
|
input.value = value;
|
573
566
|
form.appendChild(input);
|
@@ -755,8 +748,8 @@ ko.utils.domNodeDisposal = new (function () {
|
|
755
748
|
// Special support for jQuery here because it's so commonly used.
|
756
749
|
// Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
|
757
750
|
// so notify it to tear down any resources associated with the node & descendants here.
|
758
|
-
if (
|
759
|
-
|
751
|
+
if (jQueryInstance && (typeof jQueryInstance['cleanData'] == "function"))
|
752
|
+
jQueryInstance['cleanData']([node]);
|
760
753
|
}
|
761
754
|
}
|
762
755
|
})();
|
@@ -806,11 +799,11 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
|
|
806
799
|
|
807
800
|
function jQueryHtmlParse(html) {
|
808
801
|
// jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API.
|
809
|
-
if (
|
810
|
-
return
|
802
|
+
if (jQueryInstance['parseHTML']) {
|
803
|
+
return jQueryInstance['parseHTML'](html) || []; // Ensure we always return an array and never null
|
811
804
|
} else {
|
812
805
|
// For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function.
|
813
|
-
var elems =
|
806
|
+
var elems = jQueryInstance['clean']([html]);
|
814
807
|
|
815
808
|
// As of jQuery 1.7.1, jQuery parses the HTML by appending it to some dummy parent nodes held in an in-memory document fragment.
|
816
809
|
// Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
|
@@ -830,8 +823,8 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
|
|
830
823
|
}
|
831
824
|
|
832
825
|
ko.utils.parseHtmlFragment = function(html) {
|
833
|
-
return
|
834
|
-
|
826
|
+
return jQueryInstance ? jQueryHtmlParse(html) // As below, benefit from jQuery's optimisations where possible
|
827
|
+
: simpleHtmlParse(html); // ... otherwise, this simple logic will do in most common cases.
|
835
828
|
};
|
836
829
|
|
837
830
|
ko.utils.setHtml = function(node, html) {
|
@@ -847,8 +840,8 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
|
|
847
840
|
// jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
|
848
841
|
// for example <tr> elements which are not normally allowed to exist on their own.
|
849
842
|
// If you've referenced jQuery we'll use that rather than duplicating its code.
|
850
|
-
if (
|
851
|
-
|
843
|
+
if (jQueryInstance) {
|
844
|
+
jQueryInstance(node)['html'](html);
|
852
845
|
} else {
|
853
846
|
// ... otherwise, use KO's own parsing logic.
|
854
847
|
var parsedNodes = ko.utils.parseHtmlFragment(html);
|
@@ -1045,17 +1038,17 @@ var ko_subscribable_fn = {
|
|
1045
1038
|
|
1046
1039
|
var subscription = new ko.subscription(self, boundCallback, function () {
|
1047
1040
|
ko.utils.arrayRemoveItem(self._subscriptions[event], subscription);
|
1041
|
+
if (self.afterSubscriptionRemove)
|
1042
|
+
self.afterSubscriptionRemove(event);
|
1048
1043
|
});
|
1049
1044
|
|
1050
|
-
|
1051
|
-
|
1052
|
-
if (self.peek) {
|
1053
|
-
self.peek();
|
1054
|
-
}
|
1045
|
+
if (self.beforeSubscriptionAdd)
|
1046
|
+
self.beforeSubscriptionAdd(event);
|
1055
1047
|
|
1056
1048
|
if (!self._subscriptions[event])
|
1057
1049
|
self._subscriptions[event] = [];
|
1058
1050
|
self._subscriptions[event].push(subscription);
|
1051
|
+
|
1059
1052
|
return subscription;
|
1060
1053
|
},
|
1061
1054
|
|
@@ -1219,6 +1212,7 @@ ko.computedContext = ko.dependencyDetection = (function () {
|
|
1219
1212
|
ko.exportSymbol('computedContext', ko.computedContext);
|
1220
1213
|
ko.exportSymbol('computedContext.getDependenciesCount', ko.computedContext.getDependenciesCount);
|
1221
1214
|
ko.exportSymbol('computedContext.isInitial', ko.computedContext.isInitial);
|
1215
|
+
ko.exportSymbol('computedContext.isSleeping', ko.computedContext.isSleeping);
|
1222
1216
|
ko.observable = function (initialValue) {
|
1223
1217
|
var _latestValue = initialValue;
|
1224
1218
|
|
@@ -1293,6 +1287,7 @@ ko.isWriteableObservable = function (instance) {
|
|
1293
1287
|
ko.exportSymbol('observable', ko.observable);
|
1294
1288
|
ko.exportSymbol('isObservable', ko.isObservable);
|
1295
1289
|
ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
|
1290
|
+
ko.exportSymbol('isWritableObservable', ko.isWriteableObservable);
|
1296
1291
|
ko.observableArray = function (initialValues) {
|
1297
1292
|
initialValues = initialValues || [];
|
1298
1293
|
|
@@ -1546,7 +1541,9 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1546
1541
|
_isBeingEvaluated = false,
|
1547
1542
|
_suppressDisposalUntilDisposeWhenReturnsFalse = false,
|
1548
1543
|
_isDisposed = false,
|
1549
|
-
readFunction = evaluatorFunctionOrOptions
|
1544
|
+
readFunction = evaluatorFunctionOrOptions,
|
1545
|
+
pure = false,
|
1546
|
+
isSleeping = false;
|
1550
1547
|
|
1551
1548
|
if (readFunction && typeof readFunction == "object") {
|
1552
1549
|
// Single-parameter syntax - everything is on this "options" param
|
@@ -1569,12 +1566,16 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1569
1566
|
}
|
1570
1567
|
|
1571
1568
|
function disposeAllSubscriptionsToDependencies() {
|
1572
|
-
_isDisposed = true;
|
1573
1569
|
ko.utils.objectForEach(_subscriptionsToDependencies, function (id, subscription) {
|
1574
1570
|
subscription.dispose();
|
1575
1571
|
});
|
1576
1572
|
_subscriptionsToDependencies = {};
|
1573
|
+
}
|
1574
|
+
|
1575
|
+
function disposeComputed() {
|
1576
|
+
disposeAllSubscriptionsToDependencies();
|
1577
1577
|
_dependenciesCount = 0;
|
1578
|
+
_isDisposed = true;
|
1578
1579
|
_needsEvaluation = false;
|
1579
1580
|
}
|
1580
1581
|
|
@@ -1590,8 +1591,11 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1590
1591
|
}
|
1591
1592
|
}
|
1592
1593
|
|
1593
|
-
function evaluateImmediate() {
|
1594
|
+
function evaluateImmediate(suppressChangeNotification) {
|
1594
1595
|
if (_isBeingEvaluated) {
|
1596
|
+
if (pure) {
|
1597
|
+
throw Error("A 'pure' computed must not be called recursively");
|
1598
|
+
}
|
1595
1599
|
// If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
|
1596
1600
|
// This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
|
1597
1601
|
// certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
|
@@ -1616,63 +1620,83 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1616
1620
|
}
|
1617
1621
|
|
1618
1622
|
_isBeingEvaluated = true;
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
if (
|
1627
|
-
|
1628
|
-
_subscriptionsToDependencies[id] = disposalCandidates[id];
|
1623
|
+
|
1624
|
+
// When sleeping, recalculate the value and return.
|
1625
|
+
if (isSleeping) {
|
1626
|
+
try {
|
1627
|
+
var dependencyTracking = {};
|
1628
|
+
ko.dependencyDetection.begin({
|
1629
|
+
callback: function (subscribable, id) {
|
1630
|
+
if (!dependencyTracking[id]) {
|
1631
|
+
dependencyTracking[id] = 1;
|
1629
1632
|
++_dependenciesCount;
|
1630
|
-
delete disposalCandidates[id];
|
1631
|
-
--disposalCount;
|
1632
|
-
} else {
|
1633
|
-
// Brand new subscription - add it
|
1634
|
-
addSubscriptionToDependency(subscribable, id);
|
1635
1633
|
}
|
1636
|
-
}
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1634
|
+
},
|
1635
|
+
computed: dependentObservable,
|
1636
|
+
isInitial: undefined
|
1637
|
+
});
|
1638
|
+
_dependenciesCount = 0;
|
1639
|
+
_latestValue = readFunction.call(evaluatorFunctionTarget);
|
1640
|
+
} finally {
|
1641
|
+
ko.dependencyDetection.end();
|
1642
|
+
_isBeingEvaluated = false;
|
1643
|
+
}
|
1644
|
+
} else {
|
1645
|
+
try {
|
1646
|
+
// Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
|
1647
|
+
// Then, during evaluation, we cross off any that are in fact still being used.
|
1648
|
+
var disposalCandidates = _subscriptionsToDependencies, disposalCount = _dependenciesCount;
|
1649
|
+
ko.dependencyDetection.begin({
|
1650
|
+
callback: function(subscribable, id) {
|
1651
|
+
if (!_isDisposed) {
|
1652
|
+
if (disposalCount && disposalCandidates[id]) {
|
1653
|
+
// Don't want to dispose this subscription, as it's still being used
|
1654
|
+
_subscriptionsToDependencies[id] = disposalCandidates[id];
|
1655
|
+
++_dependenciesCount;
|
1656
|
+
delete disposalCandidates[id];
|
1657
|
+
--disposalCount;
|
1658
|
+
} else {
|
1659
|
+
// Brand new subscription - add it
|
1660
|
+
addSubscriptionToDependency(subscribable, id);
|
1661
|
+
}
|
1662
|
+
}
|
1663
|
+
},
|
1664
|
+
computed: dependentObservable,
|
1665
|
+
isInitial: pure ? undefined : !_dependenciesCount // If we're evaluating when there are no previous dependencies, it must be the first time
|
1666
|
+
});
|
1641
1667
|
|
1642
|
-
|
1643
|
-
|
1668
|
+
_subscriptionsToDependencies = {};
|
1669
|
+
_dependenciesCount = 0;
|
1644
1670
|
|
1645
|
-
|
1646
|
-
|
1671
|
+
try {
|
1672
|
+
var newValue = evaluatorFunctionTarget ? readFunction.call(evaluatorFunctionTarget) : readFunction();
|
1647
1673
|
|
1648
|
-
|
1649
|
-
|
1674
|
+
} finally {
|
1675
|
+
ko.dependencyDetection.end();
|
1650
1676
|
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1677
|
+
// For each subscription no longer being used, remove it from the active subscriptions list and dispose it
|
1678
|
+
if (disposalCount) {
|
1679
|
+
ko.utils.objectForEach(disposalCandidates, function(id, toDispose) {
|
1680
|
+
toDispose.dispose();
|
1681
|
+
});
|
1682
|
+
}
|
1657
1683
|
|
1658
|
-
|
1659
|
-
|
1684
|
+
_needsEvaluation = false;
|
1685
|
+
}
|
1660
1686
|
|
1661
|
-
|
1662
|
-
|
1687
|
+
if (dependentObservable.isDifferent(_latestValue, newValue)) {
|
1688
|
+
dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
|
1663
1689
|
|
1664
|
-
|
1665
|
-
|
1690
|
+
_latestValue = newValue;
|
1691
|
+
if (DEBUG) dependentObservable._latestValue = _latestValue;
|
1666
1692
|
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
1670
|
-
if (!dependentObservable._evalRateLimited || dependentObservable['throttleEvaluation']) {
|
1671
|
-
dependentObservable["notifySubscribers"](_latestValue);
|
1693
|
+
if (suppressChangeNotification !== true) { // Check for strict true value since setTimeout in Firefox passes a numeric value to the function
|
1694
|
+
dependentObservable["notifySubscribers"](_latestValue);
|
1695
|
+
}
|
1672
1696
|
}
|
1697
|
+
} finally {
|
1698
|
+
_isBeingEvaluated = false;
|
1673
1699
|
}
|
1674
|
-
} finally {
|
1675
|
-
_isBeingEvaluated = false;
|
1676
1700
|
}
|
1677
1701
|
|
1678
1702
|
if (!_dependenciesCount)
|
@@ -1690,18 +1714,18 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1690
1714
|
return this; // Permits chained assignments
|
1691
1715
|
} else {
|
1692
1716
|
// Reading the value
|
1693
|
-
if (_needsEvaluation)
|
1694
|
-
evaluateImmediate();
|
1695
1717
|
ko.dependencyDetection.registerDependency(dependentObservable);
|
1718
|
+
if (_needsEvaluation)
|
1719
|
+
evaluateImmediate(true /* suppressChangeNotification */);
|
1696
1720
|
return _latestValue;
|
1697
1721
|
}
|
1698
1722
|
}
|
1699
1723
|
|
1700
1724
|
function peek() {
|
1701
|
-
// Peek won't re-evaluate, except to get the initial value when "deferEvaluation" is set.
|
1702
|
-
//
|
1725
|
+
// Peek won't re-evaluate, except to get the initial value when "deferEvaluation" is set, or while the computed is sleeping.
|
1726
|
+
// Those are the only times that both of these conditions will be satisfied.
|
1703
1727
|
if (_needsEvaluation && !_dependenciesCount)
|
1704
|
-
evaluateImmediate();
|
1728
|
+
evaluateImmediate(true /* suppressChangeNotification */);
|
1705
1729
|
return _latestValue;
|
1706
1730
|
}
|
1707
1731
|
|
@@ -1714,7 +1738,7 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1714
1738
|
disposeWhenNodeIsRemoved = options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
|
1715
1739
|
disposeWhenOption = options["disposeWhen"] || options.disposeWhen,
|
1716
1740
|
disposeWhen = disposeWhenOption,
|
1717
|
-
dispose =
|
1741
|
+
dispose = disposeComputed,
|
1718
1742
|
_subscriptionsToDependencies = {},
|
1719
1743
|
_dependenciesCount = 0,
|
1720
1744
|
evaluationTimeoutInstance = null;
|
@@ -1746,6 +1770,30 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1746
1770
|
}
|
1747
1771
|
};
|
1748
1772
|
|
1773
|
+
if (options['pure']) {
|
1774
|
+
pure = true;
|
1775
|
+
isSleeping = true; // Starts off sleeping; will awake on the first subscription
|
1776
|
+
dependentObservable.beforeSubscriptionAdd = function () {
|
1777
|
+
// If asleep, wake up the computed and evaluate to register any dependencies.
|
1778
|
+
if (isSleeping) {
|
1779
|
+
isSleeping = false;
|
1780
|
+
evaluateImmediate(true /* suppressChangeNotification */);
|
1781
|
+
}
|
1782
|
+
}
|
1783
|
+
dependentObservable.afterSubscriptionRemove = function () {
|
1784
|
+
if (!dependentObservable.getSubscriptionsCount()) {
|
1785
|
+
disposeAllSubscriptionsToDependencies();
|
1786
|
+
isSleeping = _needsEvaluation = true;
|
1787
|
+
}
|
1788
|
+
}
|
1789
|
+
} else if (options['deferEvaluation']) {
|
1790
|
+
// This will force a computed with deferEvaluation to evaluate when the first subscriptions is registered.
|
1791
|
+
dependentObservable.beforeSubscriptionAdd = function () {
|
1792
|
+
peek();
|
1793
|
+
delete dependentObservable.beforeSubscriptionAdd;
|
1794
|
+
}
|
1795
|
+
}
|
1796
|
+
|
1749
1797
|
ko.exportProperty(dependentObservable, 'peek', dependentObservable.peek);
|
1750
1798
|
ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
|
1751
1799
|
ko.exportProperty(dependentObservable, 'isActive', dependentObservable.isActive);
|
@@ -1770,8 +1818,8 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1770
1818
|
}
|
1771
1819
|
}
|
1772
1820
|
|
1773
|
-
// Evaluate, unless deferEvaluation is true
|
1774
|
-
if (options['deferEvaluation']
|
1821
|
+
// Evaluate, unless sleeping or deferEvaluation is true
|
1822
|
+
if (!isSleeping && !options['deferEvaluation'])
|
1775
1823
|
evaluateImmediate();
|
1776
1824
|
|
1777
1825
|
// Attach a DOM node disposal callback so that the computed will be proactively disposed as soon as the node is
|
@@ -1779,7 +1827,7 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
|
|
1779
1827
|
if (disposeWhenNodeIsRemoved && isActive() && disposeWhenNodeIsRemoved.nodeType) {
|
1780
1828
|
dispose = function() {
|
1781
1829
|
ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, dispose);
|
1782
|
-
|
1830
|
+
disposeComputed();
|
1783
1831
|
};
|
1784
1832
|
ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
|
1785
1833
|
}
|
@@ -1809,6 +1857,17 @@ ko.exportSymbol('dependentObservable', ko.dependentObservable);
|
|
1809
1857
|
ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
|
1810
1858
|
ko.exportSymbol('isComputed', ko.isComputed);
|
1811
1859
|
|
1860
|
+
ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget) {
|
1861
|
+
if (typeof evaluatorFunctionOrOptions === 'function') {
|
1862
|
+
return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget, {'pure':true});
|
1863
|
+
} else {
|
1864
|
+
evaluatorFunctionOrOptions = ko.utils.extend({}, evaluatorFunctionOrOptions); // make a copy of the parameter object
|
1865
|
+
evaluatorFunctionOrOptions['pure'] = true;
|
1866
|
+
return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget);
|
1867
|
+
}
|
1868
|
+
}
|
1869
|
+
ko.exportSymbol('pureComputed', ko.pureComputed);
|
1870
|
+
|
1812
1871
|
(function() {
|
1813
1872
|
var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
|
1814
1873
|
|
@@ -2089,15 +2148,16 @@ ko.expressionRewriting = (function () {
|
|
2089
2148
|
function callPreprocessHook(obj) {
|
2090
2149
|
return (obj && obj['preprocess']) ? (val = obj['preprocess'](val, key, processKeyValue)) : true;
|
2091
2150
|
}
|
2092
|
-
if (!
|
2093
|
-
|
2151
|
+
if (!bindingParams) {
|
2152
|
+
if (!callPreprocessHook(ko['getBindingHandler'](key)))
|
2153
|
+
return;
|
2094
2154
|
|
2095
|
-
|
2096
|
-
|
2097
|
-
|
2098
|
-
|
2155
|
+
if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
|
2156
|
+
// For two-way bindings, provide a write method in case the value
|
2157
|
+
// isn't a writable observable.
|
2158
|
+
propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
|
2159
|
+
}
|
2099
2160
|
}
|
2100
|
-
|
2101
2161
|
// Values are wrapped in a function so that each value can be accessed independently
|
2102
2162
|
if (makeValueAccessors) {
|
2103
2163
|
val = 'function(){return ' + val + ' }';
|
@@ -2108,6 +2168,7 @@ ko.expressionRewriting = (function () {
|
|
2108
2168
|
var resultStrings = [],
|
2109
2169
|
propertyAccessorResultStrings = [],
|
2110
2170
|
makeValueAccessors = bindingOptions['valueAccessors'],
|
2171
|
+
bindingParams = bindingOptions['bindingParams'],
|
2111
2172
|
keyValueArray = typeof bindingsStringOrKeyValueArray === "string" ?
|
2112
2173
|
parseObjectLiteral(bindingsStringOrKeyValueArray) : bindingsStringOrKeyValueArray;
|
2113
2174
|
|
@@ -2381,20 +2442,25 @@ ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomN
|
|
2381
2442
|
ko.utils.extend(ko.bindingProvider.prototype, {
|
2382
2443
|
'nodeHasBindings': function(node) {
|
2383
2444
|
switch (node.nodeType) {
|
2384
|
-
case 1:
|
2385
|
-
|
2445
|
+
case 1: // Element
|
2446
|
+
return node.getAttribute(defaultBindingAttributeName) != null
|
2447
|
+
|| ko.components['getComponentNameForNode'](node);
|
2448
|
+
case 8: // Comment node
|
2449
|
+
return ko.virtualElements.hasBindingValue(node);
|
2386
2450
|
default: return false;
|
2387
2451
|
}
|
2388
2452
|
},
|
2389
2453
|
|
2390
2454
|
'getBindings': function(node, bindingContext) {
|
2391
|
-
var bindingsString = this['getBindingsString'](node, bindingContext)
|
2392
|
-
|
2455
|
+
var bindingsString = this['getBindingsString'](node, bindingContext),
|
2456
|
+
parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
|
2457
|
+
return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ false);
|
2393
2458
|
},
|
2394
2459
|
|
2395
2460
|
'getBindingAccessors': function(node, bindingContext) {
|
2396
|
-
var bindingsString = this['getBindingsString'](node, bindingContext)
|
2397
|
-
|
2461
|
+
var bindingsString = this['getBindingsString'](node, bindingContext),
|
2462
|
+
parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node, { 'valueAccessors': true }) : null;
|
2463
|
+
return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ true);
|
2398
2464
|
},
|
2399
2465
|
|
2400
2466
|
// The following function is only used internally by this default provider.
|
@@ -2866,8 +2932,8 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2866
2932
|
|
2867
2933
|
ko.applyBindings = function (viewModelOrBindingContext, rootNode) {
|
2868
2934
|
// If jQuery is loaded after Knockout, we won't initially have access to it. So save it here.
|
2869
|
-
if (!
|
2870
|
-
|
2935
|
+
if (!jQueryInstance && window['jQuery']) {
|
2936
|
+
jQueryInstance = window['jQuery'];
|
2871
2937
|
}
|
2872
2938
|
|
2873
2939
|
if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
|
@@ -2903,6 +2969,540 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2903
2969
|
ko.exportSymbol('contextFor', ko.contextFor);
|
2904
2970
|
ko.exportSymbol('dataFor', ko.dataFor);
|
2905
2971
|
})();
|
2972
|
+
(function(undefined) {
|
2973
|
+
var loadingSubscribablesCache = {}, // Tracks component loads that are currently in flight
|
2974
|
+
loadedDefinitionsCache = {}; // Tracks component loads that have already completed
|
2975
|
+
|
2976
|
+
ko.components = {
|
2977
|
+
get: function(componentName, callback) {
|
2978
|
+
var cachedDefinition = getObjectOwnProperty(loadedDefinitionsCache, componentName);
|
2979
|
+
if (cachedDefinition) {
|
2980
|
+
// It's already loaded and cached. Reuse the same definition object.
|
2981
|
+
// Note that for API consistency, even cache hits complete asynchronously.
|
2982
|
+
setTimeout(function() { callback(cachedDefinition) }, 0);
|
2983
|
+
} else {
|
2984
|
+
// Join the loading process that is already underway, or start a new one.
|
2985
|
+
loadComponentAndNotify(componentName, callback);
|
2986
|
+
}
|
2987
|
+
},
|
2988
|
+
|
2989
|
+
clearCachedDefinition: function(componentName) {
|
2990
|
+
delete loadedDefinitionsCache[componentName];
|
2991
|
+
},
|
2992
|
+
|
2993
|
+
_getFirstResultFromLoaders: getFirstResultFromLoaders
|
2994
|
+
};
|
2995
|
+
|
2996
|
+
function getObjectOwnProperty(obj, propName) {
|
2997
|
+
return obj.hasOwnProperty(propName) ? obj[propName] : undefined;
|
2998
|
+
}
|
2999
|
+
|
3000
|
+
function loadComponentAndNotify(componentName, callback) {
|
3001
|
+
var subscribable = getObjectOwnProperty(loadingSubscribablesCache, componentName),
|
3002
|
+
completedAsync;
|
3003
|
+
if (!subscribable) {
|
3004
|
+
// It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache.
|
3005
|
+
subscribable = loadingSubscribablesCache[componentName] = new ko.subscribable();
|
3006
|
+
beginLoadingComponent(componentName, function(definition) {
|
3007
|
+
loadedDefinitionsCache[componentName] = definition;
|
3008
|
+
delete loadingSubscribablesCache[componentName];
|
3009
|
+
|
3010
|
+
// For API consistency, all loads complete asynchronously. However we want to avoid
|
3011
|
+
// adding an extra setTimeout if it's unnecessary (i.e., the completion is already
|
3012
|
+
// async) since setTimeout(..., 0) still takes about 16ms or more on most browsers.
|
3013
|
+
if (completedAsync) {
|
3014
|
+
subscribable['notifySubscribers'](definition);
|
3015
|
+
} else {
|
3016
|
+
setTimeout(function() {
|
3017
|
+
subscribable['notifySubscribers'](definition);
|
3018
|
+
}, 0);
|
3019
|
+
}
|
3020
|
+
});
|
3021
|
+
completedAsync = true;
|
3022
|
+
}
|
3023
|
+
subscribable.subscribe(callback);
|
3024
|
+
}
|
3025
|
+
|
3026
|
+
function beginLoadingComponent(componentName, callback) {
|
3027
|
+
getFirstResultFromLoaders('getConfig', [componentName], function(config) {
|
3028
|
+
if (config) {
|
3029
|
+
// We have a config, so now load its definition
|
3030
|
+
getFirstResultFromLoaders('loadComponent', [componentName, config], function(definition) {
|
3031
|
+
callback(definition);
|
3032
|
+
});
|
3033
|
+
} else {
|
3034
|
+
// The component has no config - it's unknown to all the loaders.
|
3035
|
+
// Note that this is not an error (e.g., a module loading error) - that would abort the
|
3036
|
+
// process and this callback would not run. For this callback to run, all loaders must
|
3037
|
+
// have confirmed they don't know about this component.
|
3038
|
+
callback(null);
|
3039
|
+
}
|
3040
|
+
});
|
3041
|
+
}
|
3042
|
+
|
3043
|
+
function getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders) {
|
3044
|
+
// On the first call in the stack, start with the full set of loaders
|
3045
|
+
if (!candidateLoaders) {
|
3046
|
+
candidateLoaders = ko.components['loaders'].slice(0); // Use a copy, because we'll be mutating this array
|
3047
|
+
}
|
3048
|
+
|
3049
|
+
// Try the next candidate
|
3050
|
+
var currentCandidateLoader = candidateLoaders.shift();
|
3051
|
+
if (currentCandidateLoader) {
|
3052
|
+
var methodInstance = currentCandidateLoader[methodName];
|
3053
|
+
if (methodInstance) {
|
3054
|
+
var wasAborted = false,
|
3055
|
+
synchronousReturnValue = methodInstance.apply(currentCandidateLoader, argsExceptCallback.concat(function(result) {
|
3056
|
+
if (wasAborted) {
|
3057
|
+
callback(null);
|
3058
|
+
} else if (result !== null) {
|
3059
|
+
// This candidate returned a value. Use it.
|
3060
|
+
callback(result);
|
3061
|
+
} else {
|
3062
|
+
// Try the next candidate
|
3063
|
+
getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
|
3064
|
+
}
|
3065
|
+
}));
|
3066
|
+
|
3067
|
+
// Currently, loaders may not return anything synchronously. This leaves open the possibility
|
3068
|
+
// that we'll extend the API to support synchronous return values in the future. It won't be
|
3069
|
+
// a breaking change, because currently no loader is allowed to return anything except undefined.
|
3070
|
+
if (synchronousReturnValue !== undefined) {
|
3071
|
+
wasAborted = true;
|
3072
|
+
|
3073
|
+
// Method to suppress exceptions will remain undocumented. This is only to keep
|
3074
|
+
// KO's specs running tidily, since we can observe the loading got aborted without
|
3075
|
+
// having exceptions cluttering up the console too.
|
3076
|
+
if (!currentCandidateLoader['suppressLoaderExceptions']) {
|
3077
|
+
throw new Error('Component loaders must supply values by invoking the callback, not by returning values synchronously.');
|
3078
|
+
}
|
3079
|
+
}
|
3080
|
+
} else {
|
3081
|
+
// This candidate doesn't have the relevant handler. Synchronously move on to the next one.
|
3082
|
+
getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
|
3083
|
+
}
|
3084
|
+
} else {
|
3085
|
+
// No candidates returned a value
|
3086
|
+
callback(null);
|
3087
|
+
}
|
3088
|
+
}
|
3089
|
+
|
3090
|
+
// Reference the loaders via string name so it's possible for developers
|
3091
|
+
// to replace the whole array by assigning to ko.components.loaders
|
3092
|
+
ko.components['loaders'] = [];
|
3093
|
+
|
3094
|
+
ko.exportSymbol('components', ko.components);
|
3095
|
+
ko.exportSymbol('components.get', ko.components.get);
|
3096
|
+
ko.exportSymbol('components.clearCachedDefinition', ko.components.clearCachedDefinition);
|
3097
|
+
})();
|
3098
|
+
(function(undefined) {
|
3099
|
+
|
3100
|
+
// The default loader is responsible for two things:
|
3101
|
+
// 1. Maintaining the default in-memory registry of component configuration objects
|
3102
|
+
// (i.e., the thing you're writing to when you call ko.components.register(someName, ...))
|
3103
|
+
// 2. Answering requests for components by fetching configuration objects
|
3104
|
+
// from that default in-memory registry and resolving them into standard
|
3105
|
+
// component definition objects (of the form { createViewModel: ..., template: ... })
|
3106
|
+
// Custom loaders may override either of these facilities, i.e.,
|
3107
|
+
// 1. To supply configuration objects from some other source (e.g., conventions)
|
3108
|
+
// 2. Or, to resolve configuration objects by loading viewmodels/templates via arbitrary logic.
|
3109
|
+
|
3110
|
+
var defaultConfigRegistry = {};
|
3111
|
+
|
3112
|
+
ko.components.register = function(componentName, config) {
|
3113
|
+
if (!config) {
|
3114
|
+
throw new Error('Invalid configuration for ' + componentName);
|
3115
|
+
}
|
3116
|
+
|
3117
|
+
if (ko.components.isRegistered(componentName)) {
|
3118
|
+
throw new Error('Component ' + componentName + ' is already registered');
|
3119
|
+
}
|
3120
|
+
|
3121
|
+
defaultConfigRegistry[componentName] = config;
|
3122
|
+
}
|
3123
|
+
|
3124
|
+
ko.components.isRegistered = function(componentName) {
|
3125
|
+
return componentName in defaultConfigRegistry;
|
3126
|
+
}
|
3127
|
+
|
3128
|
+
ko.components.unregister = function(componentName) {
|
3129
|
+
delete defaultConfigRegistry[componentName];
|
3130
|
+
ko.components.clearCachedDefinition(componentName);
|
3131
|
+
}
|
3132
|
+
|
3133
|
+
ko.components.defaultLoader = {
|
3134
|
+
'getConfig': function(componentName, callback) {
|
3135
|
+
var result = defaultConfigRegistry.hasOwnProperty(componentName)
|
3136
|
+
? defaultConfigRegistry[componentName]
|
3137
|
+
: null;
|
3138
|
+
callback(result);
|
3139
|
+
},
|
3140
|
+
|
3141
|
+
'loadComponent': function(componentName, config, callback) {
|
3142
|
+
var errorCallback = makeErrorCallback(componentName);
|
3143
|
+
possiblyGetConfigFromAmd(errorCallback, config, function(loadedConfig) {
|
3144
|
+
resolveConfig(componentName, errorCallback, loadedConfig, callback);
|
3145
|
+
});
|
3146
|
+
},
|
3147
|
+
|
3148
|
+
'loadTemplate': function(componentName, templateConfig, callback) {
|
3149
|
+
resolveTemplate(makeErrorCallback(componentName), templateConfig, callback);
|
3150
|
+
},
|
3151
|
+
|
3152
|
+
'loadViewModel': function(componentName, viewModelConfig, callback) {
|
3153
|
+
resolveViewModel(makeErrorCallback(componentName), viewModelConfig, callback);
|
3154
|
+
}
|
3155
|
+
};
|
3156
|
+
|
3157
|
+
var createViewModelKey = 'createViewModel';
|
3158
|
+
|
3159
|
+
// Takes a config object of the form { template: ..., viewModel: ... }, and asynchronously convert it
|
3160
|
+
// into the standard component definition format:
|
3161
|
+
// { template: <ArrayOfDomNodes>, createViewModel: function(params, componentInfo) { ... } }.
|
3162
|
+
// Since both template and viewModel may need to be resolved asynchronously, both tasks are performed
|
3163
|
+
// in parallel, and the results joined when both are ready. We don't depend on any promises infrastructure,
|
3164
|
+
// so this is implemented manually below.
|
3165
|
+
function resolveConfig(componentName, errorCallback, config, callback) {
|
3166
|
+
var result = {},
|
3167
|
+
makeCallBackWhenZero = 2,
|
3168
|
+
tryIssueCallback = function() {
|
3169
|
+
if (--makeCallBackWhenZero === 0) {
|
3170
|
+
callback(result);
|
3171
|
+
}
|
3172
|
+
},
|
3173
|
+
templateConfig = config['template'],
|
3174
|
+
viewModelConfig = config['viewModel'];
|
3175
|
+
|
3176
|
+
if (templateConfig) {
|
3177
|
+
possiblyGetConfigFromAmd(errorCallback, templateConfig, function(loadedConfig) {
|
3178
|
+
ko.components._getFirstResultFromLoaders('loadTemplate', [componentName, loadedConfig], function(resolvedTemplate) {
|
3179
|
+
result['template'] = resolvedTemplate;
|
3180
|
+
tryIssueCallback();
|
3181
|
+
});
|
3182
|
+
});
|
3183
|
+
} else {
|
3184
|
+
tryIssueCallback();
|
3185
|
+
}
|
3186
|
+
|
3187
|
+
if (viewModelConfig) {
|
3188
|
+
possiblyGetConfigFromAmd(errorCallback, viewModelConfig, function(loadedConfig) {
|
3189
|
+
ko.components._getFirstResultFromLoaders('loadViewModel', [componentName, loadedConfig], function(resolvedViewModel) {
|
3190
|
+
result[createViewModelKey] = resolvedViewModel;
|
3191
|
+
tryIssueCallback();
|
3192
|
+
});
|
3193
|
+
});
|
3194
|
+
} else {
|
3195
|
+
tryIssueCallback();
|
3196
|
+
}
|
3197
|
+
}
|
3198
|
+
|
3199
|
+
function resolveTemplate(errorCallback, templateConfig, callback) {
|
3200
|
+
if (typeof templateConfig === 'string') {
|
3201
|
+
// Markup - parse it
|
3202
|
+
callback(ko.utils.parseHtmlFragment(templateConfig));
|
3203
|
+
} else if (templateConfig instanceof Array) {
|
3204
|
+
// Assume already an array of DOM nodes - pass through unchanged
|
3205
|
+
callback(templateConfig);
|
3206
|
+
} else if (isDocumentFragment(templateConfig)) {
|
3207
|
+
// Document fragment - use its child nodes
|
3208
|
+
callback(ko.utils.makeArray(templateConfig.childNodes));
|
3209
|
+
} else if (templateConfig['element']) {
|
3210
|
+
var element = templateConfig['element'];
|
3211
|
+
if (isDomElement(element)) {
|
3212
|
+
// Element instance - copy its child nodes
|
3213
|
+
callback(cloneNodesFromTemplateSourceElement(element));
|
3214
|
+
} else if (typeof element === 'string') {
|
3215
|
+
// Element ID - find it, then copy its child nodes
|
3216
|
+
var elemInstance = document.getElementById(element);
|
3217
|
+
if (elemInstance) {
|
3218
|
+
callback(cloneNodesFromTemplateSourceElement(elemInstance));
|
3219
|
+
} else {
|
3220
|
+
errorCallback('Cannot find element with ID ' + element);
|
3221
|
+
}
|
3222
|
+
} else {
|
3223
|
+
errorCallback('Unknown element type: ' + element);
|
3224
|
+
}
|
3225
|
+
} else {
|
3226
|
+
errorCallback('Unknown template value: ' + templateConfig);
|
3227
|
+
}
|
3228
|
+
}
|
3229
|
+
|
3230
|
+
function resolveViewModel(errorCallback, viewModelConfig, callback) {
|
3231
|
+
if (typeof viewModelConfig === 'function') {
|
3232
|
+
// Constructor - convert to standard factory function format
|
3233
|
+
// By design, this does *not* supply componentInfo to the constructor, as the intent is that
|
3234
|
+
// componentInfo contains non-viewmodel data (e.g., the component's element) that should only
|
3235
|
+
// be used in factory functions, not viewmodel constructors.
|
3236
|
+
callback(function (params /*, componentInfo */) {
|
3237
|
+
return new viewModelConfig(params);
|
3238
|
+
});
|
3239
|
+
} else if (typeof viewModelConfig[createViewModelKey] === 'function') {
|
3240
|
+
// Already a factory function - use it as-is
|
3241
|
+
callback(viewModelConfig[createViewModelKey]);
|
3242
|
+
} else if ('instance' in viewModelConfig) {
|
3243
|
+
// Fixed object instance - promote to createViewModel format for API consistency
|
3244
|
+
var fixedInstance = viewModelConfig['instance'];
|
3245
|
+
callback(function (params, componentInfo) {
|
3246
|
+
return fixedInstance;
|
3247
|
+
});
|
3248
|
+
} else if ('viewModel' in viewModelConfig) {
|
3249
|
+
// Resolved AMD module whose value is of the form { viewModel: ... }
|
3250
|
+
resolveViewModel(errorCallback, viewModelConfig['viewModel'], callback);
|
3251
|
+
} else {
|
3252
|
+
errorCallback('Unknown viewModel value: ' + viewModelConfig);
|
3253
|
+
}
|
3254
|
+
}
|
3255
|
+
|
3256
|
+
function cloneNodesFromTemplateSourceElement(elemInstance) {
|
3257
|
+
switch (ko.utils.tagNameLower(elemInstance)) {
|
3258
|
+
case 'script':
|
3259
|
+
return ko.utils.parseHtmlFragment(elemInstance.text);
|
3260
|
+
case 'textarea':
|
3261
|
+
return ko.utils.parseHtmlFragment(elemInstance.value);
|
3262
|
+
case 'template':
|
3263
|
+
// For browsers with proper <template> element support (i.e., where the .content property
|
3264
|
+
// gives a document fragment), use that document fragment.
|
3265
|
+
if (isDocumentFragment(elemInstance.content)) {
|
3266
|
+
return ko.utils.cloneNodes(elemInstance.content.childNodes);
|
3267
|
+
}
|
3268
|
+
}
|
3269
|
+
|
3270
|
+
// Regular elements such as <div>, and <template> elements on old browsers that don't really
|
3271
|
+
// understand <template> and just treat it as a regular container
|
3272
|
+
return ko.utils.cloneNodes(elemInstance.childNodes);
|
3273
|
+
}
|
3274
|
+
|
3275
|
+
function isDomElement(obj) {
|
3276
|
+
if (window['HTMLElement']) {
|
3277
|
+
return obj instanceof HTMLElement;
|
3278
|
+
} else {
|
3279
|
+
return obj && obj.tagName && obj.nodeType === 1;
|
3280
|
+
}
|
3281
|
+
}
|
3282
|
+
|
3283
|
+
function isDocumentFragment(obj) {
|
3284
|
+
if (window['DocumentFragment']) {
|
3285
|
+
return obj instanceof DocumentFragment;
|
3286
|
+
} else {
|
3287
|
+
return obj && obj.nodeType === 11;
|
3288
|
+
}
|
3289
|
+
}
|
3290
|
+
|
3291
|
+
function possiblyGetConfigFromAmd(errorCallback, config, callback) {
|
3292
|
+
if (typeof config['require'] === 'string') {
|
3293
|
+
// The config is the value of an AMD module
|
3294
|
+
if (require || window['require']) {
|
3295
|
+
(require || window['require'])([config['require']], callback);
|
3296
|
+
} else {
|
3297
|
+
errorCallback('Uses require, but no AMD loader is present');
|
3298
|
+
}
|
3299
|
+
} else {
|
3300
|
+
callback(config);
|
3301
|
+
}
|
3302
|
+
}
|
3303
|
+
|
3304
|
+
function makeErrorCallback(componentName) {
|
3305
|
+
return function (message) {
|
3306
|
+
throw new Error('Component \'' + componentName + '\': ' + message);
|
3307
|
+
};
|
3308
|
+
}
|
3309
|
+
|
3310
|
+
ko.exportSymbol('components.register', ko.components.register);
|
3311
|
+
ko.exportSymbol('components.isRegistered', ko.components.isRegistered);
|
3312
|
+
ko.exportSymbol('components.unregister', ko.components.unregister);
|
3313
|
+
|
3314
|
+
// Expose the default loader so that developers can directly ask it for configuration
|
3315
|
+
// or to resolve configuration
|
3316
|
+
ko.exportSymbol('components.defaultLoader', ko.components.defaultLoader);
|
3317
|
+
|
3318
|
+
// By default, the default loader is the only registered component loader
|
3319
|
+
ko.components['loaders'].push(ko.components.defaultLoader);
|
3320
|
+
|
3321
|
+
// Privately expose the underlying config registry for use in old-IE shim
|
3322
|
+
ko.components._allRegisteredComponents = defaultConfigRegistry;
|
3323
|
+
})();
|
3324
|
+
(function (undefined) {
|
3325
|
+
// Overridable API for determining which component name applies to a given node. By overriding this,
|
3326
|
+
// you can for example map specific tagNames to components that are not preregistered.
|
3327
|
+
ko.components['getComponentNameForNode'] = function(node) {
|
3328
|
+
var tagNameLower = ko.utils.tagNameLower(node);
|
3329
|
+
return ko.components.isRegistered(tagNameLower) && tagNameLower;
|
3330
|
+
};
|
3331
|
+
|
3332
|
+
ko.components.addBindingsForCustomElement = function(allBindings, node, bindingContext, valueAccessors) {
|
3333
|
+
// Determine if it's really a custom element matching a component
|
3334
|
+
if (node.nodeType === 1) {
|
3335
|
+
var componentName = ko.components['getComponentNameForNode'](node);
|
3336
|
+
if (componentName) {
|
3337
|
+
// It does represent a component, so add a component binding for it
|
3338
|
+
allBindings = allBindings || {};
|
3339
|
+
|
3340
|
+
if (allBindings['component']) {
|
3341
|
+
// Avoid silently overwriting some other 'component' binding that may already be on the element
|
3342
|
+
throw new Error('Cannot use the "component" binding on a custom element matching a component');
|
3343
|
+
}
|
3344
|
+
|
3345
|
+
var componentBindingValue = { 'name': componentName, 'params': getComponentParamsFromCustomElement(node, bindingContext) };
|
3346
|
+
|
3347
|
+
allBindings['component'] = valueAccessors
|
3348
|
+
? function() { return componentBindingValue; }
|
3349
|
+
: componentBindingValue;
|
3350
|
+
}
|
3351
|
+
}
|
3352
|
+
|
3353
|
+
return allBindings;
|
3354
|
+
}
|
3355
|
+
|
3356
|
+
var nativeBindingProviderInstance = new ko.bindingProvider();
|
3357
|
+
|
3358
|
+
function getComponentParamsFromCustomElement(elem, bindingContext) {
|
3359
|
+
var paramsAttribute = elem.getAttribute('params');
|
3360
|
+
|
3361
|
+
if (paramsAttribute) {
|
3362
|
+
var params = nativeBindingProviderInstance['parseBindingsString'](paramsAttribute, bindingContext, elem, { 'valueAccessors': true, 'bindingParams': true }),
|
3363
|
+
rawParamComputedValues = ko.utils.objectMap(params, function(paramValue, paramName) {
|
3364
|
+
return ko.computed(paramValue, null, { disposeWhenNodeIsRemoved: elem });
|
3365
|
+
}),
|
3366
|
+
result = ko.utils.objectMap(rawParamComputedValues, function(paramValueComputed, paramName) {
|
3367
|
+
// Does the evaluation of the parameter value unwrap any observables?
|
3368
|
+
if (!paramValueComputed.isActive()) {
|
3369
|
+
// No it doesn't, so there's no need for any computed wrapper. Just pass through the supplied value directly.
|
3370
|
+
// Example: "someVal: firstName, age: 123" (whether or not firstName is an observable/computed)
|
3371
|
+
return paramValueComputed.peek();
|
3372
|
+
} else {
|
3373
|
+
// Yes it does. Supply a computed property that unwraps both the outer (binding expression)
|
3374
|
+
// level of observability, and any inner (resulting model value) level of observability.
|
3375
|
+
// This means the component doesn't have to worry about multiple unwrapping.
|
3376
|
+
return ko.computed(function() {
|
3377
|
+
return ko.utils.unwrapObservable(paramValueComputed());
|
3378
|
+
}, null, { disposeWhenNodeIsRemoved: elem });
|
3379
|
+
}
|
3380
|
+
});
|
3381
|
+
|
3382
|
+
// Give access to the raw computeds, as long as that wouldn't overwrite any custom param also called '$raw'
|
3383
|
+
// This is in case the developer wants to react to outer (binding) observability separately from inner
|
3384
|
+
// (model value) observability, or in case the model value observable has subobservables.
|
3385
|
+
if (!result.hasOwnProperty('$raw')) {
|
3386
|
+
result['$raw'] = rawParamComputedValues;
|
3387
|
+
}
|
3388
|
+
|
3389
|
+
return result;
|
3390
|
+
} else {
|
3391
|
+
// For consistency, absence of a "params" attribute is treated the same as the presence of
|
3392
|
+
// any empty one. Otherwise component viewmodels need special code to check whether or not
|
3393
|
+
// 'params' or 'params.$raw' is null/undefined before reading subproperties, which is annoying.
|
3394
|
+
return { '$raw': {} };
|
3395
|
+
}
|
3396
|
+
}
|
3397
|
+
|
3398
|
+
// --------------------------------------------------------------------------------
|
3399
|
+
// Compatibility code for older (pre-HTML5) IE browsers
|
3400
|
+
|
3401
|
+
if (ko.utils.ieVersion < 9) {
|
3402
|
+
// Whenever you preregister a component, enable it as a custom element in the current document
|
3403
|
+
ko.components['register'] = (function(originalFunction) {
|
3404
|
+
return function(componentName) {
|
3405
|
+
document.createElement(componentName); // Allows IE<9 to parse markup containing the custom element
|
3406
|
+
return originalFunction.apply(this, arguments);
|
3407
|
+
}
|
3408
|
+
})(ko.components['register']);
|
3409
|
+
|
3410
|
+
// Whenever you create a document fragment, enable all preregistered component names as custom elements
|
3411
|
+
// This is needed to make innerShiv/jQuery HTML parsing correctly handle the custom elements
|
3412
|
+
document.createDocumentFragment = (function(originalFunction) {
|
3413
|
+
return function() {
|
3414
|
+
var newDocFrag = originalFunction(),
|
3415
|
+
allComponents = ko.components._allRegisteredComponents;
|
3416
|
+
for (var componentName in allComponents) {
|
3417
|
+
if (allComponents.hasOwnProperty(componentName)) {
|
3418
|
+
newDocFrag.createElement(componentName);
|
3419
|
+
}
|
3420
|
+
}
|
3421
|
+
return newDocFrag;
|
3422
|
+
};
|
3423
|
+
})(document.createDocumentFragment);
|
3424
|
+
}
|
3425
|
+
})();(function(undefined) {
|
3426
|
+
|
3427
|
+
var componentLoadingOperationUniqueId = 0;
|
3428
|
+
|
3429
|
+
ko.bindingHandlers['component'] = {
|
3430
|
+
'init': function(element, valueAccessor, ignored1, ignored2, bindingContext) {
|
3431
|
+
var currentViewModel,
|
3432
|
+
currentLoadingOperationId,
|
3433
|
+
disposeAssociatedComponentViewModel = function () {
|
3434
|
+
var currentViewModelDispose = currentViewModel && currentViewModel['dispose'];
|
3435
|
+
if (typeof currentViewModelDispose === 'function') {
|
3436
|
+
currentViewModelDispose.call(currentViewModel);
|
3437
|
+
}
|
3438
|
+
|
3439
|
+
// Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
|
3440
|
+
currentLoadingOperationId = null;
|
3441
|
+
};
|
3442
|
+
|
3443
|
+
ko.utils.domNodeDisposal.addDisposeCallback(element, disposeAssociatedComponentViewModel);
|
3444
|
+
|
3445
|
+
ko.computed(function () {
|
3446
|
+
var value = ko.utils.unwrapObservable(valueAccessor()),
|
3447
|
+
componentName, componentParams;
|
3448
|
+
|
3449
|
+
if (typeof value === 'string') {
|
3450
|
+
componentName = value;
|
3451
|
+
} else {
|
3452
|
+
componentName = ko.utils.unwrapObservable(value['name']);
|
3453
|
+
componentParams = ko.utils.unwrapObservable(value['params']);
|
3454
|
+
}
|
3455
|
+
|
3456
|
+
if (!componentName) {
|
3457
|
+
throw new Error('No component name specified');
|
3458
|
+
}
|
3459
|
+
|
3460
|
+
var loadingOperationId = currentLoadingOperationId = ++componentLoadingOperationUniqueId;
|
3461
|
+
ko.components.get(componentName, function(componentDefinition) {
|
3462
|
+
// If this is not the current load operation for this element, ignore it.
|
3463
|
+
if (currentLoadingOperationId !== loadingOperationId) {
|
3464
|
+
return;
|
3465
|
+
}
|
3466
|
+
|
3467
|
+
// Clean up previous state
|
3468
|
+
disposeAssociatedComponentViewModel();
|
3469
|
+
|
3470
|
+
// Instantiate and bind new component. Implicitly this cleans any old DOM nodes.
|
3471
|
+
if (!componentDefinition) {
|
3472
|
+
throw new Error('Unknown component \'' + componentName + '\'');
|
3473
|
+
}
|
3474
|
+
cloneTemplateIntoElement(componentName, componentDefinition, element);
|
3475
|
+
var componentViewModel = createViewModel(componentDefinition, element, componentParams),
|
3476
|
+
childBindingContext = bindingContext['createChildContext'](componentViewModel);
|
3477
|
+
currentViewModel = componentViewModel;
|
3478
|
+
ko.applyBindingsToDescendants(childBindingContext, element);
|
3479
|
+
});
|
3480
|
+
}, null, { disposeWhenNodeIsRemoved: element });
|
3481
|
+
|
3482
|
+
return { 'controlsDescendantBindings': true };
|
3483
|
+
}
|
3484
|
+
};
|
3485
|
+
|
3486
|
+
ko.virtualElements.allowedBindings['component'] = true;
|
3487
|
+
|
3488
|
+
function cloneTemplateIntoElement(componentName, componentDefinition, element) {
|
3489
|
+
var template = componentDefinition['template'];
|
3490
|
+
if (!template) {
|
3491
|
+
throw new Error('Component \'' + componentName + '\' has no template');
|
3492
|
+
}
|
3493
|
+
|
3494
|
+
var clonedNodesArray = ko.utils.cloneNodes(template);
|
3495
|
+
ko.virtualElements.setDomNodeChildren(element, clonedNodesArray);
|
3496
|
+
}
|
3497
|
+
|
3498
|
+
function createViewModel(componentDefinition, element, componentParams) {
|
3499
|
+
var componentViewModelFactory = componentDefinition['createViewModel'];
|
3500
|
+
return componentViewModelFactory
|
3501
|
+
? componentViewModelFactory.call(componentDefinition, componentParams, { element: element })
|
3502
|
+
: componentParams; // Template-only component
|
3503
|
+
}
|
3504
|
+
|
3505
|
+
})();
|
2906
3506
|
var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
|
2907
3507
|
ko.bindingHandlers['attr'] = {
|
2908
3508
|
'update': function(element, valueAccessor, allBindings) {
|
@@ -2946,11 +3546,16 @@ ko.bindingHandlers['attr'] = {
|
|
2946
3546
|
ko.bindingHandlers['checked'] = {
|
2947
3547
|
'after': ['value', 'attr'],
|
2948
3548
|
'init': function (element, valueAccessor, allBindings) {
|
2949
|
-
|
2950
|
-
|
2951
|
-
|
2952
|
-
|
2953
|
-
|
3549
|
+
var checkedValue = ko.pureComputed(function() {
|
3550
|
+
// Treat "value" like "checkedValue" when it is included with "checked" binding
|
3551
|
+
if (allBindings['has']('checkedValue')) {
|
3552
|
+
return ko.utils.unwrapObservable(allBindings.get('checkedValue'));
|
3553
|
+
} else if (allBindings['has']('value')) {
|
3554
|
+
return ko.utils.unwrapObservable(allBindings.get('value'));
|
3555
|
+
}
|
3556
|
+
|
3557
|
+
return element.value;
|
3558
|
+
});
|
2954
3559
|
|
2955
3560
|
function updateModel() {
|
2956
3561
|
// This updates the model value from the view value.
|
@@ -3457,7 +4062,13 @@ ko.bindingHandlers['style'] = {
|
|
3457
4062
|
var value = ko.utils.unwrapObservable(valueAccessor() || {});
|
3458
4063
|
ko.utils.objectForEach(value, function(styleName, styleValue) {
|
3459
4064
|
styleValue = ko.utils.unwrapObservable(styleValue);
|
3460
|
-
|
4065
|
+
|
4066
|
+
if (styleValue === null || styleValue === undefined || styleValue === false) {
|
4067
|
+
// Empty string removes the value, whereas null/undefined have no effect
|
4068
|
+
styleValue = "";
|
4069
|
+
}
|
4070
|
+
|
4071
|
+
element.style[styleName] = styleValue;
|
3461
4072
|
});
|
3462
4073
|
}
|
3463
4074
|
};
|
@@ -3481,17 +4092,194 @@ ko.bindingHandlers['submit'] = {
|
|
3481
4092
|
}
|
3482
4093
|
};
|
3483
4094
|
ko.bindingHandlers['text'] = {
|
3484
|
-
|
3485
|
-
|
3486
|
-
|
4095
|
+
'init': function() {
|
4096
|
+
// Prevent binding on the dynamically-injected text node (as developers are unlikely to expect that, and it has security implications).
|
4097
|
+
// It should also make things faster, as we no longer have to consider whether the text node might be bindable.
|
3487
4098
|
return { 'controlsDescendantBindings': true };
|
3488
|
-
|
4099
|
+
},
|
3489
4100
|
'update': function (element, valueAccessor) {
|
3490
4101
|
ko.utils.setTextContent(element, valueAccessor());
|
3491
4102
|
}
|
3492
4103
|
};
|
3493
4104
|
ko.virtualElements.allowedBindings['text'] = true;
|
3494
|
-
|
4105
|
+
(function () {
|
4106
|
+
|
4107
|
+
if (window && window.navigator) {
|
4108
|
+
var parseVersion = function (matches) {
|
4109
|
+
if (matches) {
|
4110
|
+
return parseFloat(matches[1]);
|
4111
|
+
}
|
4112
|
+
};
|
4113
|
+
|
4114
|
+
// Detect various browser versions because some old versions don't fully support the 'input' event
|
4115
|
+
var operaVersion = window.opera && window.opera.version && parseInt(window.opera.version()),
|
4116
|
+
userAgent = window.navigator.userAgent,
|
4117
|
+
safariVersion = parseVersion(userAgent.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),
|
4118
|
+
firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]*)/));
|
4119
|
+
}
|
4120
|
+
|
4121
|
+
// IE 8 and 9 have bugs that prevent the normal events from firing when the value changes.
|
4122
|
+
// But it does fire the 'selectionchange' event on many of those, presumably because the
|
4123
|
+
// cursor is moving and that counts as the selection changing. The 'selectionchange' event is
|
4124
|
+
// fired at the document level only and doesn't directly indicate which element changed. We
|
4125
|
+
// set up just one event handler for the document and use 'activeElement' to determine which
|
4126
|
+
// element was changed.
|
4127
|
+
if (ko.utils.ieVersion < 10) {
|
4128
|
+
var selectionChangeRegisteredName = ko.utils.domData.nextKey(),
|
4129
|
+
selectionChangeHandlerName = ko.utils.domData.nextKey();
|
4130
|
+
var selectionChangeHandler = function(event) {
|
4131
|
+
var target = this.activeElement,
|
4132
|
+
handler = target && ko.utils.domData.get(target, selectionChangeHandlerName);
|
4133
|
+
if (handler) {
|
4134
|
+
handler(event);
|
4135
|
+
}
|
4136
|
+
};
|
4137
|
+
var registerForSelectionChangeEvent = function (element, handler) {
|
4138
|
+
var ownerDoc = element.ownerDocument;
|
4139
|
+
if (!ko.utils.domData.get(ownerDoc, selectionChangeRegisteredName)) {
|
4140
|
+
ko.utils.domData.set(ownerDoc, selectionChangeRegisteredName, true);
|
4141
|
+
ko.utils.registerEventHandler(ownerDoc, 'selectionchange', selectionChangeHandler);
|
4142
|
+
}
|
4143
|
+
ko.utils.domData.set(element, selectionChangeHandlerName, handler);
|
4144
|
+
};
|
4145
|
+
}
|
4146
|
+
|
4147
|
+
ko.bindingHandlers['textInput'] = {
|
4148
|
+
'init': function (element, valueAccessor, allBindings) {
|
4149
|
+
|
4150
|
+
var previousElementValue = element.value,
|
4151
|
+
timeoutHandle,
|
4152
|
+
elementValueBeforeEvent;
|
4153
|
+
|
4154
|
+
var updateModel = function (event) {
|
4155
|
+
clearTimeout(timeoutHandle);
|
4156
|
+
elementValueBeforeEvent = timeoutHandle = undefined;
|
4157
|
+
|
4158
|
+
var elementValue = element.value;
|
4159
|
+
if (previousElementValue !== elementValue) {
|
4160
|
+
// Provide a way for tests to know exactly which event was processed
|
4161
|
+
if (DEBUG && event) element['_ko_textInputProcessedEvent'] = event.type;
|
4162
|
+
previousElementValue = elementValue;
|
4163
|
+
ko.expressionRewriting.writeValueToProperty(valueAccessor(), allBindings, 'textInput', elementValue);
|
4164
|
+
}
|
4165
|
+
};
|
4166
|
+
|
4167
|
+
var deferUpdateModel = function (event) {
|
4168
|
+
if (!timeoutHandle) {
|
4169
|
+
// The elementValueBeforeEvent variable is set *only* during the brief gap between an
|
4170
|
+
// event firing and the updateModel function running. This allows us to ignore model
|
4171
|
+
// updates that are from the previous state of the element, usually due to techniques
|
4172
|
+
// such as rateLimit. Such updates, if not ignored, can cause keystrokes to be lost.
|
4173
|
+
elementValueBeforeEvent = element.value;
|
4174
|
+
var handler = DEBUG ? updateModel.bind(element, {type: event.type}) : updateModel;
|
4175
|
+
timeoutHandle = setTimeout(handler, 4);
|
4176
|
+
}
|
4177
|
+
};
|
4178
|
+
|
4179
|
+
var updateView = function () {
|
4180
|
+
var modelValue = ko.utils.unwrapObservable(valueAccessor());
|
4181
|
+
|
4182
|
+
if (modelValue === null || modelValue === undefined) {
|
4183
|
+
modelValue = '';
|
4184
|
+
}
|
4185
|
+
|
4186
|
+
if (elementValueBeforeEvent !== undefined && modelValue === elementValueBeforeEvent) {
|
4187
|
+
setTimeout(updateView, 4);
|
4188
|
+
return;
|
4189
|
+
}
|
4190
|
+
|
4191
|
+
// Update the element only if the element and model are different. On some browsers, updating the value
|
4192
|
+
// will move the cursor to the end of the input, which would be bad while the user is typing.
|
4193
|
+
if (element.value !== modelValue) {
|
4194
|
+
previousElementValue = modelValue; // Make sure we ignore events (propertychange) that result from updating the value
|
4195
|
+
element.value = modelValue;
|
4196
|
+
}
|
4197
|
+
};
|
4198
|
+
|
4199
|
+
var onEvent = function (event, handler) {
|
4200
|
+
ko.utils.registerEventHandler(element, event, handler);
|
4201
|
+
};
|
4202
|
+
|
4203
|
+
if (DEBUG && ko.bindingHandlers['textInput']['_forceUpdateOn']) {
|
4204
|
+
// Provide a way for tests to specify exactly which events are bound
|
4205
|
+
ko.utils.arrayForEach(ko.bindingHandlers['textInput']['_forceUpdateOn'], function(eventName) {
|
4206
|
+
if (eventName.slice(0,5) == 'after') {
|
4207
|
+
onEvent(eventName.slice(5), deferUpdateModel);
|
4208
|
+
} else {
|
4209
|
+
onEvent(eventName, updateModel);
|
4210
|
+
}
|
4211
|
+
});
|
4212
|
+
} else {
|
4213
|
+
if (ko.utils.ieVersion < 10) {
|
4214
|
+
// Internet Explorer <= 8 doesn't support the 'input' event, but does include 'propertychange' that fires whenever
|
4215
|
+
// any property of an element changes. Unlike 'input', it also fires if a property is changed from JavaScript code,
|
4216
|
+
// but that's an acceptable compromise for this binding. IE 9 does support 'input', but since it doesn't fire it
|
4217
|
+
// when using autocomplete, we'll use 'propertychange' for it also.
|
4218
|
+
onEvent('propertychange', function(event) {
|
4219
|
+
if (event.propertyName === 'value') {
|
4220
|
+
updateModel(event);
|
4221
|
+
}
|
4222
|
+
});
|
4223
|
+
|
4224
|
+
if (ko.utils.ieVersion == 8) {
|
4225
|
+
// IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from
|
4226
|
+
// JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following
|
4227
|
+
// events too.
|
4228
|
+
onEvent('keyup', updateModel); // A single keystoke
|
4229
|
+
onEvent('keydown', updateModel); // The first character when a key is held down
|
4230
|
+
}
|
4231
|
+
if (ko.utils.ieVersion >= 8) {
|
4232
|
+
// Internet Explorer 9 doesn't fire the 'input' event when deleting text, including using
|
4233
|
+
// the backspace, delete, or ctrl-x keys, clicking the 'x' to clear the input, dragging text
|
4234
|
+
// out of the field, and cutting or deleting text using the context menu. 'selectionchange'
|
4235
|
+
// can detect all of those except dragging text out of the field, for which we use 'dragend'.
|
4236
|
+
// These are also needed in IE8 because of the bug described above.
|
4237
|
+
registerForSelectionChangeEvent(element, updateModel); // 'selectionchange' covers cut, paste, drop, delete, etc.
|
4238
|
+
onEvent('dragend', deferUpdateModel);
|
4239
|
+
}
|
4240
|
+
} else {
|
4241
|
+
// All other supported browsers support the 'input' event, which fires whenever the content of the element is changed
|
4242
|
+
// through the user interface.
|
4243
|
+
onEvent('input', updateModel);
|
4244
|
+
|
4245
|
+
if (safariVersion < 5 && ko.utils.tagNameLower(element) === "textarea") {
|
4246
|
+
// Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput'
|
4247
|
+
// but only when typing). So we'll just catch as much as we can with keydown, cut, and paste.
|
4248
|
+
onEvent('keydown', deferUpdateModel);
|
4249
|
+
onEvent('paste', deferUpdateModel);
|
4250
|
+
onEvent('cut', deferUpdateModel);
|
4251
|
+
} else if (operaVersion < 11) {
|
4252
|
+
// Opera 10 doesn't always fire the 'input' event for cut, paste, undo & drop operations.
|
4253
|
+
// We can try to catch some of those using 'keydown'.
|
4254
|
+
onEvent('keydown', deferUpdateModel);
|
4255
|
+
} else if (firefoxVersion < 4.0) {
|
4256
|
+
// Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
|
4257
|
+
onEvent('DOMAutoComplete', updateModel);
|
4258
|
+
|
4259
|
+
// Firefox <=3.5 doesn't fire the 'input' event when text is dropped into the input.
|
4260
|
+
onEvent('dragdrop', updateModel); // <3.5
|
4261
|
+
onEvent('drop', updateModel); // 3.5
|
4262
|
+
}
|
4263
|
+
}
|
4264
|
+
}
|
4265
|
+
|
4266
|
+
// Bind to the change event so that we can catch programmatic updates of the value that fire this event.
|
4267
|
+
onEvent('change', updateModel);
|
4268
|
+
|
4269
|
+
ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
|
4270
|
+
}
|
4271
|
+
};
|
4272
|
+
ko.expressionRewriting.twoWayBindings['textInput'] = true;
|
4273
|
+
|
4274
|
+
// textinput is an alias for textInput
|
4275
|
+
ko.bindingHandlers['textinput'] = {
|
4276
|
+
// preprocess is the only way to set up a full alias
|
4277
|
+
'preprocess': function (value, name, addBinding) {
|
4278
|
+
addBinding('textInput', value);
|
4279
|
+
}
|
4280
|
+
};
|
4281
|
+
|
4282
|
+
})();ko.bindingHandlers['uniqueName'] = {
|
3495
4283
|
'init': function (element, valueAccessor) {
|
3496
4284
|
if (valueAccessor()) {
|
3497
4285
|
var name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
|
@@ -3503,10 +4291,18 @@ ko.bindingHandlers['uniqueName'].currentIndex = 0;
|
|
3503
4291
|
ko.bindingHandlers['value'] = {
|
3504
4292
|
'after': ['options', 'foreach'],
|
3505
4293
|
'init': function (element, valueAccessor, allBindings) {
|
4294
|
+
// If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
|
4295
|
+
if (element.tagName.toLowerCase() == "input" && (element.type == "checkbox" || element.type == "radio")) {
|
4296
|
+
ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor });
|
4297
|
+
return;
|
4298
|
+
}
|
4299
|
+
|
3506
4300
|
// Always catch "change" event; possibly other events too if asked
|
3507
4301
|
var eventsToCatch = ["change"];
|
3508
4302
|
var requestedEventsToCatch = allBindings.get("valueUpdate");
|
3509
4303
|
var propertyChangedFired = false;
|
4304
|
+
var elementValueBeforeEvent = null;
|
4305
|
+
|
3510
4306
|
if (requestedEventsToCatch) {
|
3511
4307
|
if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
|
3512
4308
|
requestedEventsToCatch = [requestedEventsToCatch];
|
@@ -3515,6 +4311,7 @@ ko.bindingHandlers['value'] = {
|
|
3515
4311
|
}
|
3516
4312
|
|
3517
4313
|
var valueUpdateHandler = function() {
|
4314
|
+
elementValueBeforeEvent = null;
|
3518
4315
|
propertyChangedFired = false;
|
3519
4316
|
var modelValue = valueAccessor();
|
3520
4317
|
var elementValue = ko.selectExtensions.readValue(element);
|
@@ -3541,40 +4338,60 @@ ko.bindingHandlers['value'] = {
|
|
3541
4338
|
// (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
|
3542
4339
|
var handler = valueUpdateHandler;
|
3543
4340
|
if (ko.utils.stringStartsWith(eventName, "after")) {
|
3544
|
-
handler = function() {
|
4341
|
+
handler = function() {
|
4342
|
+
// The elementValueBeforeEvent variable is non-null *only* during the brief gap between
|
4343
|
+
// a keyX event firing and the valueUpdateHandler running, which is scheduled to happen
|
4344
|
+
// at the earliest asynchronous opportunity. We store this temporary information so that
|
4345
|
+
// if, between keyX and valueUpdateHandler, the underlying model value changes separately,
|
4346
|
+
// we can overwrite that model value change with the value the user just typed. Otherwise,
|
4347
|
+
// techniques like rateLimit can trigger model changes at critical moments that will
|
4348
|
+
// override the user's inputs, causing keystrokes to be lost.
|
4349
|
+
elementValueBeforeEvent = ko.selectExtensions.readValue(element);
|
4350
|
+
setTimeout(valueUpdateHandler, 0);
|
4351
|
+
};
|
3545
4352
|
eventName = eventName.substring("after".length);
|
3546
4353
|
}
|
3547
4354
|
ko.utils.registerEventHandler(element, eventName, handler);
|
3548
4355
|
});
|
3549
|
-
},
|
3550
|
-
'update': function (element, valueAccessor, allBindings) {
|
3551
|
-
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
3552
|
-
var elementValue = ko.selectExtensions.readValue(element);
|
3553
|
-
var valueHasChanged = (newValue !== elementValue);
|
3554
|
-
|
3555
|
-
if (valueHasChanged) {
|
3556
|
-
if (ko.utils.tagNameLower(element) === "select") {
|
3557
|
-
var allowUnset = allBindings.get('valueAllowUnset');
|
3558
|
-
var applyValueAction = function () {
|
3559
|
-
ko.selectExtensions.writeValue(element, newValue, allowUnset);
|
3560
|
-
};
|
3561
|
-
applyValueAction();
|
3562
4356
|
|
3563
|
-
|
3564
|
-
|
3565
|
-
|
3566
|
-
|
4357
|
+
var updateFromModel = function () {
|
4358
|
+
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
4359
|
+
var elementValue = ko.selectExtensions.readValue(element);
|
4360
|
+
|
4361
|
+
if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
|
4362
|
+
setTimeout(updateFromModel, 0);
|
4363
|
+
return;
|
4364
|
+
}
|
4365
|
+
|
4366
|
+
var valueHasChanged = (newValue !== elementValue);
|
4367
|
+
|
4368
|
+
if (valueHasChanged) {
|
4369
|
+
if (ko.utils.tagNameLower(element) === "select") {
|
4370
|
+
var allowUnset = allBindings.get('valueAllowUnset');
|
4371
|
+
var applyValueAction = function () {
|
4372
|
+
ko.selectExtensions.writeValue(element, newValue, allowUnset);
|
4373
|
+
};
|
4374
|
+
applyValueAction();
|
4375
|
+
|
4376
|
+
if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
|
4377
|
+
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
|
4378
|
+
// because you're not allowed to have a model value that disagrees with a visible UI selection.
|
4379
|
+
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
|
4380
|
+
} else {
|
4381
|
+
// Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
|
4382
|
+
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
|
4383
|
+
// to apply the value as well.
|
4384
|
+
setTimeout(applyValueAction, 0);
|
4385
|
+
}
|
3567
4386
|
} else {
|
3568
|
-
|
3569
|
-
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
|
3570
|
-
// to apply the value as well.
|
3571
|
-
setTimeout(applyValueAction, 0);
|
4387
|
+
ko.selectExtensions.writeValue(element, newValue);
|
3572
4388
|
}
|
3573
|
-
} else {
|
3574
|
-
ko.selectExtensions.writeValue(element, newValue);
|
3575
4389
|
}
|
3576
|
-
}
|
3577
|
-
|
4390
|
+
};
|
4391
|
+
|
4392
|
+
ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element });
|
4393
|
+
},
|
4394
|
+
'update': function() {} // Keep for backwards compatibility with code that may have wrapped value binding
|
3578
4395
|
};
|
3579
4396
|
ko.expressionRewriting.twoWayBindings['value'] = true;
|
3580
4397
|
ko.bindingHandlers['visible'] = {
|
@@ -3737,10 +4554,10 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
3737
4554
|
// with the rendered template output.
|
3738
4555
|
// You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
|
3739
4556
|
// Template sources need to have the following functions:
|
3740
|
-
// text()
|
3741
|
-
// text(value)
|
3742
|
-
// data(key)
|
3743
|
-
// data(key, value)
|
4557
|
+
// text() - returns the template text from your storage location
|
4558
|
+
// text(value) - writes the supplied template text to your storage location
|
4559
|
+
// data(key) - reads values stored using data(key, value) - see below
|
4560
|
+
// data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
|
3744
4561
|
//
|
3745
4562
|
// Optionally, template sources can also have the following functions:
|
3746
4563
|
// nodes() - returns a DOM element containing the nodes of this template, where available
|
@@ -3936,6 +4753,20 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
3936
4753
|
return renderedNodesArray;
|
3937
4754
|
}
|
3938
4755
|
|
4756
|
+
function resolveTemplateName(template, data, context) {
|
4757
|
+
// The template can be specified as:
|
4758
|
+
if (ko.isObservable(template)) {
|
4759
|
+
// 1. An observable, with string value
|
4760
|
+
return template();
|
4761
|
+
} else if (typeof template === 'function') {
|
4762
|
+
// 2. A function of (data, context) returning a string
|
4763
|
+
return template(data, context);
|
4764
|
+
} else {
|
4765
|
+
// 3. A string
|
4766
|
+
return template;
|
4767
|
+
}
|
4768
|
+
}
|
4769
|
+
|
3939
4770
|
ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
|
3940
4771
|
options = options || {};
|
3941
4772
|
if ((options['templateEngine'] || _templateEngine) == undefined)
|
@@ -3955,11 +4786,9 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
3955
4786
|
? dataOrBindingContext
|
3956
4787
|
: new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
|
3957
4788
|
|
3958
|
-
|
3959
|
-
|
3960
|
-
: typeof(template) == 'function' ? template(bindingContext['$data'], bindingContext) : template;
|
4789
|
+
var templateName = resolveTemplateName(template, bindingContext['$data'], bindingContext),
|
4790
|
+
renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
|
3961
4791
|
|
3962
|
-
var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
|
3963
4792
|
if (renderMode == "replaceNode") {
|
3964
4793
|
targetNodeOrNodeArray = renderedNodesArray;
|
3965
4794
|
firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
|
@@ -3987,7 +4816,8 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
3987
4816
|
arrayItemContext = parentBindingContext['createChildContext'](arrayValue, options['as'], function(context) {
|
3988
4817
|
context['$index'] = index;
|
3989
4818
|
});
|
3990
|
-
|
4819
|
+
|
4820
|
+
var templateName = resolveTemplateName(template, arrayValue, arrayItemContext);
|
3991
4821
|
return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
|
3992
4822
|
}
|
3993
4823
|
|
@@ -4388,11 +5218,11 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
|
4388
5218
|
// Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
|
4389
5219
|
// which KO internally refers to as version "2", so older versions are no longer detected.
|
4390
5220
|
var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
|
4391
|
-
if (!
|
5221
|
+
if (!jQueryInstance || !(jQueryInstance['tmpl']))
|
4392
5222
|
return 0;
|
4393
5223
|
// Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
|
4394
5224
|
try {
|
4395
|
-
if (
|
5225
|
+
if (jQueryInstance['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
|
4396
5226
|
// Since 1.0.0pre, custom tags should append markup to an array called "__"
|
4397
5227
|
return 2; // Final version of jquery.tmpl
|
4398
5228
|
}
|
@@ -4407,7 +5237,7 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
|
4407
5237
|
}
|
4408
5238
|
|
4409
5239
|
function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
|
4410
|
-
return
|
5240
|
+
return jQueryInstance['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
|
4411
5241
|
}
|
4412
5242
|
|
4413
5243
|
this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
|
@@ -4421,17 +5251,17 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
|
4421
5251
|
// Wrap in "with($whatever.koBindingContext) { ... }"
|
4422
5252
|
templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
|
4423
5253
|
|
4424
|
-
precompiled =
|
5254
|
+
precompiled = jQueryInstance['template'](null, templateText);
|
4425
5255
|
templateSource['data']('precompiled', precompiled);
|
4426
5256
|
}
|
4427
5257
|
|
4428
5258
|
var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
|
4429
|
-
var jQueryTemplateOptions =
|
5259
|
+
var jQueryTemplateOptions = jQueryInstance['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
|
4430
5260
|
|
4431
5261
|
var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
|
4432
5262
|
resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
|
4433
5263
|
|
4434
|
-
|
5264
|
+
jQueryInstance['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
|
4435
5265
|
return resultNodes;
|
4436
5266
|
};
|
4437
5267
|
|
@@ -4444,10 +5274,10 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
|
4444
5274
|
};
|
4445
5275
|
|
4446
5276
|
if (jQueryTmplVersion > 0) {
|
4447
|
-
|
5277
|
+
jQueryInstance['tmpl']['tag']['ko_code'] = {
|
4448
5278
|
open: "__.push($1 || '');"
|
4449
5279
|
};
|
4450
|
-
|
5280
|
+
jQueryInstance['tmpl']['tag']['ko_with'] = {
|
4451
5281
|
open: "with($1) {",
|
4452
5282
|
close: "} "
|
4453
5283
|
};
|
@@ -4466,4 +5296,4 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
|
4466
5296
|
})();
|
4467
5297
|
}));
|
4468
5298
|
}());
|
4469
|
-
})();
|
5299
|
+
})();
|