sproutcore 1.11.0.rc2 → 1.11.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG +10 -0
  3. data/VERSION.yml +1 -1
  4. data/lib/frameworks/sproutcore/CHANGELOG.md +114 -1
  5. data/lib/frameworks/sproutcore/apps/showcase/views/views_item_view.js +1 -7
  6. data/lib/frameworks/sproutcore/apps/showcase/views/views_list_view.js +9 -9
  7. data/lib/frameworks/sproutcore/frameworks/ajax/system/request.js +167 -5
  8. data/lib/frameworks/sproutcore/frameworks/ajax/system/response.js +24 -8
  9. data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/stack_layout.js +737 -0
  10. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/layout.js +0 -6
  11. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +11 -7
  12. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane_statechart.js +7 -11
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/child_view_layout_protocol.js +8 -3
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/observable_protocol.js +9 -6
  15. data/lib/frameworks/sproutcore/frameworks/{desktop/protocols/responder.js → core_foundation/protocols/responder_protocol.js} +83 -17
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/{sparse_array_delegate.js → sparse_array_delegate_protocol.js} +11 -7
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/view_transition_protocol.js +11 -6
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/color.js +2 -2
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/page.js +0 -22
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +61 -56
  21. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/main_pane.js +2 -2
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/append_remove.js +3 -3
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/animation.js +63 -39
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/border_frame_test.js +28 -28
  25. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/createLayer.js +10 -4
  26. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layout.js +102 -1
  27. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutDidChange.js +4 -4
  28. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +103 -103
  29. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/replaceAllChildren_test.js +1 -1
  30. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view.js +77 -1
  31. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view_states_test.js +18 -17
  32. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +42 -49
  33. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/animation.js +5 -6
  34. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/enabled.js +16 -5
  35. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout.js +241 -102
  36. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +1 -4
  37. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/manipulation.js +0 -11
  38. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/statechart.js +993 -610
  39. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/theming.js +3 -2
  40. data/lib/frameworks/sproutcore/frameworks/datastore/data_sources/data_source.js +6 -11
  41. data/lib/frameworks/sproutcore/frameworks/datastore/system/nested_store.js +94 -27
  42. data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +133 -53
  43. data/lib/frameworks/sproutcore/frameworks/datastore/system/store.js +30 -35
  44. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/writeAttribute.js +3 -2
  45. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/writeDataHash.js +73 -29
  46. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/conflictedStoreKeys_test.js +156 -0
  47. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/dataSourceCallbacks.js +61 -37
  48. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/find.js +2 -2
  49. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +68 -39
  50. data/lib/frameworks/sproutcore/frameworks/designer/tests/coders/page.js +1 -2
  51. data/lib/frameworks/sproutcore/frameworks/desktop/panes/panel.js +8 -6
  52. data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +80 -14
  53. data/lib/frameworks/sproutcore/frameworks/desktop/panes/sheet.js +2 -2
  54. data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drag_data_source.js → drag_data_source_protocol.js} +16 -10
  55. data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drag_source.js → drag_source_protocol.js} +28 -26
  56. data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drop_target.js → drop_target_protocol.js} +73 -75
  57. data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -4
  58. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/panel/ui.js +39 -23
  59. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/mouse.js +120 -97
  60. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/rowSizeForContentIndex.js +26 -25
  61. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/ui.js +3 -3
  62. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/segmented/ui.js +5 -0
  63. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/split/{dividers.js → dividers_test.js} +38 -38
  64. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +29 -14
  65. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroll.js +2 -1
  66. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll_view.js +13 -18
  67. data/lib/frameworks/sproutcore/frameworks/desktop/views/segmented.js +41 -35
  68. data/lib/frameworks/sproutcore/frameworks/desktop/views/slider.js +14 -14
  69. data/lib/frameworks/sproutcore/frameworks/desktop/views/split.js +41 -26
  70. data/lib/frameworks/sproutcore/frameworks/desktop/views/static_content.js +2 -12
  71. data/lib/frameworks/sproutcore/frameworks/foundation/gestures/tap.js +2 -2
  72. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/auto_resize.js +14 -10
  73. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/gesturable.js +104 -63
  74. data/lib/frameworks/sproutcore/frameworks/foundation/protocols/swap_transition_protocol.js +9 -4
  75. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_mixin_tests.js +1 -2
  76. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_resize_test.js +33 -33
  77. data/lib/frameworks/sproutcore/frameworks/foundation/tests/transitions/view_transitions_test.js +5 -5
  78. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/methods.js +0 -4
  79. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/transition_test.js +0 -4
  80. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/ui.js +0 -2
  81. data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +12 -8
  82. data/lib/frameworks/sproutcore/frameworks/media/resources/silence.mp3 +0 -0
  83. data/lib/frameworks/sproutcore/frameworks/media/tests/audio.js +69 -0
  84. data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +1 -0
  85. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
  86. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/observable.js +11 -4
  87. data/lib/frameworks/sproutcore/frameworks/runtime/protocols/mixin_protocol.js +150 -0
  88. data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +447 -137
  89. data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +9 -15
  90. data/lib/frameworks/sproutcore/frameworks/runtime/system/set.js +19 -17
  91. data/lib/frameworks/sproutcore/frameworks/runtime/tests/system/binding.js +188 -16
  92. data/lib/frameworks/sproutcore/frameworks/statechart/system/state.js +1 -1
  93. data/lib/frameworks/sproutcore/frameworks/template_view/panes/template.js +0 -3
  94. data/lib/frameworks/sproutcore/frameworks/template_view/tests/panes/template.js +0 -17
  95. data/lib/frameworks/sproutcore/frameworks/template_view/tests/views/template/collection.js +43 -26
  96. data/lib/frameworks/sproutcore/frameworks/template_view/views/bindable_span.js +9 -2
  97. data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list.css +0 -1
  98. data/lib/frameworks/sproutcore/themes/ace/resources/scroll/scroll.css +3 -0
  99. data/sproutcore.gemspec +3 -3
  100. metadata +19 -17
  101. data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/horizontal_stack_layout.js +0 -465
  102. data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/vertical_stack_layout.js +0 -472
  103. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/build.js +0 -87
  104. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/build_children.js +0 -89
@@ -109,7 +109,6 @@ test("Reversing SHOWING to HIDING: FADE_IN & FADE_OUT", function () {
109
109
  equals(view.get('isVisible'), false, "The isVisible property of the view is");
110
110
  ok(!jqEl.hasClass('sc-hidden'), "The view doesn't have sc-hidden class name.");
111
111
  ok(jqEl.css('opacity') > 0, "The view's opacity is not 0 still.");
112
- equals(view.$().css('opacity'), '1', "The view's opacity is");
113
112
  }, 200);
114
113
 
