kea-rails 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 41bd1eac8219569939c938b547353ca7c9d1278c
4
- data.tar.gz: 09eb89eab320e446e915de000b41b428af9ce557
3
+ metadata.gz: 7114b9b4ade204e3dea3a496fc050fcc98ee6835
4
+ data.tar.gz: 27deae513503b4b475116bb20b8de56440d4f362
5
5
  SHA512:
6
- metadata.gz: dcdc1ce36da5e0e52630b689e6be1737bc05067ac1b8a1e60ae7c30e51d3c39b851a23057b00468063eb303bdc43fe9f416058b5041c11d59ae5485143836c00
7
- data.tar.gz: 4feb3931baa0e9349e28b31f246bde3f4b5f32afe519681fb9b0195fed4c26ebb6f527547fe0d44ad338bbfeb3a7ed5c18201c9c990cee76903a1f4f369eeca5
6
+ metadata.gz: a0f71e1f6bdc2abf06dcb32a88b30087c10b9e6182410ce7da262deec6afd977dbbec2ddab5cefd839581c6de7c76fdef48aba2bba3bb529c244ae16722aa098
7
+ data.tar.gz: a76dcd363f6f31f0545ce798f78dfd78b0200b85f6b93a7ade1a59e6e839eb3ed68196dcf0a6fdf3fd5af5146fff2bbfb7b7d1a4218525249fef14b492fe90b1
@@ -17,7 +17,7 @@
17
17
  });
18
18
  }
19
19
 
