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.
- checksums.yaml +8 -8
- data/CHANGELOG +10 -0
- data/VERSION.yml +1 -1
- data/lib/frameworks/sproutcore/CHANGELOG.md +114 -1
- data/lib/frameworks/sproutcore/apps/showcase/views/views_item_view.js +1 -7
- data/lib/frameworks/sproutcore/apps/showcase/views/views_list_view.js +9 -9
- data/lib/frameworks/sproutcore/frameworks/ajax/system/request.js +167 -5
- data/lib/frameworks/sproutcore/frameworks/ajax/system/response.js +24 -8
- data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/stack_layout.js +737 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/layout.js +0 -6
- data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +11 -7
- data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane_statechart.js +7 -11
- data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/child_view_layout_protocol.js +8 -3
- data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/observable_protocol.js +9 -6
- data/lib/frameworks/sproutcore/frameworks/{desktop/protocols/responder.js → core_foundation/protocols/responder_protocol.js} +83 -17
- data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/{sparse_array_delegate.js → sparse_array_delegate_protocol.js} +11 -7
- data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/view_transition_protocol.js +11 -6
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/color.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/page.js +0 -22
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +61 -56
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/main_pane.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/append_remove.js +3 -3
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/animation.js +63 -39
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/border_frame_test.js +28 -28
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/createLayer.js +10 -4
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layout.js +102 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutDidChange.js +4 -4
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +103 -103
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/replaceAllChildren_test.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view.js +77 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view_states_test.js +18 -17
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +42 -49
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/animation.js +5 -6
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/enabled.js +16 -5
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout.js +241 -102
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +1 -4
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/manipulation.js +0 -11
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/statechart.js +993 -610
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/theming.js +3 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/data_sources/data_source.js +6 -11
- data/lib/frameworks/sproutcore/frameworks/datastore/system/nested_store.js +94 -27
- data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +133 -53
- data/lib/frameworks/sproutcore/frameworks/datastore/system/store.js +30 -35
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/writeAttribute.js +3 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/writeDataHash.js +73 -29
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/conflictedStoreKeys_test.js +156 -0
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/dataSourceCallbacks.js +61 -37
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/find.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +68 -39
- data/lib/frameworks/sproutcore/frameworks/designer/tests/coders/page.js +1 -2
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/panel.js +8 -6
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +80 -14
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/sheet.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drag_data_source.js → drag_data_source_protocol.js} +16 -10
- data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drag_source.js → drag_source_protocol.js} +28 -26
- data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drop_target.js → drop_target_protocol.js} +73 -75
- data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -4
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/panel/ui.js +39 -23
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/mouse.js +120 -97
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/rowSizeForContentIndex.js +26 -25
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/ui.js +3 -3
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/segmented/ui.js +5 -0
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/split/{dividers.js → dividers_test.js} +38 -38
- data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +29 -14
- data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroll.js +2 -1
- data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll_view.js +13 -18
- data/lib/frameworks/sproutcore/frameworks/desktop/views/segmented.js +41 -35
- data/lib/frameworks/sproutcore/frameworks/desktop/views/slider.js +14 -14
- data/lib/frameworks/sproutcore/frameworks/desktop/views/split.js +41 -26
- data/lib/frameworks/sproutcore/frameworks/desktop/views/static_content.js +2 -12
- data/lib/frameworks/sproutcore/frameworks/foundation/gestures/tap.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/foundation/mixins/auto_resize.js +14 -10
- data/lib/frameworks/sproutcore/frameworks/foundation/mixins/gesturable.js +104 -63
- data/lib/frameworks/sproutcore/frameworks/foundation/protocols/swap_transition_protocol.js +9 -4
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_mixin_tests.js +1 -2
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_resize_test.js +33 -33
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/transitions/view_transitions_test.js +5 -5
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/methods.js +0 -4
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/transition_test.js +0 -4
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/ui.js +0 -2
- data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +12 -8
- data/lib/frameworks/sproutcore/frameworks/media/resources/silence.mp3 +0 -0
- data/lib/frameworks/sproutcore/frameworks/media/tests/audio.js +69 -0
- data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +1 -0
- data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/runtime/mixins/observable.js +11 -4
- data/lib/frameworks/sproutcore/frameworks/runtime/protocols/mixin_protocol.js +150 -0
- data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +447 -137
- data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +9 -15
- data/lib/frameworks/sproutcore/frameworks/runtime/system/set.js +19 -17
- data/lib/frameworks/sproutcore/frameworks/runtime/tests/system/binding.js +188 -16
- data/lib/frameworks/sproutcore/frameworks/statechart/system/state.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/template_view/panes/template.js +0 -3
- data/lib/frameworks/sproutcore/frameworks/template_view/tests/panes/template.js +0 -17
- data/lib/frameworks/sproutcore/frameworks/template_view/tests/views/template/collection.js +43 -26
- data/lib/frameworks/sproutcore/frameworks/template_view/views/bindable_span.js +9 -2
- data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list.css +0 -1
- data/lib/frameworks/sproutcore/themes/ace/resources/scroll/scroll.css +3 -0
- data/sproutcore.gemspec +3 -3
- metadata +19 -17
- data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/horizontal_stack_layout.js +0 -465
- data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/vertical_stack_layout.js +0 -472
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/build.js +0 -87
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/build_children.js +0 -89
data/lib/frameworks/sproutcore/frameworks/foundation/tests/transitions/view_transitions_test.js
CHANGED
@@ -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
|
-
|
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);
|
@@ -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
|
1263
|
-
//
|
1264
|
-
//
|
1265
|
-
this
|
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
|
1291
|
-
//
|
1292
|
-
//
|
1293
|
-
this
|
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
|
|
Binary file
|
@@ -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.
|
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.
|
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
|
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
|
-
|
1166
|
-
|
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 =
|
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
|
-
|
283
|
-
|
284
|
-
|
285
|
-
ret = ret.
|
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
|
497
|
-
if (this.
|
498
|
-
this.
|
499
|
-
this.
|
500
|
-
this.
|
501
|
-
this.
|
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
|
865
|
-
if (this.
|
866
|
-
this.
|
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
|
981
|
-
|
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
|
-
|
989
|
-
|
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
|
-
|
992
|
-
|
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
|
995
|
-
@param {Object} [placeholder] placeholder value.
|
1106
|
+
@param {String} [fromPath]
|
996
1107
|
@returns {SC.Binding} this
|
997
1108
|
*/
|
998
|
-
|
999
|
-
|
1000
|
-
|
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
|
1047
|
-
|
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
|
-
|
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
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
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
|
1062
|
-
an
|
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
|
-
|
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
|
-
|
1078
|
-
|
1189
|
+
* null => ''
|
1190
|
+
* undefined => ''
|
1191
|
+
* 123 => '123'
|
1192
|
+
* true => 'true'
|
1193
|
+
* {} => '[object Object]' (i.e. x = {}; return x.toString())
|
1079
1194
|
|
1080
|
-
@param {String}
|
1195
|
+
@param {String} fromPath from path or null
|
1081
1196
|
@returns {SC.Binding} this
|
1082
1197
|
*/
|
1083
|
-
|
1084
|
-
return this.from(fromPath).transform(function (
|
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
|
-
|
1201
|
+
// Null or undefined will be converted to an empty string.
|
1202
|
+
if (SC.none(value)) {
|
1203
|
+
value = '';
|
1093
1204
|
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
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
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
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
|
-
|
1122
|
-
|
1123
|
-
|
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
|
-
|
1126
|
-
|
1127
|
-
|
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
|
-
|
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('
|
1140
|
-
|
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.
|
1143
|
-
ret.
|
1144
|
-
ret.
|
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
|
1152
|
-
|
1153
|
-
|
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
|
-
|
1320
|
+
// Whether the list has a selection or not.
|
1321
|
+
listHasSelectionBinding: SC.Binding.oneWay('MyApp.itemsController.hasSelection'),
|
1156
1322
|
|
1157
|
-
|
1158
|
-
|
1323
|
+
// Whether the user can delete items or not.
|
1324
|
+
userCanDeleteBinding: SC.Binding.oneWay('MyApp.userController.canDelete'),
|
1159
1325
|
|
1160
|
-
|
1161
|
-
|
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
|
-
|
1165
|
-
|
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 (
|
1168
|
-
|
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
|
1173
|
-
|
1174
|
-
|
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
|
-
|
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
|
-
|
1179
|
-
content is editable, or if the user has admin rights:
|
1393
|
+
})
|
1180
1394
|
|
1181
|
-
|
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
|
-
|
1186
|
-
|
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 (
|
1189
|
-
|
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
|
-
|