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.
@@ -1,5 +1,5 @@
1
1
  module Knockoutjs
2
2
  module Rails
3
- VERSION = "3.1.0.1"
3
+ VERSION = "3.2.0"
4
4
  end
5
5
  end
@@ -1,6 +1,8 @@
1
- // Knockout JavaScript library v3.1.0
2
- // (c) Steven Sanderson - http://knockoutjs.com/
3
- // License: MIT (http://www.opensource.org/licenses/mit-license.php)
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
- jQuery = window["jQuery"],
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
- var tokens = koPath.split(".");
36
+ var tokens = koPath.split(".");
35
37
 
36
- // In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable)
37
- // At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko)
38
- var target = ko;
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
- for (var i = 0; i < tokens.length - 1; i++)
41
- target = target[tokens[i]];
42
- target[tokens[tokens.length - 1]] = object;
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
- owner[publicName] = object;
47
+ owner[publicName] = object;
46
48
  };
47
- ko.version = "3.1.0";
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 && jQuery) {
369
- jQuery(element)['bind'](eventType, handler);
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 (jQuery && !useClickWorkaround) {
397
- jQuery(element)['trigger'](eventType);
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 (jQuery && (typeof jQuery['cleanData'] == "function"))
759
- jQuery['cleanData']([node]);
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 (jQuery['parseHTML']) {
810
- return jQuery['parseHTML'](html) || []; // Ensure we always return an array and never null
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 = jQuery['clean']([html]);
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 jQuery ? jQueryHtmlParse(html) // As below, benefit from jQuery's optimisations where possible
834
- : simpleHtmlParse(html); // ... otherwise, this simple logic will do in most common cases.
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 (jQuery) {
851
- jQuery(node)['html'](html);
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
- // This will force a computed with deferEvaluation to evaluate before any subscriptions
1051
- // are registered.
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
- try {
1620
- // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
1621
- // Then, during evaluation, we cross off any that are in fact still being used.
1622
- var disposalCandidates = _subscriptionsToDependencies, disposalCount = _dependenciesCount;
1623
- ko.dependencyDetection.begin({
1624
- callback: function(subscribable, id) {
1625
- if (!_isDisposed) {
1626
- if (disposalCount && disposalCandidates[id]) {
1627
- // Don't want to dispose this subscription, as it's still being used
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
- computed: dependentObservable,
1639
- isInitial: !_dependenciesCount // If we're evaluating when there are no previous dependencies, it must be the first time
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
- _subscriptionsToDependencies = {};
1643
- _dependenciesCount = 0;
1668
+ _subscriptionsToDependencies = {};
1669
+ _dependenciesCount = 0;
1644
1670
 
1645
- try {
1646
- var newValue = evaluatorFunctionTarget ? readFunction.call(evaluatorFunctionTarget) : readFunction();
1671
+ try {
1672
+ var newValue = evaluatorFunctionTarget ? readFunction.call(evaluatorFunctionTarget) : readFunction();
1647
1673
 
1648
- } finally {
1649
- ko.dependencyDetection.end();
1674
+ } finally {
1675
+ ko.dependencyDetection.end();
1650
1676
 
1651
- // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
1652
- if (disposalCount) {
1653
- ko.utils.objectForEach(disposalCandidates, function(id, toDispose) {
1654
- toDispose.dispose();
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
- _needsEvaluation = false;
1659
- }
1684
+ _needsEvaluation = false;
1685
+ }
1660
1686
 
1661
- if (dependentObservable.isDifferent(_latestValue, newValue)) {
1662
- dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
1687
+ if (dependentObservable.isDifferent(_latestValue, newValue)) {
1688
+ dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
1663
1689
 
1664
- _latestValue = newValue;
1665
- if (DEBUG) dependentObservable._latestValue = _latestValue;
1690
+ _latestValue = newValue;
1691
+ if (DEBUG) dependentObservable._latestValue = _latestValue;
1666
1692
 
1667
- // If rate-limited, the notification will happen within the limit function. Otherwise,
1668
- // notify as soon as the value changes. Check specifically for the throttle setting since
1669
- // it overrides rateLimit.
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
- // That's the only time that both of these conditions will be satisfied.
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 = disposeAllSubscriptionsToDependencies,
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'] !== true)
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
- disposeAllSubscriptionsToDependencies();
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 (!callPreprocessHook(ko['getBindingHandler'](key)))
2093
- return;
2151
+ if (!bindingParams) {
2152
+ if (!callPreprocessHook(ko['getBindingHandler'](key)))
2153
+ return;
2094
2154
 
2095
- if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
2096
- // For two-way bindings, provide a write method in case the value
2097
- // isn't a writable observable.
2098
- propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
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: return node.getAttribute(defaultBindingAttributeName) != null; // Element
2385
- case 8: return ko.virtualElements.hasBindingValue(node); // Comment node
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
- return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
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
- return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node, {'valueAccessors':true}) : null;
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 (!jQuery && window['jQuery']) {
2870
- jQuery = window['jQuery'];
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
- function checkedValue() {
2950
- return allBindings['has']('checkedValue')
2951
- ? ko.utils.unwrapObservable(allBindings.get('checkedValue'))
2952
- : element.value;
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
- element.style[styleName] = styleValue || ""; // Empty string removes the value, whereas null/undefined have no effect
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
- 'init': function() {
3485
- // Prevent binding on the dynamically-injected text node (as developers are unlikely to expect that, and it has security implications).
3486
- // It should also make things faster, as we no longer have to consider whether the text node might be bindable.
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
- ko.bindingHandlers['uniqueName'] = {
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() { setTimeout(valueUpdateHandler, 0) };
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
- if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
3564
- // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
3565
- // because you're not allowed to have a model value that disagrees with a visible UI selection.
3566
- ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
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
- // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
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() - returns the template text from your storage location
3741
- // text(value) - writes the supplied template text to your storage location
3742
- // data(key) - reads values stored using data(key, value) - see below
3743
- // data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
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
- // Support selecting template as a function of the data being rendered
3959
- var templateName = ko.isObservable(template) ? template()
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
- var templateName = typeof(template) == 'function' ? template(arrayValue, arrayItemContext) : template;
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 (!jQuery || !(jQuery['tmpl']))
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 (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
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 jQuery['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
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 = jQuery['template'](null, templateText);
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 = jQuery['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
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
- jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
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
- jQuery['tmpl']['tag']['ko_code'] = {
5277
+ jQueryInstance['tmpl']['tag']['ko_code'] = {
4448
5278
  open: "__.push($1 || '');"
4449
5279
  };
4450
- jQuery['tmpl']['tag']['ko_with'] = {
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
+ })();