knockoutjs-rails 3.1.0.1 → 3.2.0

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