20
- if (setupContext && typeof childVm.setup === 'function') {
20
+ if (typeof childVm.setup === 'function') {
21
21
  childVm.setup(setupContext);
22
22
  }
23
23
 
@@ -0,0 +1,32 @@
1
+ (function(ko, $) {
2
+ "use strict";
3
+
4
+ ko.components.register('confirmation-button', {
5
+ viewModel: function(params) {
6
+ this.text = ko.observable(params.initialText || '');
7
+ this.css = params.css || '';
8
+ },
9
+ template: '<!-- ko template: { nodes: $componentTemplateNodes } --><!-- /ko -->'
10
+ });
11
+
12
+ })(ko, $);
13
+
14
+ (function(ko, $) {
15
+ "use strict";
16
+
17
+ ko.components.register('confirmation-button', {
18
+ viewModel: {
19
+ createViewModel: function createViewModel(params, componentInfo) {
20
+ var ConfirmationButtonVm;
21
+
22
+ ConfirmationButtonVm = function ConfirmationButtonVm(params) {
23
+
24
+ };
25
+
26
+ return new ConfirmationButtonVm(params);
27
+ }
28
+ },
29
+ template: 'x'
30
+ });
31
+
32
+ })(ko, $);
@@ -1,5 +1,6 @@
1
1
  //= require_directory ./bindings
2
2
  //= require_directory ./extenders
3
+ //= require_directory ./components
3
4
  //= require ./models/base
4
5
  //= require ./services/base
5
6
  //= require ./viewmodels/base
@@ -1,4 +1,4 @@
1
- //= require knockout-3.2.0-debug
1
+ //= require knockout-3.3.0-debug
2
2
 
3
3
  //= require_directory ../../../../vendor/assets/javascripts/.
4
4
 
@@ -12,7 +12,9 @@ module Kea
12
12
  options[:scope] ||= self
13
13
  options[:url_options] ||= url_options
14
14
 
15
- target.active_model_serializer.new(target, options).to_json
15
+ serializer = options[:serializer] || target.active_model_serializer
16
+
17
+ serializer.new(target, options).to_json
16
18
  end
17
19
 
18
20
  def cache_json(object, path = nil, options = {})
@@ -25,6 +27,20 @@ module Kea
25
27
  content_for :json_cache, "window.app.cache['#{path}'] = #{content};\n".html_safe
26
28
  end
27
29
 
30
+ def knockout_template(name, partial: nil, &block)
31
+ content_for :knockout_templates do
32
+ if partial
33
+ content_tag :script, type: "text/html", id: name do
34
+ render partial: partial
35
+ end
36
+ else
37
+ content_tag :script, type: "text/html", id: name do
38
+ capture(&block)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
28
44
  def overlay_template(name, partial: nil, &block)
29
45
  content_for :knockout_templates do
30
46
  if partial
@@ -1,3 +1,3 @@
1
1
  module Kea
2
- VERSION = "1.0.4"
2
+ VERSION = "1.0.5"
3
3
  end
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Knockout JavaScript library v3.2.0
2
+ * Knockout JavaScript library v3.3.0
3
3
  * (c) Steven Sanderson - http://knockoutjs.com/
4
4
  * License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
5
  */
@@ -16,18 +16,17 @@ var DEBUG=true;
16
16
  JSON = window["JSON"];
17
17
  (function(factory) {
18
18
  // Support three module loading scenarios
19
- if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
20
- // [1] CommonJS/Node.js
21
- var target = module['exports'] || exports; // module.exports is for Node.js
22
- factory(target, require);
23
- } else if (typeof define === 'function' && define['amd']) {
24
- // [2] AMD anonymous module
19
+ if (typeof define === 'function' && define['amd']) {
20
+ // [1] AMD anonymous module
25
21
  define(['exports', 'require'], factory);
22
+ } else if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
23
+ // [2] CommonJS/Node.js
24
+ factory(module['exports'] || exports); // module.exports is for Node.js
26
25
  } else {
27
26
  // [3] No module loader (plain <script> tag) - put directly in global namespace
28
27
  factory(window['ko'] = {});
29
28
  }
30
- }(function(koExports, require){
29
+ }(function(koExports, amdRequire){
31
30
  // Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
32
31
  // In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
33
32
  var ko = typeof koExports !== 'undefined' ? koExports : {};
@@ -46,7 +45,7 @@ ko.exportSymbol = function(koPath, object) {
46
45
  ko.exportProperty = function(owner, publicName, object) {
47
46
  owner[publicName] = object;
48
47
  };
49
- ko.version = "3.2.0";
48
+ ko.version = "3.3.0";
50
49
 
51
50
  ko.exportSymbol('version', ko.version);
52
51
  ko.utils = (function () {
@@ -113,6 +112,37 @@ ko.utils = (function () {
113
112
  return (inputType == "checkbox") || (inputType == "radio");
114
113
  }
115
114
 
115
+ // For details on the pattern for changing node classes
116
+ // see: https://github.com/knockout/knockout/issues/1597
117
+ var cssClassNameRegex = /\S+/g;
118
+
119
+ function toggleDomNodeCssClass(node, classNames, shouldHaveClass) {
120
+ var addOrRemoveFn;
121
+ if (classNames) {
122
+ if (typeof node.classList === 'object') {
123
+ addOrRemoveFn = node.classList[shouldHaveClass ? 'add' : 'remove'];
124
+ ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
125
+ addOrRemoveFn.call(node.classList, className);
126
+ });
127
+ } else if (typeof node.className['baseVal'] === 'string') {
128
+ // SVG tag .classNames is an SVGAnimatedString instance
129
+ toggleObjectClassPropertyString(node.className, 'baseVal', classNames, shouldHaveClass);
130
+ } else {
131
+ // node.className ought to be a string.
132
+ toggleObjectClassPropertyString(node, 'className', classNames, shouldHaveClass);
133
+ }
134
+ }
135
+ }
136
+
137
+ function toggleObjectClassPropertyString(obj, prop, classNames, shouldHaveClass) {
138
+ // obj/prop is either a node/'className' or a SVGAnimatedString/'baseVal'.
139
+ var currentClassNames = obj[prop].match(cssClassNameRegex) || [];
140
+ ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
141
+ ko.utils.addOrRemoveItem(currentClassNames, className, shouldHaveClass);
142
+ });
143
+ obj[prop] = currentClassNames.join(" ");
144
+ }
145
+
116
146
  return {
117
147
  fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
118
148
 
@@ -226,8 +256,9 @@ ko.utils = (function () {
226
256
  // Ensure it's a real array, as we're about to reparent the nodes and
227
257
  // we don't want the underlying collection to change while we're doing that.
228
258
  var nodesArray = ko.utils.makeArray(nodes);
259
+ var templateDocument = (nodesArray[0] && nodesArray[0].ownerDocument) || document;
229
260
 
230
- var container = document.createElement('div');
261
+ var container = templateDocument.createElement('div');
231
262
  for (var i = 0, j = nodesArray.length; i < j; i++) {
232
263
  container.appendChild(ko.cleanNode(nodesArray[i]));
233
264
  }
@@ -283,7 +314,7 @@ ko.utils = (function () {
283
314
 
284
315
  // Rule [A]
285
316
  while (continuousNodeArray.length && continuousNodeArray[0].parentNode !== parentNode)
286
- continuousNodeArray.shift();
317
+ continuousNodeArray.splice(0, 1);
287
318
 
288
319
  // Rule [B]
289
320
  if (continuousNodeArray.length > 1) {
@@ -412,16 +443,7 @@ ko.utils = (function () {
412
443
  return ko.isObservable(value) ? value.peek() : value;
413
444
  },
414
445
 
415
- toggleDomNodeCssClass: function (node, classNames, shouldHaveClass) {
416
- if (classNames) {
417
- var cssClassNameRegex = /\S+/g,
418
- currentClassNames = node.className.match(cssClassNameRegex) || [];
419
- ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
420
- ko.utils.addOrRemoveItem(currentClassNames, className, shouldHaveClass);
421
- });
422
- node.className = currentClassNames.join(" ");
423
- }
424
- },
446
+ toggleDomNodeCssClass: toggleDomNodeCssClass,
425
447
 
426
448
  setTextContent: function(element, textContent) {
427
449
  var value = ko.utils.unwrapObservable(textContent);
@@ -595,16 +617,26 @@ ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
595
617
  ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
596
618
  ko.exportSymbol('utils.objectForEach', ko.utils.objectForEach);
597
619
  ko.exportSymbol('utils.addOrRemoveItem', ko.utils.addOrRemoveItem);
620
+ ko.exportSymbol('utils.setTextContent', ko.utils.setTextContent);
598
621
  ko.exportSymbol('unwrap', ko.utils.unwrapObservable); // Convenient shorthand, because this is used so commonly
599
622
 
600
623
  if (!Function.prototype['bind']) {
601
624
  // Function.prototype.bind is a standard part of ECMAScript 5th Edition (December 2009, http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf)
602
625
  // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
603
626
  Function.prototype['bind'] = function (object) {
604
- var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift();
605
- return function () {
606
- return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
607
- };
627
+ var originalFunction = this;
628
+ if (arguments.length === 1) {
629
+ return function () {
630
+ return originalFunction.apply(object, arguments);
631
+ };
632
+ } else {
633
+ var partialArgs = Array.prototype.slice.call(arguments, 1);
634
+ return function () {
635
+ var args = partialArgs.slice(0);
636
+ args.push.apply(args, arguments);
637
+ return originalFunction.apply(object, args);
638
+ };
639
+ }
608
640
  };
609
641
  }
610
642
 
@@ -751,7 +783,7 @@ ko.utils.domNodeDisposal = new (function () {
751
783
  if (jQueryInstance && (typeof jQueryInstance['cleanData'] == "function"))
752
784
  jQueryInstance['cleanData']([node]);
753
785
  }
754
- }
786
+ };
755
787
  })();
756
788
  ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience
757
789
  ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience
@@ -763,7 +795,10 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
763
795
  (function () {
764
796
  var leadingCommentRegex = /^(\s*)<!--(.*?)-->/;
765
797
 
766
- function simpleHtmlParse(html) {
798
+ function simpleHtmlParse(html, documentContext) {
799
+ documentContext || (documentContext = document);
800
+ var windowContext = documentContext['parentWindow'] || documentContext['defaultView'] || window;
801
+
767
802
  // Based on jQuery's "clean" function, but only accounting for table-related elements.
768
803
  // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly
769
804
 
@@ -773,7 +808,7 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
773
808
  // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
774
809
 
775
810
  // Trim whitespace, otherwise indexOf won't work as expected
776
- var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div");
811
+ var tags = ko.utils.stringTrim(html).toLowerCase(), div = documentContext.createElement("div");
777
812
 
778
813
  // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column
779
814
  var wrap = tags.match(/^<(thead|tbody|tfoot)/) && [1, "<table>", "</table>"] ||
@@ -784,8 +819,8 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
784
819
  // Go to html and back, then peel off extra wrappers
785
820
  // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
786
821
  var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
787
- if (typeof window['innerShiv'] == "function") {
788
- div.appendChild(window['innerShiv'](markup));
822
+ if (typeof windowContext['innerShiv'] == "function") {
823
+ div.appendChild(windowContext['innerShiv'](markup));
789
824
  } else {
790
825
  div.innerHTML = markup;
791
826
  }
@@ -797,13 +832,13 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
797
832
  return ko.utils.makeArray(div.lastChild.childNodes);
798
833
  }
799
834
 
800
- function jQueryHtmlParse(html) {
835
+ function jQueryHtmlParse(html, documentContext) {
801
836
  // jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API.
802
837
  if (jQueryInstance['parseHTML']) {
803
- return jQueryInstance['parseHTML'](html) || []; // Ensure we always return an array and never null
838
+ return jQueryInstance['parseHTML'](html, documentContext) || []; // Ensure we always return an array and never null
804
839
  } else {
805
840
  // For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function.
806
- var elems = jQueryInstance['clean']([html]);
841
+ var elems = jQueryInstance['clean']([html], documentContext);
807
842
 
808
843
  // 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.
809
844
  // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
@@ -822,9 +857,9 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
822
857
  }
823
858
  }
824
859
 
825
- ko.utils.parseHtmlFragment = function(html) {
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.
860
+ ko.utils.parseHtmlFragment = function(html, documentContext) {
861
+ return jQueryInstance ? jQueryHtmlParse(html, documentContext) // As below, benefit from jQuery's optimisations where possible
862
+ : simpleHtmlParse(html, documentContext); // ... otherwise, this simple logic will do in most common cases.
828
863
  };
829
864
 
830
865
  ko.utils.setHtml = function(node, html) {
@@ -844,7 +879,7 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
844
879
  jQueryInstance(node)['html'](html);
845
880
  } else {
846
881
  // ... otherwise, use KO's own parsing logic.
847
- var parsedNodes = ko.utils.parseHtmlFragment(html);
882
+ var parsedNodes = ko.utils.parseHtmlFragment(html, node.ownerDocument);
848
883
  for (var i = 0; i < parsedNodes.length; i++)
849
884
  node.appendChild(parsedNodes[i]);
850
885
  }
@@ -1011,7 +1046,7 @@ function applyExtenders(requestedExtenders) {
1011
1046
  ko.exportSymbol('extenders', ko.extenders);
1012
1047
 
1013
1048
  ko.subscription = function (target, callback, disposeCallback) {
1014
- this.target = target;
1049
+ this._target = target;
1015
1050
  this.callback = callback;
1016
1051
  this.disposeCallback = disposeCallback;
1017
1052
  this.isDisposed = false;
@@ -1025,6 +1060,7 @@ ko.subscription.prototype.dispose = function () {
1025
1060
  ko.subscribable = function () {
1026
1061
  ko.utils.setPrototypeOfOrExtend(this, ko.subscribable['fn']);
1027
1062
  this._subscriptions = {};
1063
+ this._versionNumber = 1;
1028
1064
  }
1029
1065
 
1030
1066
  var defaultEvent = "change";
@@ -1054,6 +1090,9 @@ var ko_subscribable_fn = {
1054
1090
 
1055
1091
  "notifySubscribers": function (valueToNotify, event) {
1056
1092
  event = event || defaultEvent;
1093
+ if (event === defaultEvent) {
1094
+ this.updateVersion();
1095
+ }
1057
1096
  if (this.hasSubscriptionsForEvent(event)) {
1058
1097
  try {
1059
1098
  ko.dependencyDetection.begin(); // Begin suppressing dependency detection (by setting the top frame to undefined)
@@ -1069,6 +1108,18 @@ var ko_subscribable_fn = {
1069
1108
  }
1070
1109
  },
1071
1110
 
1111
+ getVersion: function () {
1112
+ return this._versionNumber;
1113
+ },
1114
+
1115
+ hasChanged: function (versionToCheck) {
1116
+ return this.getVersion() !== versionToCheck;
1117
+ },
1118
+
1119
+ updateVersion: function () {
1120
+ ++this._versionNumber;
1121
+ },
1122
+
1072
1123
  limit: function(limitFunction) {
1073
1124
  var self = this, selfIsObservable = ko.isObservable(self),
1074
1125
  isPending, previousValue, pendingValue, beforeChange = 'beforeChange';
@@ -1115,12 +1166,16 @@ var ko_subscribable_fn = {
1115
1166
  return this._subscriptions[event] && this._subscriptions[event].length;
1116
1167
  },
1117
1168
 
1118
- getSubscriptionsCount: function () {
1119
- var total = 0;
1120
- ko.utils.objectForEach(this._subscriptions, function(eventName, subscriptions) {
1121
- total += subscriptions.length;
1122
- });
1123
- return total;
1169
+ getSubscriptionsCount: function (event) {
1170
+ if (event) {
1171
+ return this._subscriptions[event] && this._subscriptions[event].length || 0;
1172
+ } else {
1173
+ var total = 0;
1174
+ ko.utils.objectForEach(this._subscriptions, function(eventName, subscriptions) {
1175
+ total += subscriptions.length;
1176
+ });
1177
+ return total;
1178
+ }
1124
1179
  },
1125
1180
 
1126
1181
  isDifferent: function(oldValue, newValue) {
@@ -1213,6 +1268,8 @@ ko.exportSymbol('computedContext', ko.computedContext);
1213
1268
  ko.exportSymbol('computedContext.getDependenciesCount', ko.computedContext.getDependenciesCount);
1214
1269
  ko.exportSymbol('computedContext.isInitial', ko.computedContext.isInitial);
1215
1270
  ko.exportSymbol('computedContext.isSleeping', ko.computedContext.isSleeping);
1271
+
1272
+ ko.exportSymbol('ignoreDependencies', ko.ignoreDependencies = ko.dependencyDetection.ignore);
1216
1273
  ko.observable = function (initialValue) {
1217
1274
  var _latestValue = initialValue;
1218
1275
 
@@ -1418,15 +1475,27 @@ ko.extenders['trackArrayChanges'] = function(target) {
1418
1475
  }
1419
1476
  var trackingChanges = false,
1420
1477
  cachedDiff = null,
1478
+ arrayChangeSubscription,
1421
1479
  pendingNotifications = 0,
1422
- underlyingSubscribeFunction = target.subscribe;
1480
+ underlyingBeforeSubscriptionAddFunction = target.beforeSubscriptionAdd,
1481
+ underlyingAfterSubscriptionRemoveFunction = target.afterSubscriptionRemove;
1423
1482
 
1424
- // Intercept "subscribe" calls, and for array change events, ensure change tracking is enabled
1425
- target.subscribe = target['subscribe'] = function(callback, callbackTarget, event) {
1483
+ // Watch "subscribe" calls, and for array change events, ensure change tracking is enabled
1484
+ target.beforeSubscriptionAdd = function (event) {
1485
+ if (underlyingBeforeSubscriptionAddFunction)
1486
+ underlyingBeforeSubscriptionAddFunction.call(target, event);
1426
1487
  if (event === arrayChangeEventName) {
1427
1488
  trackChanges();
1428
1489
  }
1429
- return underlyingSubscribeFunction.apply(this, arguments);
1490
+ };
1491
+ // Watch "dispose" calls, and for array change events, ensure change tracking is disabled when all are disposed
1492
+ target.afterSubscriptionRemove = function (event) {
1493
+ if (underlyingAfterSubscriptionRemoveFunction)
1494
+ underlyingAfterSubscriptionRemoveFunction.call(target, event);
1495
+ if (event === arrayChangeEventName && !target.hasSubscriptionsForEvent(arrayChangeEventName)) {
1496
+ arrayChangeSubscription.dispose();
1497
+ trackingChanges = false;
1498
+ }
1430
1499
  };
1431
1500
 
1432
1501
  function trackChanges() {
@@ -1450,22 +1519,23 @@ ko.extenders['trackArrayChanges'] = function(target) {
1450
1519
  // change it's possible to produce a diff
1451
1520
  var previousContents = [].concat(target.peek() || []);
1452
1521
  cachedDiff = null;
1453
- target.subscribe(function(currentContents) {
1522
+ arrayChangeSubscription = target.subscribe(function(currentContents) {
1454
1523
  // Make a copy of the current contents and ensure it's an array
1455
1524
  currentContents = [].concat(currentContents || []);
1456
1525
 
1457
1526
  // Compute the diff and issue notifications, but only if someone is listening
1458
1527
  if (target.hasSubscriptionsForEvent(arrayChangeEventName)) {
1459
1528
  var changes = getChanges(previousContents, currentContents);
1460
- if (changes.length) {
1461
- target['notifySubscribers'](changes, arrayChangeEventName);
1462
- }
1463
1529
  }
1464
1530
 
1465
1531
  // Eliminate references to the old, removed items, so they can be GCed
1466
1532
  previousContents = currentContents;
1467
1533
  cachedDiff = null;
1468
1534
  pendingNotifications = 0;
1535
+
1536
+ if (changes && changes.length) {
1537
+ target['notifySubscribers'](changes, arrayChangeEventName);
1538
+ }
1469
1539
  });
1470
1540
  }
1471
1541
 
@@ -1558,44 +1628,58 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1558
1628
  if (typeof readFunction != "function")
1559
1629
  throw new Error("Pass a function that returns the value of the ko.computed");
1560
1630
 
1561
- function addSubscriptionToDependency(subscribable, id) {
1562
- if (!_subscriptionsToDependencies[id]) {
1563
- _subscriptionsToDependencies[id] = subscribable.subscribe(evaluatePossiblyAsync);
1564
- ++_dependenciesCount;
1631
+ function addDependencyTracking(id, target, trackingObj) {
1632
+ if (pure && target === dependentObservable) {
1633
+ throw Error("A 'pure' computed must not be called recursively");
1565
1634
  }
1635
+
1636
+ dependencyTracking[id] = trackingObj;
1637
+ trackingObj._order = _dependenciesCount++;
1638
+ trackingObj._version = target.getVersion();
1566
1639
  }
1567
1640
 
1568
- function disposeAllSubscriptionsToDependencies() {
1569
- ko.utils.objectForEach(_subscriptionsToDependencies, function (id, subscription) {
1570
- subscription.dispose();
1571
- });
1572
- _subscriptionsToDependencies = {};
1641
+ function haveDependenciesChanged() {
1642
+ var id, dependency;
1643
+ for (id in dependencyTracking) {
1644
+ if (dependencyTracking.hasOwnProperty(id)) {
1645
+ dependency = dependencyTracking[id];
1646
+ if (dependency._target.hasChanged(dependency._version)) {
1647
+ return true;
1648
+ }
1649
+ }
1650
+ }
1573
1651
  }
1574
1652
 
1575
1653
  function disposeComputed() {
1576
- disposeAllSubscriptionsToDependencies();
1654
+ if (!isSleeping && dependencyTracking) {
1655
+ ko.utils.objectForEach(dependencyTracking, function (id, dependency) {
1656
+ if (dependency.dispose)
1657
+ dependency.dispose();
1658
+ });
1659
+ }
1660
+ dependencyTracking = null;
1577
1661
  _dependenciesCount = 0;
1578
1662
  _isDisposed = true;
1579
1663
  _needsEvaluation = false;
1664
+ isSleeping = false;
1580
1665
  }
1581
1666
 
1582
1667
  function evaluatePossiblyAsync() {
1583
1668
  var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
1584
1669
  if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
1585
1670
  clearTimeout(evaluationTimeoutInstance);
1586
- evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
1671
+ evaluationTimeoutInstance = setTimeout(function () {
1672
+ evaluateImmediate(true /*notifyChange*/);
1673
+ }, throttleEvaluationTimeout);
1587
1674
  } else if (dependentObservable._evalRateLimited) {
1588
1675
  dependentObservable._evalRateLimited();
1589
1676
  } else {
1590
- evaluateImmediate();
1677
+ evaluateImmediate(true /*notifyChange*/);
1591
1678
  }
1592
1679
  }
1593
1680
 
1594
- function evaluateImmediate(suppressChangeNotification) {
1681
+ function evaluateImmediate(notifyChange) {
1595
1682
  if (_isBeingEvaluated) {
1596
- if (pure) {
1597
- throw Error("A 'pure' computed must not be called recursively");
1598
- }
1599
1683
  // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
1600
1684
  // This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
1601
1685
  // certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
@@ -1621,82 +1705,71 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1621
1705
 
1622
1706
  _isBeingEvaluated = true;
1623
1707
 
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;
1632
- ++_dependenciesCount;
1708
+ try {
1709
+ // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
1710
+ // Then, during evaluation, we cross off any that are in fact still being used.
1711
+ var disposalCandidates = dependencyTracking,
1712
+ disposalCount = _dependenciesCount,
1713
+ isInitial = pure ? undefined : !_dependenciesCount; // If we're evaluating when there are no previous dependencies, it must be the first time
1714
+
1715
+ ko.dependencyDetection.begin({
1716
+ callback: function(subscribable, id) {
1717
+ if (!_isDisposed) {
1718
+ if (disposalCount && disposalCandidates[id]) {
1719
+ // Don't want to dispose this subscription, as it's still being used
1720
+ addDependencyTracking(id, subscribable, disposalCandidates[id]);
1721
+ delete disposalCandidates[id];
1722
+ --disposalCount;
1723
+ } else if (!dependencyTracking[id]) {
1724
+ // Brand new subscription - add it
1725
+ addDependencyTracking(id, subscribable, isSleeping ? { _target: subscribable } : subscribable.subscribe(evaluatePossiblyAsync));
1633
1726
  }
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
- });
1727
+ }
1728
+ },
1729
+ computed: dependentObservable,
1730
+ isInitial: isInitial
1731
+ });
1667
1732
 
1668
- _subscriptionsToDependencies = {};
1669
- _dependenciesCount = 0;
1733
+ dependencyTracking = {};
1734
+ _dependenciesCount = 0;
1670
1735
 
1671
- try {
1672
- var newValue = evaluatorFunctionTarget ? readFunction.call(evaluatorFunctionTarget) : readFunction();
1736
+ try {
1737
+ var newValue = evaluatorFunctionTarget ? readFunction.call(evaluatorFunctionTarget) : readFunction();
1673
1738
 
1674
- } finally {
1675
- ko.dependencyDetection.end();
1739
+ } finally {
1740
+ ko.dependencyDetection.end();
1676
1741
 
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) {
1742
+ // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
1743
+ if (disposalCount && !isSleeping) {
1744
+ ko.utils.objectForEach(disposalCandidates, function(id, toDispose) {
1745
+ if (toDispose.dispose)
1680
1746
  toDispose.dispose();
1681
- });
1682
- }
1683
-
1684
- _needsEvaluation = false;
1747
+ });
1685
1748
  }
1686
1749
 
1687
- if (dependentObservable.isDifferent(_latestValue, newValue)) {
1688
- dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
1750
+ _needsEvaluation = false;
1751
+ }
1689
1752
 
1690
- _latestValue = newValue;
1691
- if (DEBUG) dependentObservable._latestValue = _latestValue;
1753
+ if (dependentObservable.isDifferent(_latestValue, newValue)) {
1754
+ if (!isSleeping) {
1755
+ notify(_latestValue, "beforeChange");
1756
+ }
1692
1757
 
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
- }
1758
+ _latestValue = newValue;
1759
+ if (DEBUG) dependentObservable._latestValue = _latestValue;
1760
+
1761
+ if (isSleeping) {
1762
+ dependentObservable.updateVersion();
1763
+ } else if (notifyChange) {
1764
+ notify(_latestValue);
1696
1765
  }
1697
- } finally {
1698
- _isBeingEvaluated = false;
1699
1766
  }
1767
+
1768
+ if (isInitial) {
1769
+ notify(_latestValue, "awake");
1770
+ }
1771
+ } finally {
1772
+ _isBeingEvaluated = false;
1700
1773
  }
1701
1774
 
1702
1775
  if (!_dependenciesCount)
@@ -1715,17 +1788,18 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1715
1788
  } else {
1716
1789
  // Reading the value
1717
1790
  ko.dependencyDetection.registerDependency(dependentObservable);
1718
- if (_needsEvaluation)
1719
- evaluateImmediate(true /* suppressChangeNotification */);
1791
+ if (_needsEvaluation || (isSleeping && haveDependenciesChanged())) {
1792
+ evaluateImmediate();
1793
+ }
1720
1794
  return _latestValue;
1721
1795
  }
1722
1796
  }
1723
1797
 
1724
1798
  function peek() {
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.
1727
- if (_needsEvaluation && !_dependenciesCount)
1728
- evaluateImmediate(true /* suppressChangeNotification */);
1799
+ // Peek won't re-evaluate, except while the computed is sleeping or to get the initial value when "deferEvaluation" is set.
1800
+ if ((_needsEvaluation && !_dependenciesCount) || (isSleeping && haveDependenciesChanged())) {
1801
+ evaluateImmediate();
1802
+ }
1729
1803
  return _latestValue;
1730
1804
  }
1731
1805
 
@@ -1733,13 +1807,17 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1733
1807
  return _needsEvaluation || _dependenciesCount > 0;
1734
1808
  }
1735
1809
 
1810
+ function notify(value, event) {
1811
+ dependentObservable["notifySubscribers"](value, event);
1812
+ }
1813
+
1736
1814
  // By here, "options" is always non-null
1737
1815
  var writeFunction = options["write"],
1738
1816
  disposeWhenNodeIsRemoved = options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
1739
1817
  disposeWhenOption = options["disposeWhen"] || options.disposeWhen,
1740
1818
  disposeWhen = disposeWhenOption,
1741
1819
  dispose = disposeComputed,
1742
- _subscriptionsToDependencies = {},
1820
+ dependencyTracking = {},
1743
1821
  _dependenciesCount = 0,
1744
1822
  evaluationTimeoutInstance = null;
1745
1823
 
@@ -1751,7 +1829,7 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1751
1829
 
1752
1830
  dependentObservable.peek = peek;
1753
1831
  dependentObservable.getDependenciesCount = function () { return _dependenciesCount; };
1754
- dependentObservable.hasWriteFunction = typeof options["write"] === "function";
1832
+ dependentObservable.hasWriteFunction = typeof writeFunction === "function";
1755
1833
  dependentObservable.dispose = function () { dispose(); };
1756
1834
  dependentObservable.isActive = isActive;
1757
1835
 
@@ -1773,24 +1851,69 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1773
1851
  if (options['pure']) {
1774
1852
  pure = true;
1775
1853
  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) {
1854
+ dependentObservable.beforeSubscriptionAdd = function (event) {
1855
+ // If asleep, wake up the computed by subscribing to any dependencies.
1856
+ if (!_isDisposed && isSleeping && event == 'change') {
1779
1857
  isSleeping = false;
1780
- evaluateImmediate(true /* suppressChangeNotification */);
1858
+ if (_needsEvaluation || haveDependenciesChanged()) {
1859
+ dependencyTracking = null;
1860
+ _dependenciesCount = 0;
1861
+ _needsEvaluation = true;
1862
+ evaluateImmediate();
1863
+ } else {
1864
+ // First put the dependencies in order
1865
+ var dependeciesOrder = [];
1866
+ ko.utils.objectForEach(dependencyTracking, function (id, dependency) {
1867
+ dependeciesOrder[dependency._order] = id;
1868
+ });
1869
+ // Next, subscribe to each one
1870
+ ko.utils.arrayForEach(dependeciesOrder, function(id, order) {
1871
+ var dependency = dependencyTracking[id],
1872
+ subscription = dependency._target.subscribe(evaluatePossiblyAsync);
1873
+ subscription._order = order;
1874
+ subscription._version = dependency._version;
1875
+ dependencyTracking[id] = subscription;
1876
+ });
1877
+ }
1878
+ if (!_isDisposed) { // test since evaluating could trigger disposal
1879
+ notify(_latestValue, "awake");
1880
+ }
1781
1881
  }
1782
- }
1783
- dependentObservable.afterSubscriptionRemove = function () {
1784
- if (!dependentObservable.getSubscriptionsCount()) {
1785
- disposeAllSubscriptionsToDependencies();
1786
- isSleeping = _needsEvaluation = true;
1882
+ };
1883
+
1884
+ dependentObservable.afterSubscriptionRemove = function (event) {
1885
+ if (!_isDisposed && event == 'change' && !dependentObservable.hasSubscriptionsForEvent('change')) {
1886
+ ko.utils.objectForEach(dependencyTracking, function (id, dependency) {
1887
+ if (dependency.dispose) {
1888
+ dependencyTracking[id] = {
1889
+ _target: dependency._target,
1890
+ _order: dependency._order,
1891
+ _version: dependency._version
1892
+ };
1893
+ dependency.dispose();
1894
+ }
1895
+ });
1896
+ isSleeping = true;
1897
+ notify(undefined, "asleep");
1787
1898
  }
1788
- }
1899
+ };
1900
+
1901
+ // Because a pure computed is not automatically updated while it is sleeping, we can't
1902
+ // simply return the version number. Instead, we check if any of the dependencies have
1903
+ // changed and conditionally re-evaluate the computed observable.
1904
+ dependentObservable._originalGetVersion = dependentObservable.getVersion;
1905
+ dependentObservable.getVersion = function () {
1906
+ if (isSleeping && (_needsEvaluation || haveDependenciesChanged())) {
1907
+ evaluateImmediate();
1908
+ }
1909
+ return dependentObservable._originalGetVersion();
1910
+ };
1789
1911
  } else if (options['deferEvaluation']) {
1790
1912
  // 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;
1913
+ dependentObservable.beforeSubscriptionAdd = function (event) {
1914
+ if (event == 'change' || event == 'beforeChange') {
1915
+ peek();
1916
+ }
1794
1917
  }
1795
1918
  }
1796
1919
 
@@ -2085,7 +2208,7 @@ ko.expressionRewriting = (function () {
2085
2208
  if (str.charCodeAt(0) === 123) str = str.slice(1, -1);
2086
2209
 
2087
2210
  // Split into tokens
2088
- var result = [], toks = str.match(bindingToken), key, values, depth = 0;
2211
+ var result = [], toks = str.match(bindingToken), key, values = [], depth = 0;
2089
2212
 
2090
2213
  if (toks) {
2091
2214
  // Append a comma so that we don't need a separate code block to deal with the last item
@@ -2096,15 +2219,17 @@ ko.expressionRewriting = (function () {
2096
2219
  // A comma signals the end of a key/value pair if depth is zero
2097
2220
  if (c === 44) { // ","
2098
2221
  if (depth <= 0) {
2099
- if (key)
2100
- result.push(values ? {key: key, value: values.join('')} : {'unknown': key});
2101
- key = values = depth = 0;
2222
+ result.push((key && values.length) ? {key: key, value: values.join('')} : {'unknown': key || values.join('')});
2223
+ key = depth = 0;
2224
+ values = [];
2102
2225
  continue;
2103
2226
  }
2104
2227
  // Simply skip the colon that separates the name and value
2105
2228
  } else if (c === 58) { // ":"
2106
- if (!values)
2229
+ if (!depth && !key && values.length === 1) {
2230
+ key = values.pop();
2107
2231
  continue;
2232
+ }
2108
2233
  // A set of slashes is initially matched as a regular expression, but could be division
2109
2234
  } else if (c === 47 && i && tok.length > 1) { // "/"
2110
2235
  // Look at the end of the previous token to determine if the slash is actually division
@@ -2123,15 +2248,11 @@ ko.expressionRewriting = (function () {
2123
2248
  ++depth;
2124
2249
  } else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']'
2125
2250
  --depth;
2126
- // The key must be a single token; if it's a string, trim the quotes
2127
- } else if (!key && !values) {
2128
- key = (c === 34 || c === 39) /* '"', "'" */ ? tok.slice(1, -1) : tok;
2129
- continue;
2251
+ // The key will be the first token; if it's a string, trim the quotes
2252
+ } else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'"
2253
+ tok = tok.slice(1, -1);
2130
2254
  }
2131
- if (values)
2132
- values.push(tok);
2133
- else
2134
- values = [tok];
2255
+ values.push(tok);
2135
2256
  }
2136
2257
  }
2137
2258
  return result;
@@ -2512,9 +2633,10 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
2512
2633
  // may consider adding <template> to this list, because such elements' contents are always
2513
2634
  // intended to be bound in a different context from where they appear in the document.
2514
2635
  var bindingDoesNotRecurseIntoElementTypes = {
2515
- // Don't want bindings that operate on text nodes to mutate <script> contents,
2636
+ // Don't want bindings that operate on text nodes to mutate <script> and <textarea> contents,
2516
2637
  // because it's unexpected and a potential XSS issue
2517
- 'script': true
2638
+ 'script': true,
2639
+ 'textarea': true
2518
2640
  };
2519
2641
 
2520
2642
  // Use an overridable method for retrieving binding handlers so that a plugins may support dynamically created handlers
@@ -2978,8 +3100,15 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
2978
3100
  var cachedDefinition = getObjectOwnProperty(loadedDefinitionsCache, componentName);
2979
3101
  if (cachedDefinition) {
2980
3102
  // 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);
3103
+ // Note that for API consistency, even cache hits complete asynchronously by default.
3104
+ // You can bypass this by putting synchronous:true on your component config.
3105
+ if (cachedDefinition.isSynchronousComponent) {
3106
+ ko.dependencyDetection.ignore(function() { // See comment in loaderRegistryBehaviors.js for reasoning
3107
+ callback(cachedDefinition.definition);
3108
+ });
3109
+ } else {
3110
+ setTimeout(function() { callback(cachedDefinition.definition); }, 0);
3111
+ }
2983
3112
  } else {
2984
3113
  // Join the loading process that is already underway, or start a new one.
2985
3114
  loadComponentAndNotify(componentName, callback);
@@ -3003,14 +3132,22 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3003
3132
  if (!subscribable) {
3004
3133
  // It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache.
3005
3134
  subscribable = loadingSubscribablesCache[componentName] = new ko.subscribable();
3006
- beginLoadingComponent(componentName, function(definition) {
3007
- loadedDefinitionsCache[componentName] = definition;
3135
+ subscribable.subscribe(callback);
3136
+
3137
+ beginLoadingComponent(componentName, function(definition, config) {
3138
+ var isSynchronousComponent = !!(config && config['synchronous']);
3139
+ loadedDefinitionsCache[componentName] = { definition: definition, isSynchronousComponent: isSynchronousComponent };
3008
3140
  delete loadingSubscribablesCache[componentName];
3009
3141
 
3010
3142
  // For API consistency, all loads complete asynchronously. However we want to avoid
3011
3143
  // adding an extra setTimeout if it's unnecessary (i.e., the completion is already
3012
3144
  // async) since setTimeout(..., 0) still takes about 16ms or more on most browsers.
3013
- if (completedAsync) {
3145
+ //
3146
+ // You can bypass the 'always synchronous' feature by putting the synchronous:true
3147
+ // flag on your component configuration when you register it.
3148
+ if (completedAsync || isSynchronousComponent) {
3149
+ // Note that notifySubscribers ignores any dependencies read within the callback.
3150
+ // See comment in loaderRegistryBehaviors.js for reasoning
3014
3151
  subscribable['notifySubscribers'](definition);
3015
3152
  } else {
3016
3153
  setTimeout(function() {
@@ -3019,8 +3156,9 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3019
3156
  }
3020
3157
  });
3021
3158
  completedAsync = true;
3159
+ } else {
3160
+ subscribable.subscribe(callback);
3022
3161
  }
3023
- subscribable.subscribe(callback);
3024
3162
  }
3025
3163
 
3026
3164
  function beginLoadingComponent(componentName, callback) {
@@ -3028,14 +3166,14 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3028
3166
  if (config) {
3029
3167
  // We have a config, so now load its definition
3030
3168
  getFirstResultFromLoaders('loadComponent', [componentName, config], function(definition) {
3031
- callback(definition);
3169
+ callback(definition, config);
3032
3170
  });
3033
3171
  } else {
3034
3172
  // The component has no config - it's unknown to all the loaders.
3035
3173
  // Note that this is not an error (e.g., a module loading error) - that would abort the
3036
3174
  // process and this callback would not run. For this callback to run, all loaders must
3037
3175
  // have confirmed they don't know about this component.
3038
- callback(null);
3176
+ callback(null, null);
3039
3177
  }
3040
3178
  });
3041
3179
  }
@@ -3291,8 +3429,8 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3291
3429
  function possiblyGetConfigFromAmd(errorCallback, config, callback) {
3292
3430
  if (typeof config['require'] === 'string') {
3293
3431
  // The config is the value of an AMD module
3294
- if (require || window['require']) {
3295
- (require || window['require'])([config['require']], callback);
3432
+ if (amdRequire || window['require']) {
3433
+ (amdRequire || window['require'])([config['require']], callback);
3296
3434
  } else {
3297
3435
  errorCallback('Uses require, but no AMD loader is present');
3298
3436
  }
@@ -3364,18 +3502,26 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3364
3502
  return ko.computed(paramValue, null, { disposeWhenNodeIsRemoved: elem });
3365
3503
  }),
3366
3504
  result = ko.utils.objectMap(rawParamComputedValues, function(paramValueComputed, paramName) {
3505
+ var paramValue = paramValueComputed.peek();
3367
3506
  // Does the evaluation of the parameter value unwrap any observables?
3368
3507
  if (!paramValueComputed.isActive()) {
3369
3508
  // No it doesn't, so there's no need for any computed wrapper. Just pass through the supplied value directly.
3370
3509
  // Example: "someVal: firstName, age: 123" (whether or not firstName is an observable/computed)
3371
- return paramValueComputed.peek();
3510
+ return paramValue;
3372
3511
  } else {
3373
3512
  // Yes it does. Supply a computed property that unwraps both the outer (binding expression)
3374
3513
  // 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 });
3514
+ // This means the component doesn't have to worry about multiple unwrapping. If the value is a
3515
+ // writable observable, the computed will also be writable and pass the value on to the observable.
3516
+ return ko.computed({
3517
+ 'read': function() {
3518
+ return ko.utils.unwrapObservable(paramValueComputed());
3519
+ },
3520
+ 'write': ko.isWriteableObservable(paramValue) && function(value) {
3521
+ paramValueComputed()(value);
3522
+ },
3523
+ disposeWhenNodeIsRemoved: elem
3524
+ });
3379
3525
  }
3380
3526
  });
3381
3527
 
@@ -3438,7 +3584,8 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3438
3584
 
3439
3585
  // Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
3440
3586
  currentLoadingOperationId = null;
3441
- };
3587
+ },
3588
+ originalChildNodes = ko.utils.makeArray(ko.virtualElements.childNodes(element));
3442
3589
 
3443
3590
  ko.utils.domNodeDisposal.addDisposeCallback(element, disposeAssociatedComponentViewModel);
3444
3591
 
@@ -3472,8 +3619,11 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3472
3619
  throw new Error('Unknown component \'' + componentName + '\'');
3473
3620
  }
3474
3621
  cloneTemplateIntoElement(componentName, componentDefinition, element);
3475
- var componentViewModel = createViewModel(componentDefinition, element, componentParams),
3476
- childBindingContext = bindingContext['createChildContext'](componentViewModel);
3622
+ var componentViewModel = createViewModel(componentDefinition, element, originalChildNodes, componentParams),
3623
+ childBindingContext = bindingContext['createChildContext'](componentViewModel, /* dataItemAlias */ undefined, function(ctx) {
3624
+ ctx['$component'] = componentViewModel;
3625
+ ctx['$componentTemplateNodes'] = originalChildNodes;
3626
+ });
3477
3627
  currentViewModel = componentViewModel;
3478
3628
  ko.applyBindingsToDescendants(childBindingContext, element);
3479
3629
  });
@@ -3495,10 +3645,10 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3495
3645
  ko.virtualElements.setDomNodeChildren(element, clonedNodesArray);
3496
3646
  }
3497
3647
 
3498
- function createViewModel(componentDefinition, element, componentParams) {
3648
+ function createViewModel(componentDefinition, element, originalChildNodes, componentParams) {
3499
3649
  var componentViewModelFactory = componentDefinition['createViewModel'];
3500
3650
  return componentViewModelFactory
3501
- ? componentViewModelFactory.call(componentDefinition, componentParams, { element: element })
3651
+ ? componentViewModelFactory.call(componentDefinition, componentParams, { 'element': element, 'templateNodes': originalChildNodes })
3502
3652
  : componentParams; // Template-only component
3503
3653
  }
3504
3654
 
@@ -3651,7 +3801,7 @@ ko.bindingHandlers['checkedValue'] = {
3651
3801
  ko.bindingHandlers['css'] = {
3652
3802
  'update': function (element, valueAccessor) {
3653
3803
  var value = ko.utils.unwrapObservable(valueAccessor());
3654
- if (typeof value == "object") {
3804
+ if (value !== null && typeof value == "object") {
3655
3805
  ko.utils.objectForEach(value, function(className, shouldHaveClass) {
3656
3806
  shouldHaveClass = ko.utils.unwrapObservable(shouldHaveClass);
3657
3807
  ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
@@ -3893,19 +4043,23 @@ ko.bindingHandlers['options'] = {
3893
4043
  return ko.utils.arrayFilter(element.options, function (node) { return node.selected; });
3894
4044
  }
3895
4045
 
3896
- var selectWasPreviouslyEmpty = element.length == 0;
3897
- var previousScrollTop = (!selectWasPreviouslyEmpty && element.multiple) ? element.scrollTop : null;
3898
- var unwrappedArray = ko.utils.unwrapObservable(valueAccessor());
3899
- var includeDestroyed = allBindings.get('optionsIncludeDestroyed');
3900
- var arrayToDomNodeChildrenOptions = {};
3901
- var captionValue;
3902
- var filteredArray;
3903
- var previousSelectedValues;
3904
-
3905
- if (element.multiple) {
3906
- previousSelectedValues = ko.utils.arrayMap(selectedOptions(), ko.selectExtensions.readValue);
3907
- } else {
3908
- previousSelectedValues = element.selectedIndex >= 0 ? [ ko.selectExtensions.readValue(element.options[element.selectedIndex]) ] : [];
4046
+ var selectWasPreviouslyEmpty = element.length == 0,
4047
+ multiple = element.multiple,
4048
+ previousScrollTop = (!selectWasPreviouslyEmpty && multiple) ? element.scrollTop : null,
4049
+ unwrappedArray = ko.utils.unwrapObservable(valueAccessor()),
4050
+ valueAllowUnset = allBindings.get('valueAllowUnset') && allBindings['has']('value'),
4051
+ includeDestroyed = allBindings.get('optionsIncludeDestroyed'),
4052
+ arrayToDomNodeChildrenOptions = {},
4053
+ captionValue,
4054
+ filteredArray,
4055
+ previousSelectedValues = [];
4056
+
4057
+ if (!valueAllowUnset) {
4058
+ if (multiple) {
4059
+ previousSelectedValues = ko.utils.arrayMap(selectedOptions(), ko.selectExtensions.readValue);
4060
+ } else if (element.selectedIndex >= 0) {
4061
+ previousSelectedValues.push(ko.selectExtensions.readValue(element.options[element.selectedIndex]));
4062
+ }
3909
4063
  }
3910
4064
 
3911
4065
  if (unwrappedArray) {
@@ -3946,7 +4100,7 @@ ko.bindingHandlers['options'] = {
3946
4100
  var itemUpdate = false;
3947
4101
  function optionForArrayItem(arrayEntry, index, oldOptions) {
3948
4102
  if (oldOptions.length) {
3949
- previousSelectedValues = oldOptions[0].selected ? [ ko.selectExtensions.readValue(oldOptions[0]) ] : [];
4103
+ previousSelectedValues = !valueAllowUnset && oldOptions[0].selected ? [ ko.selectExtensions.readValue(oldOptions[0]) ] : [];
3950
4104
  itemUpdate = true;
3951
4105
  }
3952
4106
  var option = element.ownerDocument.createElement("option");
@@ -3973,20 +4127,25 @@ ko.bindingHandlers['options'] = {
3973
4127
  };
3974
4128
 
3975
4129
  function setSelectionCallback(arrayEntry, newOptions) {
3976
- // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
3977
- // That's why we first added them without selection. Now it's time to set the selection.
3978
- if (previousSelectedValues.length) {
4130
+ if (itemUpdate && valueAllowUnset) {
4131
+ // The model value is authoritative, so make sure its value is the one selected
4132
+ // There is no need to use dependencyDetection.ignore since setDomNodeChildrenFromArrayMapping does so already.
4133
+ ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value')), true /* allowUnset */);
4134
+ } else if (previousSelectedValues.length) {
4135
+ // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
4136
+ // That's why we first added them without selection. Now it's time to set the selection.
3979
4137
  var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[0])) >= 0;
3980
4138
  ko.utils.setOptionNodeSelectionState(newOptions[0], isSelected);
3981
4139
 
3982
4140
  // If this option was changed from being selected during a single-item update, notify the change
3983
- if (itemUpdate && !isSelected)
4141
+ if (itemUpdate && !isSelected) {
3984
4142
  ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
4143
+ }
3985
4144
  }
3986
4145
  }
3987
4146
 
3988
4147
  var callback = setSelectionCallback;
3989
- if (allBindings['has']('optionsAfterRender')) {
4148
+ if (allBindings['has']('optionsAfterRender') && typeof allBindings.get('optionsAfterRender') == "function") {
3990
4149
  callback = function(arrayEntry, newOptions) {
3991
4150
  setSelectionCallback(arrayEntry, newOptions);
3992
4151
  ko.dependencyDetection.ignore(allBindings.get('optionsAfterRender'), null, [newOptions[0], arrayEntry !== captionPlaceholder ? arrayEntry : undefined]);
@@ -3996,13 +4155,13 @@ ko.bindingHandlers['options'] = {
3996
4155
  ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, arrayToDomNodeChildrenOptions, callback);
3997
4156
 
3998
4157
  ko.dependencyDetection.ignore(function () {
3999
- if (allBindings.get('valueAllowUnset') && allBindings['has']('value')) {
4158
+ if (valueAllowUnset) {
4000
4159
  // The model value is authoritative, so make sure its value is the one selected
4001
4160
  ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value')), true /* allowUnset */);
4002
4161
  } else {
4003
4162
  // Determine if the selection has changed as a result of updating the options list
4004
4163
  var selectionChanged;
4005
- if (element.multiple) {
4164
+ if (multiple) {
4006
4165
  // For a multiple-select box, compare the new selection count to the previous one
4007
4166
  // But if nothing was selected before, the selection can't have changed
4008
4167
  selectionChanged = previousSelectedValues.length && selectedOptions().length < previousSelectedValues.length;
@@ -4417,6 +4576,7 @@ makeEventHandlerShortcut('click');
4417
4576
  // // - you might also want to make bindingContext.$parent, bindingContext.$parents,
4418
4577
  // // and bindingContext.$root available in the template too
4419
4578
  // // - options gives you access to any other properties set on "data-bind: { template: options }"
4579
+ // // - templateDocument is the document object of the template
4420
4580
  // //
4421
4581
  // // Return value: an array of DOM nodes
4422
4582
  // }
@@ -4434,7 +4594,7 @@ makeEventHandlerShortcut('click');
4434
4594
 
4435
4595
  ko.templateEngine = function () { };
4436
4596
 
4437
- ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
4597
+ ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options, templateDocument) {
4438
4598
  throw new Error("Override renderTemplateSource");
4439
4599
  };
4440
4600
 
@@ -4459,7 +4619,7 @@ ko.templateEngine.prototype['makeTemplateSource'] = function(template, templateD
4459
4619
 
4460
4620
  ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) {
4461
4621
  var templateSource = this['makeTemplateSource'](template, templateDocument);
4462
- return this['renderTemplateSource'](templateSource, bindingContext, options);
4622
+ return this['renderTemplateSource'](templateSource, bindingContext, options, templateDocument);
4463
4623
  };
4464
4624
 
4465
4625
  ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) {
@@ -4479,7 +4639,7 @@ ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCal
4479
4639
  ko.exportSymbol('templateEngine', ko.templateEngine);
4480
4640
 
4481
4641
  ko.templateRewriting = (function () {
4482
- var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi;
4642
+ var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi;
4483
4643
  var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
4484
4644
 
4485
4645
  function validateDataBindValuesForRewriting(keyValueArray) {
@@ -4554,10 +4714,10 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
4554
4714
  // with the rendered template output.
4555
4715
  // You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
4556
4716
  // Template sources need to have the following functions:
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".
4717
+ // text() - returns the template text from your storage location
4718
+ // text(value) - writes the supplied template text to your storage location
4719
+ // data(key) - reads values stored using data(key, value) - see below
4720
+ // data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
4561
4721
  //
4562
4722
  // Optionally, template sources can also have the following functions:
4563
4723
  // nodes() - returns a DOM element containing the nodes of this template, where available
@@ -4720,7 +4880,7 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
4720
4880
  function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
4721
4881
  options = options || {};
4722
4882
  var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
4723
- var templateDocument = firstTargetNode && firstTargetNode.ownerDocument;
4883
+ var templateDocument = (firstTargetNode || template || {}).ownerDocument;
4724
4884
  var templateEngineToUse = (options['templateEngine'] || _templateEngine);
4725
4885
  ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument);
4726
4886
  var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument);
@@ -4826,6 +4986,10 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
4826
4986
  activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
4827
4987
  if (options['afterRender'])
4828
4988
  options['afterRender'](addedNodesArray, arrayValue);
4989
+
4990
+ // release the "cache" variable, so that it can be collected by
4991
+ // the GC when its value isn't used from within the bindings anymore.
4992
+ arrayItemContext = null;
4829
4993
  };
4830
4994
 
4831
4995
  return ko.dependentObservable(function () {
@@ -4860,6 +5024,17 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
4860
5024
  if (typeof bindingValue == "string" || bindingValue['name']) {
4861
5025
  // It's a named template - clear the element
4862
5026
  ko.virtualElements.emptyNode(element);
5027
+ } else if ('nodes' in bindingValue) {
5028
+ // We've been given an array of DOM nodes. Save them as the template source.
5029
+ // There is no known use case for the node array being an observable array (if the output
5030
+ // varies, put that behavior *into* your template - that's what templates are for), and
5031
+ // the implementation would be a mess, so assert that it's not observable.
5032
+ var nodes = bindingValue['nodes'] || [];
5033
+ if (ko.isObservable(nodes)) {
5034
+ throw new Error('The "nodes" option must be a plain, non-observable array.');
5035
+ }
5036
+ var container = ko.utils.moveCleanedNodesToContainerElement(nodes); // This also removes the nodes from their current parent
5037
+ new ko.templateSources.anonymousTemplate(element)['nodes'](container);
4863
5038
  } else {
4864
5039
  // It's an anonymous template - store the element contents, then clear the element
4865
5040
  var templateNodes = ko.virtualElements.childNodes(element),
@@ -5194,7 +5369,7 @@ ko.nativeTemplateEngine = function () {
5194
5369
 
5195
5370
  ko.nativeTemplateEngine.prototype = new ko.templateEngine();
5196
5371
  ko.nativeTemplateEngine.prototype.constructor = ko.nativeTemplateEngine;
5197
- ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
5372
+ ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options, templateDocument) {
5198
5373
  var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly
5199
5374
  templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null,
5200
5375
  templateNodes = templateNodesFunc ? templateSource['nodes']() : null;
@@ -5203,7 +5378,7 @@ ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSo
5203
5378
  return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes);
5204
5379
  } else {
5205
5380
  var templateText = templateSource['text']();
5206
- return ko.utils.parseHtmlFragment(templateText);
5381
+ return ko.utils.parseHtmlFragment(templateText, templateDocument);
5207
5382
  }
5208
5383
  };
5209
5384
 
@@ -5240,7 +5415,8 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
5240
5415
  return jQueryInstance['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
5241
5416
  }
5242
5417
 
5243
- this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
5418
+ this['renderTemplateSource'] = function(templateSource, bindingContext, options, templateDocument) {
5419
+ templateDocument = templateDocument || document;
5244
5420
  options = options || {};
5245
5421
  ensureHasReferencedJQueryTemplates();
5246
5422
 
@@ -5259,7 +5435,7 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
5259
5435
  var jQueryTemplateOptions = jQueryInstance['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
5260
5436
 
5261
5437
  var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
5262
- resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
5438
+ resultNodes['appendTo'](templateDocument.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
5263
5439
 
5264
5440
  jQueryInstance['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
5265
5441
  return resultNodes;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kea-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan-Christian Foeh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-09 00:00:00.000000000 Z
11
+ date: 2015-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -42,6 +42,7 @@ files:
42
42
  - app/assets/javascripts/kea/bindings/submit_button.js
43
43
  - app/assets/javascripts/kea/bindings/validation_state.js
44
44
  - app/assets/javascripts/kea/bindings/wait_for_vm.js
45
+ - app/assets/javascripts/kea/components/confirmation_button.js
45
46
  - app/assets/javascripts/kea/extenders/equals.js
46
47
  - app/assets/javascripts/kea/extenders/external_validator.js
47
48
  - app/assets/javascripts/kea/extenders/min_length.js
@@ -203,7 +204,7 @@ files:
203
204
  - vendor/assets/components/veiljs/veil.js
204
205
  - vendor/assets/javascripts/fuse.js
205
206
  - vendor/assets/javascripts/jquery-ui.js
206
- - vendor/assets/javascripts/knockout-3.2.0-debug.js
207
+ - vendor/assets/javascripts/knockout-3.3.0-debug.js
207
208
  - vendor/assets/javascripts/moment.js
208
209
  - vendor/assets/javascripts/pikaday.js
209
210
  - vendor/assets/stylesheets/pikaday.css