115
114
  setTimeout(function () {
@@ -208,8 +207,8 @@ test("Reversing HIDING to SHOWING: FADE_IN & FADE_OUT", function () {
208
207
  // Test assumption.
209
208
  equals(view.get('isVisible'), false, "The isVisible property of the view is");
210
209
  ok(!jqEl.hasClass('sc-hidden'), "The view doesn't have sc-hidden class name.");
211
- ok(jqEl.css('opacity') > 0, "The view's opacity is not 0.");
212
- ok(jqEl.css('opacity') < 1, "The view's opacity is not 1.");
210
+ ok(jqEl.css('opacity') > 0, "The view's current opacity is not 0.");
211
+ ok(jqEl.css('opacity') < 1, "The view's current opacity is not 1.");
213
212
 
214
213
  // Cancel fading out.
215
214
  SC.run(function () {
@@ -219,7 +218,8 @@ test("Reversing HIDING to SHOWING: FADE_IN & FADE_OUT", function () {
219
218
  jqEl = view.$();
220
219
  equals(view.get('isVisible'), true, "The isVisible property of the view is");
221
220
  ok(!jqEl.hasClass('sc-hidden'), "The view doesn't have sc-hidden class name.");
222
- equals(view.$().css('opacity'), '0', "The view's opacity is");
221
+ ok(jqEl.css('opacity') > 0, "The view's current opacity is not 0 still.");
222
+ ok(jqEl.css('opacity') < 1, "The view's current opacity is not 1 still.");
223
223
  }, 200);
224
224
 
225
225
  setTimeout(function () {
@@ -228,7 +228,7 @@ test("Reversing HIDING to SHOWING: FADE_IN & FADE_OUT", function () {
228
228
  // Test assumption.
229
229
  equals(view.get('isVisible'), true, "The isVisible property of the view is");
230
230
  ok(!jqEl.hasClass('sc-hidden'), "The view doesn't have sc-hidden class name.");
231
- equals(jqEl.css('opacity'), '1', "The view's opacity is now");
231
+ equals(jqEl.css('opacity'), '1', "The view's current opacity is now");
232
232
 
233
233
  start();
234
234
  }, 1000);
@@ -21,10 +21,6 @@ module("SC.ContainerView Methods", {
21
21
  childViews: [containerView]
22
22
  }).append();
23
23
  });
24
-
25
- SC.run(function () {
26
- containerView.awake();
27
- });
28
24
  },
29
25
 
30
26
  teardown: function () {
@@ -30,10 +30,6 @@ module("SC.ContainerView Transitions", {
30
30
  childViews: [containerView]
31
31
  }).append();
32
32
  });
33
-
34
- SC.run(function () {
35
- containerView.awake();
36
- });
37
33
  },
38
34
 
39
35
  teardown: function () {
@@ -144,7 +144,6 @@
144
144
 
145
145
  test("default nowShowing", function(){
146
146
  var view = pane.view("nowShowingDefault");
147
- view.awake();
148
147
 
149
148
  var contentView = view.get('contentView');
150
149
 
@@ -164,7 +163,6 @@
164
163
 
165
164
  test("Cleans up instantiated views", function() {
166
165
  var view = pane.view("cleans-up-views");
167
- view.awake();
168
166
 
169
167
  var contentView = view.get('contentView');
170
168
  SC.run(function() { view.set('nowShowing', SC.View.create()); });
@@ -1259,10 +1259,12 @@ SC.TextFieldView = SC.FieldView.extend(SC.Editable,
1259
1259
  if (SC.browser.isMozilla &&
1260
1260
  evt.keyCode === SC.Event.KEY_RETURN) { this.fieldValueDidChange(); }
1261
1261
 
1262
- // The caret/selection could have moved. In some browsers, though, the
1263
- // element's values won't be updated until after this event is finished
1264
- // processing.
1265
- this.notifyPropertyChange('selection');
1262
+ // The caret/selection may have changed.
1263
+ // This cannot notify immediately, because in some browsers (tested Chrome 39.0 on OS X), the
1264
+ // value of `selectionStart` and `selectionEnd` won't have updated yet. Thus if we notified
1265
+ // immediately, observers of this view's `selection` property would get the old value.
1266
+ this.invokeNext(this._textField_selectionDidChange);
1267
+
1266
1268
  evt.allowDefault();
1267
1269
  return true;
1268
1270
  },
@@ -1287,10 +1289,12 @@ SC.TextFieldView = SC.FieldView.extend(SC.Editable,
1287
1289
  return true;
1288
1290
  }
1289
1291
 
1290
- // The caret/selection could have moved. In some browsers, though, the
1291
- // element's values won't be updated until after this event is finished
1292
- // processing.
1293
- this.notifyPropertyChange('selection');
1292
+ // The caret/selection may have changed.
1293
+ // This cannot notify immediately, because in some browsers (tested Chrome 39.0 on OS X), the
1294
+ // value of `selectionStart` and `selectionEnd` won't have updated yet. Thus if we notified
1295
+ // immediately, observers of this view's `selection` property would get the old value.
1296
+ this.invokeNext(this._textField_selectionDidChange);
1297
+
1294
1298
  return sc_super();
1295
1299
  },
1296
1300
 
@@ -0,0 +1,69 @@
1
+ // ==========================================================================
2
+ // Project: SproutCore - JavaScript Application Framework
3
+ // Copyright: ©2006-2011 Strobe Inc. and contributors.
4
+ // Portions ©2008-2011 Apple Inc. All rights reserved.
5
+ // License: Licensed under MIT license (see license.js)
6
+ // ==========================================================================
7
+ var
8
+ INITIAL_VOL = 0.8,
9
+ TIMEOUT = 1000,
10
+ TESTFILE = static_url("silence.mp3"), // the real sound of silence
11
+ isPhantom = !!window.callPhantom,
12
+ pane, audioView;
13
+
14
+ module("SC.AudioView", {
15
+
16
+ setup: function () {
17
+ SC.RunLoop.begin();
18
+
19
+ audioView = SC.AudioView.create({
20
+ volume: INITIAL_VOL
21
+ });
22
+
23
+ pane = SC.MainPane.create();
24
+ pane.appendChild(audioView);
25
+ pane.append();
26
+
27
+ SC.RunLoop.end();
28
+
29
+ SC.Timer.schedule({
30
+ action: function () { audioView.set("value", TESTFILE); },
31
+ interval: 100
32
+ });
33
+ },
34
+
35
+ teardown: function () {
36
+ pane.remove();
37
+ pane = audioView = null;
38
+ }
39
+ });
40
+
41
+ function checkAudioSupport() {
42
+ if (isPhantom) {
43
+ warn("Audio cannot be tested in PhantomJS (see http://phantomjs.org/supported-web-standards.html)");
44
+ return false;
45
+ }
46
+ return true;
47
+ }
48
+
49
+ test("Test MP3 file", function () {
50
+ if (!checkAudioSupport()) return;
51
+ stop(TIMEOUT);
52
+
53
+ // assume audio has been loaded when the duration changes
54
+ audioView.addObserver("duration", function (sender, key) {
55
+ ok(sender.get(key), "MP3 file did load");
56
+ start();
57
+ });
58
+ });
59
+
60
+ test("Test initial volume", function () {
61
+ if (!checkAudioSupport()) return;
62
+ stop(TIMEOUT);
63
+
64
+ // assume audio has been loaded when the duration changes
65
+ audioView.addObserver("duration", function (sender, key) {
66
+ equals(audioView.get("volume"), INITIAL_VOL, "Initial volume is still set after audio has been loaded");
67
+ start();
68
+ });
69
+ });
@@ -298,6 +298,7 @@ SC.AudioView = SC.View.extend(
298
298
  addAudioDOMEvents: function() {
299
299
  var audioElem, view=this;
300
300
  audioElem = this.$('audio')[0];
301
+ audioElem.volume = this.get('volume');
301
302
  this.set('audioObject', audioElem);
302
303
  SC.Event.add(audioElem, 'durationchange', this, function () {
303
304
  SC.run(function() {
@@ -39,7 +39,7 @@ window.SproutCore = window.SproutCore || SC;
39
39
  // rest of the methods go into the mixin defined below.
40
40
 
41
41
  /**
42
- @version 1.11.0.rc2
42
+ @version 1.11.0.rc3
43
43
  @namespace
44
44
 
45
45
  All SproutCore methods and functions are defined
@@ -56,7 +56,7 @@ window.SproutCore = window.SproutCore || SC;
56
56
  */
57
57
  SC = window.SC; // This is dumb but necessary for jsdoc to get it right
58
58
 
59
- SC.VERSION = '1.11.0.rc2';
59
+ SC.VERSION = '1.11.0.rc3';
60
60
 
61
61
  /**
62
62
  @private
@@ -561,7 +561,9 @@ SC.Observable = /** @scope SC.Observable.prototype */ {
561
561
  }
562
562
 
563
563
  // otherwise notify property observers immediately
564
- } else this._notifyPropertyObservers(key);
564
+ } else {
565
+ this._notifyPropertyObservers(key);
566
+ }
565
567
 
566
568
  return this;
567
569
  },
@@ -1162,9 +1164,12 @@ SC.Observable = /** @scope SC.Observable.prototype */ {
1162
1164
  return observers ? observers.getMembers() : [];
1163
1165
  },
1164
1166
 
1165
- // this private method actually notifies the observers for any keys in the
1166
- // observer queue. If you pass a key it will be added to the queue.
1167
+ /** @private
1168
+ This private method actually notifies the observers for any keys in the observer queue. If you
1169
+ pass a key it will be added to the queue.
1170
+ */
1167
1171
  _notifyPropertyObservers: function (key) {
1172
+ // Ensure that this object has been initialized.
1168
1173
  if (!this._observableInited) this.initObservable();
1169
1174
 
1170
1175
  SC.Observers.flush(this); // hookup as many observers as possible.
@@ -1182,7 +1187,7 @@ SC.Observable = /** @scope SC.Observable.prototype */ {
1182
1187
  //@endif
1183
1188
 
1184
1189
  // Get any starObservers -- they will be notified of all changes.
1185
- starObservers = this['_kvo_observers_*'];
1190
+ starObservers = this['_kvo_observers_*'];
1186
1191
 
1187
1192
  // prevent notifications from being sent until complete
1188
1193
  this._kvo_changeLevel = this._kvo_changeLevel + 1;
@@ -1259,6 +1264,7 @@ SC.Observable = /** @scope SC.Observable.prototype */ {
1259
1264
  // observer, you will not be notified until the next time.)
1260
1265
  members = observers.getMembers();
1261
1266
  membersLength = members.length;
1267
+
1262
1268
  for (memberLoc = 0; memberLoc < membersLength; memberLoc++) {
1263
1269
  member = members[memberLoc];
1264
1270
 
@@ -1297,6 +1303,7 @@ SC.Observable = /** @scope SC.Observable.prototype */ {
1297
1303
  //@if(debug)
1298
1304
  if (log) console.log('%@...firing local observer %@.%@ for key "%@"'.fmt(spaces, this, member, key));
1299
1305
  //@endif
1306
+
1300
1307
  method.call(this, this, key, null, rev);
1301
1308
  }
1302
1309
  }
@@ -0,0 +1,150 @@
1
+ // ==========================================================================
2
+ // Project: SproutCore Costello - Property Observing Library
3
+ // Copyright: ©2006-2011 Strobe Inc. and contributors.
4
+ // Portions ©2008-2011 Apple Inc. All rights reserved.
5
+ // License: Licensed under MIT license (see license.js)
6
+ // ==========================================================================
7
+
8
+ /** @namespace
9
+ The `SC.ObjectMixinProtocol` protocol defines the properties and methods that you may implement
10
+ in your mixin objects (i.e. JavaScript Objects passed to SC.Object's `extend` or `create`) in
11
+ order to access additional functionality when used. They will be used if defined but are not
12
+ required.
13
+
14
+ # What is a Mixin?
15
+
16
+ A mixin, in this context, is a simple JavaScript Object that can be used to provide extra
17
+ functionality to SC.Object subclasses. While you can mix JavaScript Objects into "classes" (i.e.
18
+ using `SC.mixin(SomeClass)`), this particular protocol only refers to mixins in the context of
19
+ use with an SC.Object "instance" (i.e. SC.Object.create({ ... })).
20
+
21
+ For example, in order to share a method between two different classes of object, we can use a
22
+ mixin object that both will consume,
23
+
24
+ // Common default properties and shared methods which our different classes may consume.
25
+ MyApp.MyMixin = {
26
+ a: true,
27
+
28
+ aFunc: function () {
29
+ this.set('a', false);
30
+ },
31
+
32
+ b: [], // SHARED OBJECT!
33
+
34
+ c: {} // SHARED OBJECT!
35
+ };
36
+
37
+ // Two different object types, which both need the functionality provided by MyApp.MyMixin.
38
+ MyApp.ObjectType1 = SC.Object.extend(MyApp.MyMixin);
39
+ MyApp.ObjectType2 = SC.Object.extend();
40
+
41
+ obj1 = MyApp.ObjectType1.create();
42
+ obj2 = MyApp.ObjectType2.create(MyApp.MyMixin);
43
+
44
+ // Some proofs.
45
+ // 1. The default properties are copied over to the new objects.
46
+ obj1.get('a'); // true <--
47
+ obj2.get('a'); // true <--
48
+
49
+ // 2. The primitive properties are unique to each object.
50
+ obj1.set('a', false);
51
+ obj1.get('a'); // false <--
52
+ obj2.get('a'); // true <--
53
+
54
+ // 3. The methods are copied over to the new objects.
55
+ obj1.aFunc; // function () { ... } <--
56
+ obj2.aFunc; // function () { ... } <--
57
+
58
+ // 4. The functions/objects are shared between objects.
59
+ obj1.aFunc === MyApp.MyMixin.aFunc; // true <--
60
+ obj1.aFunc === obj2.aFunc; // true <--
61
+ obj1.b === obj2.b; // true <-- !! Beware of modifying this object !!
62
+ obj1.c === obj2.c; // true <-- !! Beware of modifying this object !!
63
+
64
+ In this example, we used a mixin to share functionality between two classes, which is very easily
65
+ achieved. There is one issue, that has been known to trip up developers, which should be
66
+ highlighted. If you set default *Objects* (e.g. [] or {}) in a mixin, these same Objects will be
67
+ shared between all of the mixin's consumers.
68
+
69
+ If you want to set a default Object that is unique to each consumer of the mixin, a better
70
+ practice is to set it in `initMixin()` or to check for its existence the first time it is used
71
+ and only create it then.
72
+
73
+ *Note: Do not mix `SC.ObjectMixinProtocol` into your classes. As a protocol, it exists only for
74
+ reference sake. You only need define any of the properties or methods listed below in order to use
75
+ this protocol.*
76
+ */
77
+ SC.ObjectMixinProtocol = {
78
+
79
+ /**
80
+ This *optional* method is called to further initialize the consumer of the mixin when it is
81
+ created. When a mixin (i.e. JavaScript Object) is used to extend an `SC.Object` subclass, we may
82
+ want to perform additional set up of the `SC.Object` instance when it is created according to
83
+ the needs of the mixin. In order to support this, `SC.Object` will call this method,
84
+ `initMixin`, *if implemented*, on each mixin in the order that they were added.
85
+
86
+ For example, if we use two mixins that both initialize the same value, the last mixin added
87
+ would win,
88
+
89
+ myObject = SC.Object.create(
90
+ // First mixin.
91
+ {
92
+ initMixin: function () {
93
+ this.set('a', true);
94
+ }
95
+ },
96
+
97
+ // Second mixin.
98
+ {
99
+ initMixin: function () {
100
+ this.set('a', false);
101
+ }
102
+ });
103
+
104
+ myObject.get('a'); // false <--
105
+
106
+ This was just an example to illustrate the order in which `initMixin` is called. It is rare
107
+ that mixins will collide with each other, but it is something to bear in mind when making heavy
108
+ use of mixins.
109
+
110
+ Note, that unlike the similar `init()` method of `SC.Object`, you do *not* need to call
111
+ `sc_super` in `initMixin`.
112
+ */
113
+ initMixin: function () {},
114
+
115
+ /**
116
+ This *optional* method is called to further de-initialize the consumer of the mixin when it is
117
+ destroyed. When a mixin (i.e. JavaScript Object) is used to extend an `SC.Object` subclass, we
118
+ may want to perform additional teardown of the `SC.Object` instance when it is destroyed
119
+ according to the needs of the mixin (e.g. to clean up objects that the mixin code initialized
120
+ and that may otherwise lead to memory leaks). In order to support this, `SC.Object` will call
121
+ this method, `destroyMixin`, *if implemented*, on each mixin in the order that they were
122
+ initially added.
123
+
124
+ For example, if we use two mixins that both de-initialize the same value, the last mixin added
125
+ would win,
126
+
127
+ myObject = SC.Object.create(
128
+ // Mixin.
129
+ {
130
+ initMixin: function () {
131
+ // Created extra object for some purpose.
132
+ this.set('anObject', SC.Object.create());
133
+ },
134
+
135
+ destroyMixin: function () {
136
+ // Clean up extra object that the mixin is responsible for.
137
+ var anObject = this.get('anObject');
138
+ anObject.destroy();
139
+ this.set('anObject', null);
140
+ }
141
+ });
142
+
143
+ myObject.get('a'); // false <--
144
+
145
+ Note, that unlike the similar `destroy()` method of `SC.Object`, you do *not* need to call
146
+ `sc_super` in `destroyMixin`.
147
+ */
148
+ destroyMixin: function () {}
149
+
150
+ };
@@ -279,11 +279,13 @@ SC.Binding = /** @scope SC.Binding.prototype */{
279
279
  beget: function (fromPath) {
280
280
  var ret = SC.beget(this);
281
281
  ret.parentBinding = this;
282
- // Logic gates must be recreated on beget.
283
- if (ret._LogicGate) {
284
- ret._logicGate = ret._LogicGate.create(ret._logicGateHash);
285
- ret = ret.from('logicProperty', ret._logicGate).oneWay();
282
+
283
+ // Mix adapters must be recreated on beget.
284
+ if (ret._MixAdapter) {
285
+ ret._mixAdapter = ret._MixAdapter.create(ret._mixAdapterHash);
286
+ ret = ret.from('aggregateProperty', ret._mixAdapter).oneWay();
286
287
  }
288
+
287
289
  // Enables duplicate API calls for SC.Binding.beget and SC.Binding.from
288
290
  if (fromPath !== undefined) ret = ret.from(fromPath);
289
291
  return ret;
@@ -443,7 +445,7 @@ SC.Binding = /** @scope SC.Binding.prototype */{
443
445
  }
444
446
  },
445
447
 
446
- /**
448
+ /**
447
449
  Disconnects the binding instance. Changes will no longer be relayed. You
448
450
  will not usually need to call this method.
449
451
 
@@ -493,12 +495,12 @@ SC.Binding = /** @scope SC.Binding.prototype */{
493
495
  // Mark it destroyed.
494
496
  this.isDestroyed = YES;
495
497
 
496
- // Clean up the logic gate, if any. (See logic gate methods.)
497
- if (this._logicGate) {
498
- this._logicGate.destroy();
499
- this._logicGate = null;
500
- this._LogicGate = null;
501
- this._logicGateHash = null;
498
+ // Clean up the mix adapter, if any. (See adapter methods.)
499
+ if (this._mixAdapter) {
500
+ this._mixAdapter.destroy();
501
+ this._mixAdapter = null;
502
+ this._MixAdapter = null;
503
+ this._mixAdapterHash = null;
502
504
  }
503
505
 
504
506
  // Disconnect the binding.
@@ -861,9 +863,9 @@ SC.Binding = /** @scope SC.Binding.prototype */{
861
863
  if (tuple) {
862
864
  this._toTarget = tuple[0];
863
865
  this._toPropertyKey = tuple[1];
864
- // Hook up _logicGate if needed (see logic gate methods).
865
- if (this._logicGate) {
866
- this._logicGate.set('localObject', this._toTarget);
866
+ // Hook up _mixAdapter if needed (see adapter methods).
867
+ if (this._mixAdapter) {
868
+ this._mixAdapter.set('localObject', this._toTarget);
867
869
  }
868
870
  }
869
871
  }
@@ -949,6 +951,112 @@ SC.Binding = /** @scope SC.Binding.prototype */{
949
951
  return binding;
950
952
  },
951
953
 
954
+ /**
955
+ Adds a transform to convert the value to a bool value. If the value is
956
+ an array it will return YES if array is not empty. If the value is a string
957
+ it will return YES if the string is not empty.
958
+
959
+ @param {String} [fromPath]
960
+ @returns {SC.Binding} this
961
+ */
962
+ bool: function (fromPath) {
963
+ return this.from(fromPath).transform(function (v) {
964
+ var t = SC.typeOf(v);
965
+ if (t === SC.T_ERROR) return v;
966
+ return (t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v;
967
+ });
968
+ },
969
+
970
+ /**
971
+ Adds a transform that will return YES if the value is equal to equalValue, NO otherwise.
972
+
973
+ isVisibleBinding: SC.Binding.oneWay("MyApp.someController.title").equalTo(comparisonValue)
974
+
975
+ Or:
976
+
977
+ isVisibleBinding: SC.Binding.equalTo("MyApp.someController.title", comparisonValue)
978
+
979
+ @param {String} fromPath from path or null
980
+ @param {Object} equalValue the value to compare with
981
+ @returns {SC.Binding} this
982
+ */
983
+ equalTo: function(fromPath, equalValue) {
984
+ // Normalize arguments.
985
+ if (equalValue === undefined) {
986
+ equalValue = fromPath;
987
+ fromPath = null;
988
+ }
989
+
990
+ return this.from(fromPath).transform(function(value, binding) {
991
+ return value === equalValue;
992
+ });
993
+ },
994
+
995
+ /**
996
+ Adds a transform that will *always* return an integer Number value. Null and undefined values will
997
+ return 0 while String values will be transformed using the parseInt method (according to the
998
+ radix) and Boolean values will be 1 or 0 if true or false accordingly. Other edge cases like NaN
999
+ or other non-Numbers will also return 0.
1000
+
1001
+ Example results:
1002
+
1003
+ * null => 0
1004
+ * undefined => 0
1005
+ * '123' => 123
1006
+ * true => 1
1007
+ * {} => 0
1008
+
1009
+ @param {String} fromPathOrRadix from path or the radix for the parsing or null for 10
1010
+ @param {String} radix the radix for the parsing or null for 10
1011
+ @returns {SC.Binding} this
1012
+ */
1013
+ integer: function (fromPathOrRadix, radix) {
1014
+ // Normalize arguments.
1015
+ if (radix === undefined) {
1016
+ radix = fromPathOrRadix;
1017
+ fromPathOrRadix = null;
1018
+ }
1019
+
1020
+ // Use base 10 by default.
1021
+ if (radix === undefined) radix = 10;
1022
+
1023
+ return this.from(fromPathOrRadix).transform(function (value) {
1024
+
1025
+ // Null or undefined will be converted to 0.
1026
+ if (SC.none(value)) {
1027
+ value = 0;
1028
+
1029
+ // String values will be converted to integer Numbers using parseInt with the given radix.
1030
+ } else if (typeof value === SC.T_STRING) {
1031
+ value = window.parseInt(value, radix);
1032
+
1033
+ // Boolean values will be converted to 0 or 1 accordingly.
1034
+ } else if (typeof value === SC.T_BOOL) {
1035
+ value = value ? 1 : 0;
1036
+ }
1037
+
1038
+ // All other non-Number values will be converted to 0 (this includes bad String parses above).
1039
+ if (typeof value !== SC.T_NUMBER || isNaN(value)) {
1040
+ value = 0;
1041
+ }
1042
+
1043
+ return value;
1044
+ });
1045
+ },
1046
+
1047
+ /**
1048
+ Adds a transform that will return YES if the value is null or undefined, NO otherwise.
1049
+
1050
+ @param {String} [fromPath]
1051
+ @returns {SC.Binding} this
1052
+ */
1053
+ isNull: function (fromPath) {
1054
+ return this.from(fromPath).transform(function (v) {
1055
+ var t = SC.typeOf(v);
1056
+ return (t === SC.T_ERROR) ? v : SC.none(v);
1057
+ });
1058
+ },
1059
+
952
1060
  /**
953
1061
  Specifies that the binding should not return error objects. If the value
954
1062
  of a binding is an Error object, it will be transformed to a null value
@@ -977,33 +1085,31 @@ SC.Binding = /** @scope SC.Binding.prototype */{
977
1085
  },
978
1086
 
979
1087
  /**
980
- Adds a transform to the chain that will allow only single values to pass.
981
- This will allow single values, nulls, and error values to pass through. If
982
- you pass an array, it will be mapped as so:
983
-
984
- [] => null
985
- [a] => a
986
- [a,b,c] => Multiple Placeholder
1088
+ Adds a transform to convert the value to the inverse of a bool value. This
1089
+ uses the same transform as bool() but inverts it.
987
1090
 
988
- You can pass in an optional multiple placeholder or it will use the
989
- default.
1091
+ @param {String} [fromPath]
1092
+ @returns {SC.Binding} this
1093
+ */
1094
+ not: function (fromPath) {
1095
+ return this.from(fromPath).transform(function (v) {
1096
+ var t = SC.typeOf(v);
1097
+ if (t === SC.T_ERROR) return v;
1098
+ return !((t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v);
1099
+ });
1100
+ },
990
1101
 
991
- Note that this transform will only happen on forwarded valued. Reverse
992
- values are send unchanged.
1102
+ /**
1103
+ Adds a transform that will convert the passed value to an array. If
1104
+ the value is null or undefined, it will be converted to an empty array.
993
1105
 
994
- @param {String} fromPath from path or null
995
- @param {Object} [placeholder] placeholder value.
1106
+ @param {String} [fromPath]
996
1107
  @returns {SC.Binding} this
997
1108
  */
998
- single: function (fromPath, placeholder) {
999
- if (placeholder === undefined) {
1000
- placeholder = SC.MULTIPLE_PLACEHOLDER;
1001
- }
1002
- return this.from(fromPath).transform(function (value, isForward) {
1003
- if (value && value.isEnumerable) {
1004
- var len = value.get('length');
1005
- value = (len > 1) ? placeholder : (len <= 0) ? null : value.firstObject();
1006
- }
1109
+ multiple: function (fromPath) {
1110
+ return this.from(fromPath).transform(function (value) {
1111
+ /*jshint eqnull:true*/
1112
+ if (!SC.isArray(value)) value = (value == null) ? [] : [value];
1007
1113
  return value;
1008
1114
  });
1009
1115
  },
@@ -1043,150 +1149,355 @@ SC.Binding = /** @scope SC.Binding.prototype */{
1043
1149
  },
1044
1150
 
1045
1151
  /**
1046
- Adds a transform that will convert the passed value to an array. If
1047
- the value is null or undefined, it will be converted to an empty array.
1152
+ Adds a transform to the chain that will allow only single values to pass.
1153
+ This will allow single values, nulls, and error values to pass through. If
1154
+ you pass an array, it will be mapped as so:
1048
1155
 
1049
- @param {String} [fromPath]
1156
+ [] => null
1157
+ [a] => a
1158
+ [a,b,c] => Multiple Placeholder
1159
+
1160
+ You can pass in an optional multiple placeholder or it will use the
1161
+ default.
1162
+
1163
+ Note that this transform will only happen on forwarded valued. Reverse
1164
+ values are send unchanged.
1165
+
1166
+ @param {String} fromPath from path or null
1167
+ @param {Object} [placeholder] placeholder value.
1050
1168
  @returns {SC.Binding} this
1051
1169
  */
1052
- multiple: function (fromPath) {
1053
- return this.from(fromPath).transform(function (value) {
1054
- /*jshint eqnull:true*/
1055
- if (!SC.isArray(value)) value = (value == null) ? [] : [value];
1170
+ single: function (fromPath, placeholder) {
1171
+ if (placeholder === undefined) {
1172
+ placeholder = SC.MULTIPLE_PLACEHOLDER;
1173
+ }
1174
+ return this.from(fromPath).transform(function (value, isForward) {
1175
+ if (value && value.isEnumerable) {
1176
+ var len = value.get('length');
1177
+ value = (len > 1) ? placeholder : (len <= 0) ? null : value.firstObject();
1178
+ }
1056
1179
  return value;
1057
1180
  });
1058
1181
  },
1059
1182
 
1060
1183
  /**
1061
- Adds a transform to convert the value to a bool value. If the value is
1062
- an array it will return YES if array is not empty. If the value is a string
1063
- it will return YES if the string is not empty.
1184
+ Adds a transform that will *always* return a String value. Null and undefined values will return
1185
+ an empty string while all other non-String values will be transformed using the toString method.
1064
1186
 
1065
- @param {String} [fromPath]
1066
- @returns {SC.Binding} this
1067
- */
1068
- bool: function (fromPath) {
1069
- return this.from(fromPath).transform(function (v) {
1070
- var t = SC.typeOf(v);
1071
- if (t === SC.T_ERROR) return v;
1072
- return (t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v;
1073
- });
1074
- },
1187
+ Example results:
1075
1188
 
1076
- /**
1077
- Adds a transform to convert the value to the inverse of a bool value. This
1078
- uses the same transform as bool() but inverts it.
1189
+ * null => ''
1190
+ * undefined => ''
1191
+ * 123 => '123'
1192
+ * true => 'true'
1193
+ * {} => '[object Object]' (i.e. x = {}; return x.toString())
1079
1194
 
1080
- @param {String} [fromPath]
1195
+ @param {String} fromPath from path or null
1081
1196
  @returns {SC.Binding} this
1082
1197
  */
1083
- not: function (fromPath) {
1084
- return this.from(fromPath).transform(function (v) {
1085
- var t = SC.typeOf(v);
1086
- if (t === SC.T_ERROR) return v;
1087
- return !((t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v);
1088
- });
1089
- },
1198
+ string: function (fromPath) {
1199
+ return this.from(fromPath).transform(function (value) {
1090
1200
 
1091
- /**
1092
- Adds a transform that will return YES if the value is null or undefined, NO otherwise.
1201
+ // Null or undefined will be converted to an empty string.
1202
+ if (SC.none(value)) {
1203
+ value = '';
1093
1204
 
1094
- @param {String} [fromPath]
1095
- @returns {SC.Binding} this
1096
- */
1097
- isNull: function (fromPath) {
1098
- return this.from(fromPath).transform(function (v) {
1099
- var t = SC.typeOf(v);
1100
- return (t === SC.T_ERROR) ? v : SC.none(v);
1205
+ // Non-string values will be converted to strings using `toString`.
1206
+ } else if (typeof value !== SC.T_STRING && value.toString) {
1207
+ value = value.toString();
1208
+ }
1209
+
1210
+ return value;
1101
1211
  });
1102
1212
  },
1103
1213
 
1104
- /* @private Used with the logic gate bindings. */
1105
- _LogicGateAnd: SC.Object.extend({
1106
- logicProperty: function () {
1107
- return (this.get('valueA') && this.get('valueB'));
1108
- }.property('valueA', 'valueB').cacheable()
1109
- }),
1110
-
1111
- /* @private Used with the logic gate bindings. */
1112
- _LogicGateOr: SC.Object.extend({
1113
- logicProperty: function () {
1114
- return (this.get('valueA') || this.get('valueB'));
1115
- }.property('valueA', 'valueB').cacheable()
1116
- }),
1117
-
1118
- /* @private Used by logic gate bindings. */
1119
- _logicGateBinding: function (gateClass, pathA, pathB) {
1214
+ /* @private Used by mix adapter bindings. */
1215
+ _sc_mixAdapterBinding: function (adapterClass) {
1216
+ var paths = [];
1217
+
1218
+ //@if(debug)
1219
+ // Add some developer support to prevent improper use.
1220
+ if (arguments.length < 3 ) {
1221
+ SC.Logger.warn('Developer Warning: Invalid mix binding, it should have at least two target paths');
1222
+ }
1223
+ //@endif
1224
+
1120
1225
  // If either path is local, remove any * chains and append the localObject path to it.
1121
- if (pathA.indexOf('*') === 0 || pathA.indexOf('.') === 0) {
1122
- pathA = pathA.slice(1).replace(/\*/g, '.');
1123
- pathA = '*localObject.' + pathA;
1226
+ for (var i = 1; i < arguments.length; i++) {
1227
+ var path = arguments[i];
1228
+
1229
+ if (path.indexOf('*') === 0 || path.indexOf('.') === 0) {
1230
+ path = path.slice(1).replace(/\*/g, '.');
1231
+ path = '*localObject.' + path;
1232
+ }
1233
+ paths.push( path );
1124
1234
  }
1125
- if (pathB.indexOf('*') === 0 || pathB.indexOf('.') === 0) {
1126
- pathB = pathB.slice(1).replace(/\*/g, '.');
1127
- pathB = '*localObject.' + pathB;
1235
+
1236
+ // Gets the adapter class and instantiates a nice copy.
1237
+ var adapterHash = {
1238
+ localObject: null,
1239
+ };
1240
+
1241
+ // create the oneWay bindings pointing to the real data sources.
1242
+ // for naming use a hardcoded convention 'value' + index of the property/path.
1243
+ // of course, these properties are internal so we are not concerned by the naming convention
1244
+ for (i = 0; i < paths.length; ++i) {
1245
+ var key = 'value' + i;
1246
+ adapterHash[key + 'Binding'] = SC.Binding.oneWay(paths[i]);
1128
1247
  }
1129
1248
 
1130
- // Gets the gate class and instantiates a nice copy.
1131
- var gateHash = {
1132
- localObject: null,
1133
- valueABinding: SC.Binding.oneWay(pathA),
1134
- valueBBinding: SC.Binding.oneWay(pathB)
1135
- },
1136
- gate = gateClass.create(gateHash);
1249
+ var adapter = adapterClass.create(adapterHash);
1137
1250
 
1138
1251
  // Creates and populates the return binding.
1139
- var ret = this.from('logicProperty', gate).oneWay();
1140
- // This is all needed later on by beget, which must create a new logic gate instance
1252
+ var ret = this.from('aggregateProperty', adapter).oneWay();
1253
+
1254
+ // This is all needed later on by beget, which must create a new adapter instance
1141
1255
  // or risk bad behavior.
1142
- ret._LogicGate = gateClass;
1143
- ret._logicGateHash = gateHash;
1144
- ret._logicGate = gate;
1256
+ ret._MixAdapter = adapterClass;
1257
+ ret._mixAdapterHash = adapterHash;
1258
+ ret._mixAdapter = adapter;
1145
1259
 
1146
1260
  // On our way.
1147
1261
  return ret;
1148
1262
  },
1149
1263
 
1264
+ /** @private */
1265
+ _sc_mixImpl: function(paths, mixFunction) {
1266
+ var len = paths.length,
1267
+ properties = [];
1268
+
1269
+ //@if(debug)
1270
+ // Add some developer support to prevent improper use.
1271
+ if (SC.none(mixFunction) || SC.typeOf(mixFunction) !== SC.T_FUNCTION ) {
1272
+ SC.Logger.error('Developer Error: Invalid mix binding, the last argument must be a function.');
1273
+ }
1274
+ //@endif
1275
+
1276
+ // Create the adapter class that eventually will contain bindings pointing to all values that will be processed
1277
+ // by mixFunction. The effective aggregation is done by another property that depends on all these local properties
1278
+ // and is invalidated whenever they change.
1279
+ // First of all, create the list of the property names that the aggregate property depends on.
1280
+ // The names of these dynamically created properties are matching the pattern
1281
+ // mentioned above (into _sc_mixAdapterBinding): 'value' + index of the property/path
1282
+ for (var i = 0; i < len; ++i) {
1283
+ properties.push('value' + i);
1284
+ }
1285
+
1286
+ // Create a proxy SC.Object which will be bound to the each of the paths and contain a computed
1287
+ // property that will be dependent on all of the bound properties. The computed property will
1288
+ // return the result of the mix function.
1289
+ var adapter = SC.Object.extend({
1290
+ // Use SC.Function.property to be able to pass an array as arguments to .property
1291
+ aggregateProperty: SC.Function.property(function() {
1292
+ // Get an array of current values that will be passed to the mix function.
1293
+ var values = properties.map(function (name) {
1294
+ return this.get(name);
1295
+ }, this);
1296
+
1297
+ // Call the mixFunction providing an array containing all current source property values.
1298
+ return mixFunction.apply(null, values);
1299
+ }, properties).cacheable()
1300
+ });
1301
+
1302
+ return this._sc_mixAdapterBinding.apply(this, [adapter].concat(paths));
1303
+ },
1304
+
1150
1305
  /**
1151
- Adds a transform that forwards the logical 'AND' of values at 'pathA' and
1152
- 'pathB' whenever either source changes. Note that the transform acts strictly
1153
- as a one-way binding, working only in the direction
1306
+ Adds a transform that returns the logical 'AND' of all the values at the provided paths. This is
1307
+ a quick and useful way to bind a `Boolean` property to two or more other `Boolean` properties.
1308
+
1309
+ For example, imagine that we wanted to only enable a deletion button when an item in a list
1310
+ is selected *and* the current user is allowed to delete items. If these two values are set
1311
+ on controllers respectively at `MyApp.itemsController.hasSelection` and
1312
+ `MyApp.userController.canDelete`. We could do the following,
1313
+
1314
+ deleteButton: SC.ButtonView.design({
1315
+
1316
+ // Action & target for the button.
1317
+ action: 'deleteSelectedItem',
1318
+ target: MyApp.statechart,
1154
1319
 
1155
- 'pathA' AND 'pathB' --> value (value returned is the result of ('pathA' && 'pathB'))
1320
+ // Whether the list has a selection or not.
1321
+ listHasSelectionBinding: SC.Binding.oneWay('MyApp.itemsController.hasSelection'),
1156
1322
 
1157
- Usage example where a delete button's 'isEnabled' value is determined by whether
1158
- something is selected in a list and whether the current user is allowed to delete:
1323
+ // Whether the user can delete items or not.
1324
+ userCanDeleteBinding: SC.Binding.oneWay('MyApp.userController.canDelete'),
1159
1325
 
1160
- deleteButton: SC.ButtonView.design({
1161
- isEnabledBinding: SC.Binding.and('MyApp.itemsController.hasSelection', 'MyApp.userController.canDelete')
1162
- })
1326
+ // Note: Only enable when the list has a selection and the user is allowed!
1327
+ isEnabled: function () {
1328
+ return this.get('listHasSelection') && this.get('userCanDelete');
1329
+ }.property('listHasSelection', 'userCanDelete').cacheable()
1163
1330
 
1164
- @param {String} pathA The first part of the conditional
1165
- @param {String} pathB The second part of the conditional
1331
+ })
1332
+
1333
+ However, this would be much simpler to write by using the `and` binding transform like so,
1334
+
1335
+ deleteButton: SC.ButtonView.design({
1336
+
1337
+ // Action & target for the button.
1338
+ action: 'deleteSelectedItem',
1339
+ target: MyApp.statechart,
1340
+
1341
+ // Note: Only enable when the list has a selection and the user is allowed!
1342
+ isEnabledBinding: SC.Binding.and('MyApp.itemsController.hasSelection', 'MyApp.userController.canDelete')
1343
+
1344
+ })
1345
+
1346
+ *Note:* the transform acts strictly as a one-way binding, working only in the one direction.
1347
+
1348
+ @param {String...} the property paths of source values that will be provided to the AND transform.
1166
1349
  */
1167
- and: function (pathA, pathB) {
1168
- return this._logicGateBinding(this._LogicGateAnd, pathA, pathB);
1350
+ and: function () {
1351
+ // Fast copy.
1352
+ var len = arguments.length,
1353
+ paths = new Array(len);
1354
+ for (var i = 0; i < len; i++) { paths[i] = arguments[i]; }
1355
+
1356
+ // Create a new mix implementation for the AND function.
1357
+ return this._sc_mixImpl(paths, function() {
1358
+ var result = true;
1359
+
1360
+ for (i = 0; result && (i < arguments.length); i++) { // Bails early if any value is false.
1361
+ result = result && arguments[i];
1362
+ }
1363
+
1364
+ return result;
1365
+ });
1169
1366
  },
1170
1367
 
1171
1368
  /**
1172
- Adds a transform that forwards the 'OR' of values at 'pathA' and
1173
- 'pathB' whenever either source changes. Note that the transform acts strictly
1174
- as a one-way binding, working only in the direction
1369
+ Adds a transform that returns the logical 'OR' of all the values at the provided paths. This is
1370
+ a quick and useful way to bind a `Boolean` property to two or more other `Boolean` properties.
1371
+
1372
+ For example, imagine that we wanted to show a button when one or both of two values are present.
1373
+ If these two values are set on controllers respectively at `MyApp.profileController.hasDisplayName` and
1374
+ `MyApp.profileController.hasFullName`. We could do the following,
1375
+
1376
+ saveButton: SC.ButtonView.design({
1377
+
1378
+ // Action & target for the button.
1379
+ action: 'saveProfile',
1380
+ target: MyApp.statechart,
1381
+
1382
+ // Whether the profile has a displayName or not.
1383
+ profileHasDisplayNameBinding: SC.Binding.oneWay('MyApp.profileController.hasDisplayName'),
1384
+
1385
+ // Whether the profile has a fullName or not.
1386
+ profileHasFullNameBinding: SC.Binding.oneWay('MyApp.profileController.hasFullName'),
1175
1387
 
1176
- 'pathA' OR 'pathB' --> value (value returned is the result of ('pathA' || 'pathB'))
1388
+ // Note: Only show when the profile has a displayName or a fullName or both!
1389
+ isVisible: function () {
1390
+ return this.get('profileHasDisplayName') || this.get('profileHasFullName');
1391
+ }.property('profileHasDisplayName', 'profileHasFullName').cacheable()
1177
1392
 
1178
- Usage example where a delete button's 'isEnabled' value is determined by if the
1179
- content is editable, or if the user has admin rights:
1393
+ })
1180
1394
 
1181
- deleteButton: SC.ButtonView.design({
1182
- isEnabledBinding: SC.Binding.or('*content.isEditable', 'MyApp.userController.isAdmin')
1183
- })
1395
+ However, this would be much simpler to write by using the `or` binding transform like so,
1184
1396
 
1185
- @param {String} pathA The first part of the conditional
1186
- @param {String} pathB The second part of the conditional
1397
+ saveButton: SC.ButtonView.design({
1398
+
1399
+ // Action & target for the button.
1400
+ action: 'saveProfile',
1401
+ target: MyApp.statechart,
1402
+
1403
+ // Note: Only show when the profile has a displayName or a fullName or both!
1404
+ isVisibleBinding: SC.Binding.or('MyApp.profileController.hasDisplayName', 'MyApp.profileController.hasFullName')
1405
+
1406
+ })
1407
+
1408
+ *Note:* the transform acts strictly as a one-way binding, working only in the one direction.
1409
+
1410
+ @param {String...} the paths of source values that will be provided to the OR sequence.
1187
1411
  */
1188
- or: function (pathA, pathB) {
1189
- return this._logicGateBinding(this._LogicGateOr, pathA, pathB);
1412
+ or: function () {
1413
+ // Fast copy.
1414
+ var len = arguments.length,
1415
+ paths = new Array(len);
1416
+ for (var i = 0; i < len; i++) { paths[i] = arguments[i]; }
1417
+
1418
+ // Create a new mix implementation for the OR function.
1419
+ return this._sc_mixImpl( paths, function() {
1420
+ var result = false;
1421
+ for (i = 0; !result && (i < arguments.length); i++) { // Bails early if any value is true.
1422
+ result = result || arguments[i];
1423
+ }
1424
+
1425
+ return result;
1426
+ });
1427
+ },
1428
+
1429
+ /**
1430
+ Adds a transform that aggregates through a given function the values at the provided paths. The
1431
+ given function is called whenever any of the values are updated. This is a quick way to
1432
+ aggregate multiple properties into a single property value.
1433
+
1434
+ For example, to concatenate two properties 'MyApp.groupController.name' and
1435
+ 'MyApp.userController.fullName', we could do the following,
1436
+
1437
+ currentGroupUserLabel: SC.LabelView.extend({
1438
+
1439
+ // The group name (may be null).
1440
+ groupNameBinding: SC.Binding.oneWay('MyApp.groupController.name'),
1441
+
1442
+ // The user full name (may be null).
1443
+ userFullNameBinding: SC.Binding.oneWay('MyApp.userController.fullName'),
1444
+
1445
+ // Ex. Returns one of "", "Selected Group", or "Selected Group: Selected User"
1446
+ value: function () {
1447
+ var groupName = this.get('groupName'),
1448
+ userFullName = this.get('userFullName');
1449
+
1450
+ if (SC.none(userFullName)) {
1451
+ if (SC.none(groupName)) {
1452
+ return ''; // No group and no user.
1453
+ } else {
1454
+ return groupName; // Just a group.
1455
+ }
1456
+ } else {
1457
+ return '%@: %@'.fmt(groupName, userFullName); // Group and user.
1458
+ }
1459
+ }.property('groupName', 'userFullName').cacheable()
1460
+
1461
+ })
1462
+
1463
+ However, this is simpler (ex. 86 fewer characters) to write by using the `mix` binding transform like so,
1464
+
1465
+ currentGroupUserLabel: SC.LabelView.extend({
1466
+
1467
+ // Ex. Returns one of "", "Selected Group", or "Selected Group: Selected User"
1468
+ valueBinding: SC.Binding.mix(
1469
+ 'MyApp.groupController.name', // The group name (may be null).
1470
+ 'MyApp.userController.fullName', // The user full name (may be null).
1471
+
1472
+ // Aggregate function. The arguments match the bound property values above.
1473
+ function (groupName, userFullName) {
1474
+ if (SC.none(userFullName)) {
1475
+ if (SC.none(groupName)) {
1476
+ return ''; // No group and no user.
1477
+ } else {
1478
+ return groupName; // Just a group.
1479
+ }
1480
+ } else {
1481
+ return '%@: %@'.fmt(groupName, userFullName); // Group and user.
1482
+ }
1483
+ })
1484
+
1485
+ })
1486
+
1487
+ *Note:* the number of parameters of `mixFunction` should match the number of paths provided.
1488
+ *Note:* the transform acts strictly as a one-way binding, working only in the one direction.
1489
+
1490
+ @param {String...} the paths of source values that will be provided to the aggregate function.
1491
+ @param {Function} mixFunction the function that aggregates the values
1492
+ */
1493
+ mix: function() {
1494
+ var len = arguments.length - 1,
1495
+ paths = new Array(len);
1496
+
1497
+ // Fast copy. The function is the last argument.
1498
+ for (var i = 0; i < len; i++) { paths[i] = arguments[i]; }
1499
+
1500
+ return this._sc_mixImpl(paths, arguments[len]);
1190
1501
  },
1191
1502
 
1192
1503
  /**
@@ -1231,4 +1542,3 @@ SC.Binding = /** @scope SC.Binding.prototype */{
1231
1542
  SC.binding(path) = SC.Binding.from(path)
1232
1543
  */
1233
1544
  SC.binding = function (path, root) { return SC.Binding.from(path, root); };
1234
-