sproutcore 1.11.0.rc1 → 1.11.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +8 -8
  2. data/VERSION.yml +1 -1
  3. data/lib/frameworks/sproutcore/CHANGELOG.md +93 -65
  4. data/lib/frameworks/sproutcore/apps/showcase/controllers/source_tree_controller.js +17 -7
  5. data/lib/frameworks/sproutcore/apps/showcase/resources/main_page.js +22 -2
  6. data/lib/frameworks/sproutcore/apps/showcase/resources/stylesheet.css +14 -0
  7. data/lib/frameworks/sproutcore/apps/showcase/resources/views_page.js +2 -2
  8. data/lib/frameworks/sproutcore/frameworks/ajax/system/request.js +20 -8
  9. data/lib/frameworks/sproutcore/frameworks/ajax/system/websocket.js +58 -43
  10. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/action_support.js +192 -35
  11. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/delegate_support.js +7 -3
  12. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/responder_context.js +27 -7
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/browser.js +20 -63
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/color.js +16 -7
  15. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +279 -159
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/render_context.js +1 -1
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +21 -10
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/action_support.js +32 -28
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/root_responder/targetForAction.js +107 -90
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/root_responder/touch.js +33 -25
  21. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/touch.js +23 -15
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/animation.js +1 -1
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout.js +12 -6
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +55 -33
  25. data/lib/frameworks/sproutcore/frameworks/datastore/system/store.js +7 -1
  26. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/methods.js +228 -72
  27. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/touch.js +54 -100
  28. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/segmented/ui.js +15 -0
  29. data/lib/frameworks/sproutcore/frameworks/desktop/views/button.js +57 -45
  30. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +9 -16
  31. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll_view.js +111 -44
  32. data/lib/frameworks/sproutcore/frameworks/desktop/views/segmented.js +2 -0
  33. data/lib/frameworks/sproutcore/frameworks/foundation/system/module.js +51 -5
  34. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
  35. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/observable.js +13 -10
  36. data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +2 -1
  37. data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +28 -9
  38. data/lib/frameworks/sproutcore/frameworks/runtime/system/set.js +7 -5
  39. data/lib/frameworks/sproutcore/frameworks/statechart/ext/function.js +51 -46
  40. data/lib/frameworks/sproutcore/frameworks/statechart/system/state.js +8 -5
  41. data/lib/frameworks/sproutcore/frameworks/statechart/system/statechart.js +12 -3
  42. metadata +2 -2
@@ -71,12 +71,16 @@ SC.DelegateSupport = {
71
71
 
72
72
  @param {Object} delegate a delegate object. May be null.
73
73
  @param {String} methodName a method name
74
- @param {Object...} args (OPTIONAL) any additional arguments
74
+ @param {Object...} [args...] any additional arguments
75
75
 
76
76
  @returns {Object} value returned by delegate
77
77
  */
78
- invokeDelegateMethod: function(delegate, methodName, args) {
79
- args = SC.A(arguments); args = args.slice(2, args.length) ;
78
+ invokeDelegateMethod: function(delegate, methodName) {
79
+ // Fast arguments access.
80
+ // Accessing `arguments.length` is just a Number and doesn't materialize the `arguments` object, which is costly.
81
+ var args = new Array(arguments.length - 2); // SC.A(arguments).slice(2)
82
+ for (var i = 0, len = args.length; i < len; i++) { args[i] = arguments[i + 2]; }
83
+
80
84
  if (!delegate || !delegate[methodName]) delegate = this ;
81
85
 
82
86
  var method = delegate[methodName];
@@ -25,11 +25,8 @@ sc_require('system/responder');
25
25
  */
26
26
  SC.ResponderContext = {
27
27
 
28
- // ..........................................................
29
- // PROPERTIES
30
- //
31
-
32
- isResponderContext: YES,
28
+ //@if(debug)
29
+ /* BEGIN DEBUG ONLY PROPERTIES AND METHODS */
33
30
 
34
31
  /** @property
35
32
 
@@ -38,6 +35,15 @@ SC.ResponderContext = {
38
35
  */
39
36
  trace: NO,
40
37
 
38
+ /* END DEBUG ONLY PROPERTIES AND METHODS */
39
+ //@endif
40
+
41
+ // ..........................................................
42
+ // PROPERTIES
43
+ //
44
+
45
+ isResponderContext: YES,
46
+
41
47
  /** @property
42
48
  The default responder. Set this to point to a responder object that can
43
49
  respond to events when no other view in the hierarchy handles them.
@@ -90,6 +96,7 @@ SC.ResponderContext = {
90
96
  return responder._scrc_name || responder.toString(); // try again
91
97
  },
92
98
 
99
+ /** @private */
93
100
  _findResponderNamesFor: function(responder, level, path) {
94
101
  var key, value;
95
102
 
@@ -122,21 +129,27 @@ SC.ResponderContext = {
122
129
  makeFirstResponder: function(responder, evt) {
123
130
  var current = this.get('firstResponder'),
124
131
  last = this.get('nextResponder'),
132
+ //@if(debug)
125
133
  trace = this.get('trace'),
134
+ //@endif
126
135
  common ;
127
136
 
128
137
  if (this._locked) {
138
+ //@if(debug)
129
139
  if (trace) {
130
140
  SC.Logger.log('%@: AFTER ACTION: makeFirstResponder => %@'.fmt(this, this.responderNameFor(responder)));
131
141
  }
142
+ //@endif
132
143
 
133
144
  this._pendingResponder = responder;
134
145
  return ;
135
146
  }
136
147
 
148
+ //@if(debug)
137
149
  if (trace) {
138
150
  SC.Logger.log('%@: makeFirstResponder => %@'.fmt(this, this.responderNameFor(responder)));
139
151
  }
152
+ //@endif
140
153
 
141
154
  if (responder) responder.set("becomingFirstResponder", YES);
142
155
 
@@ -167,7 +180,7 @@ SC.ResponderContext = {
167
180
  if (responder) responder.set('isFirstResponder', YES);
168
181
 
169
182
  this._notifyDidBecomeFirstResponder(responder, responder, common);
170
-
183
+
171
184
  // now, tell everyone the good news!
172
185
  this.endPropertyChanges();
173
186
 
@@ -220,20 +233,25 @@ SC.ResponderContext = {
220
233
 
221
234
  @param {String} action name of action
222
235
  @param {Object} sender object sending the action
223
- @param {Object} context optional additional context info
236
+ @param {Object} [context] additional context info
224
237
  @returns {SC.Responder} the responder that handled it or null
225
238
  */
226
239
  sendAction: function(action, sender, context) {
227
240
  var working = this.get('firstResponder'),
228
241
  last = this.get('nextResponder'),
242
+ //@if(debug)
229
243
  trace = this.get('trace'),
244
+ //@endif
230
245
  handled = NO,
231
246
  responder;
232
247
 
233
248
  this._locked = YES;
249
+
250
+ //@if(debug)
234
251
  if (trace) {
235
252
  SC.Logger.log("%@: begin action '%@' (%@, %@)".fmt(this, action, sender, context));
236
253
  }
254
+ //@endif
237
255
 
238
256
  if (!handled && !working && this.tryToPerform) {
239
257
  handled = this.tryToPerform(action, sender, context);
@@ -249,10 +267,12 @@ SC.ResponderContext = {
249
267
  }
250
268
  }
251
269
 
270
+ //@if(debug)
252
271
  if (trace) {
253
272
  if (!handled) SC.Logger.log("%@: action '%@' NOT HANDLED".fmt(this,action));
254
273
  else SC.Logger.log("%@: action '%@' handled by %@".fmt(this, action, this.responderNameFor(working)));
255
274
  }
275
+ //@endif
256
276
 
257
277
  this._locked = NO ;
258
278
 
@@ -83,48 +83,6 @@ SC.mixin(SC.browser,
83
83
  return 0;
84
84
  },
85
85
 
86
- /**
87
- @deprecated Since 1.7. Use SC.browser.compare(version, otherVersion) instead.
88
-
89
- Pass any number of arguments, and this will check them against the browser
90
- version split on ".". If any of them are not equal, return the inequality.
91
- If as many arguments as were passed in are equal, return 0. If something
92
- is NaN, return 0.
93
- */
94
-
95
- // Deprecation Note:
96
- //
97
- // This function forces the comparison against the value of
98
- // SC.browser.version, but the old value of SC.browser.version would
99
- // occasionally be the browser's version or the layout engine's version,
100
- // which could cause unexpected results. As well, there was no way to
101
- // compare the actual browser version or OS version.
102
- compareVersion: function () {
103
- //@if(debug)
104
- SC.warn('Developer Warning: SC.browser.compareVersion() has been deprecated. Please ' +
105
- 'use SC.browser.compare() instead. Example: ' +
106
- 'SC.browser.compareVersion(16,0,912) < 0 becomes ' +
107
- 'SC.browser.compare(SC.browser.engineVersion, \'16.0.912\').');
108
- //@endif
109
-
110
- if (this._versionSplit === undefined) {
111
- var coerce = function (part) {
112
- return Number(part.match(/^[0-9]+/));
113
- };
114
- this._versionSplit = SC.A(this.version.split('.')).map(coerce);
115
- }
116
-
117
- var tests = SC.A(arguments).map(Number);
118
- for (var i = 0; i < tests.length; i++) {
119
- var check = this._versionSplit[i] - tests[i];
120
- if (isNaN(check)) return 0;
121
- if (check !== 0) return check;
122
- }
123
-
124
- return 0;
125
- },
126
-
127
-
128
86
  /**
129
87
  This simple method allows you to more safely use experimental properties and
130
88
  methods in current and future browsers.
@@ -180,19 +138,6 @@ SC.mixin(SC.browser,
180
138
  @returns {string} The name of the property or method on the target or SC.UNSUPPORTED if no method found.
181
139
  */
182
140
  experimentalNameFor: function (target, standardName, testValue) {
183
- var cachedNames = this._cachedNames,
184
- targetGuid = SC.guidFor(target);
185
-
186
- // Fast path & cache initialization.
187
- if (!cachedNames) {
188
- cachedNames = this._cachedNames = {};
189
- cachedNames[targetGuid] = {};
190
- } else if (!cachedNames[targetGuid]) {
191
- cachedNames[targetGuid] = {};
192
- } else if (cachedNames[targetGuid][standardName]) {
193
- return cachedNames[targetGuid][standardName];
194
- }
195
-
196
141
  // Test the property name.
197
142
  var ret = standardName;
198
143
 
@@ -220,9 +165,6 @@ SC.mixin(SC.browser,
220
165
  }
221
166
  }
222
167
 
223
- // Cache the experimental property name (even SC.UNSUPPORTED) for quick repeat access.
224
- cachedNames[targetGuid][standardName] = ret;
225
-
226
168
  return ret;
227
169
  },
228
170
 
@@ -262,13 +204,28 @@ SC.mixin(SC.browser,
262
204
  @returns {string} Future-proof style name for use in the current browser or SC.UNSUPPORTED if no style support found.
263
205
  */
264
206
  experimentalStyleNameFor: function (standardStyleName, testValue) {
265
- // Test the style name.
266
- var el = this._testEl;
207
+ var cachedNames = this._sc_experimentalStyleNames,
208
+ ret;
209
+
210
+ // Fast path & cache initialization.
211
+ if (!cachedNames) {
212
+ cachedNames = this._sc_experimentalStyleNames = {};
213
+ }
214
+
215
+ if (cachedNames[standardStyleName]) {
216
+ ret = cachedNames[standardStyleName];
217
+ } else {
218
+ // Test the style name.
219
+ var el = this._testEl;
267
220
 
268
- // Create a test element and cache it for repeated use.
269
- if (!el) { el = this._testEl = document.createElement("div"); }
221
+ // Create a test element and cache it for repeated use.
222
+ if (!el) { el = this._testEl = document.createElement("div"); }
270
223
 
271
- return this.experimentalNameFor(el.style, standardStyleName, testValue);
224
+ // Cache the experimental style name (even SC.UNSUPPORTED) for quick repeat access.
225
+ ret = cachedNames[standardStyleName] = this.experimentalNameFor(el.style, standardStyleName, testValue);
226
+ }
227
+
228
+ return ret;
272
229
  },
273
230
 
274
231
  /**
@@ -144,7 +144,7 @@
144
144
  if (SC.Color.supportsARGB) {
145
145
  var gradient = "progid:DXImageTransform.Microsoft.gradient";
146
146
  css = ("-ms-filter:" + gradient + "(startColorstr=%@1,endColorstr=%@1);" +
147
- "filter:" + gradient + "(startColorstr=%@1,endColorstr=%@1)" +
147
+ "filter:" + gradient + "(startColorstr=%@1,endColorstr=%@1)" +
148
148
  "zoom: 1").fmt(color);
149
149
  } else {
150
150
  css = "background-color:" + color;
@@ -482,7 +482,7 @@ SC.Color = SC.Object.extend(
482
482
  */
483
483
  cssText: function (key, value) {
484
484
  // Getter.
485
- if (value === undefined) {
485
+ if (value === undefined) {
486
486
  // FAST PATH: Error.
487
487
  if (this.get('isError')) return this.get('errorValue');
488
488
 
@@ -641,16 +641,22 @@ SC.Color.mixin(
641
641
 
642
642
  /** @private Overrides create to support creation with {a, r, g, b} hash. */
643
643
  create: function() {
644
- var args = SC.A(arguments),
645
- len = args.length,
646
- vals = {},
644
+ var vals = {},
647
645
  hasVals = NO,
648
646
  keys = ['a', 'r', 'g', 'b'],
647
+ args, len,
649
648
  hash, i, k, key;
649
+
650
+
651
+ // Fast arguments access.
652
+ // Accessing `arguments.length` is just a Number and doesn't materialize the `arguments` object, which is costly.
653
+ args = new Array(arguments.length); // SC.A(arguments)
654
+ len = args.length;
655
+
650
656
  // Loop through all arguments. If any of them contain numeric a, r, g or b arguments,
651
657
  // clone the hash and move the value from (e.g.) a to _a.
652
658
  for (i = 0; i < len; i++) {
653
- hash = args[i];
659
+ hash = arguments[i];
654
660
  if (SC.typeOf(hash.a) === SC.T_NUMBER
655
661
  || SC.typeOf(hash.r) === SC.T_NUMBER
656
662
  || SC.typeOf(hash.g) === SC.T_NUMBER
@@ -665,8 +671,11 @@ SC.Color.mixin(
665
671
  delete hash[key];
666
672
  }
667
673
  }
674
+ } else {
675
+ args[i] = hash;
668
676
  }
669
677
  }
678
+
670
679
  if (hasVals) args.push(vals);
671
680
  return SC.Object.create.apply(this, args);
672
681
  },
@@ -921,7 +930,7 @@ SC.Color.mixin(
921
930
 
922
931
  // ..........................................................
923
932
  // Regular expressions for accepted color types
924
- //
933
+ //
925
934
  PARSE_RGBA: /^rgba\(\s*([\d]+%?)\s*,\s*([\d]+%?)\s*,\s*([\d]+%?)\s*,\s*([.\d]+)\s*\)$/,
926
935
  PARSE_RGB : /^rgb\(\s*([\d]+%?)\s*,\s*([\d]+%?)\s*,\s*([\d]+%?)\s*\)$/,
927
936
  PARSE_HSLA: /^hsla\(\s*(-?[\d]+)\s*\s*,\s*([\d]+)%\s*,\s*([\d]+)%\s*,\s*([.\d]+)\s*\)$/,
@@ -28,90 +28,10 @@ sc_require('system/core_query') ;
28
28
  @since SproutCore 1.0
29
29
  */
30
30
  SC.Event = function(originalEvent) {
31
- var idx, len;
32
- // copy properties from original event, if passed in.
33
- if (originalEvent) {
34
- this.originalEvent = originalEvent ;
35
- var props = SC.Event._props, key;
36
- len = props.length;
37
- idx = len;
38
- while(--idx >= 0) {
39
- key = props[idx] ;
40
- this[key] = originalEvent[key] ;
41
- }
42
- }
43
-
44
- // Fix timeStamp
45
- this.timeStamp = this.timeStamp || Date.now();
46
-
47
- // Fix target property, if necessary
48
- // Fixes #1925 where srcElement might not be defined either
49
- if (!this.target) this.target = this.srcElement || document;
50
-
51
- // check if target is a textnode (safari)
52
- if (this.target.nodeType === 3 ) this.target = this.target.parentNode;
53
-
54
- // Add relatedTarget, if necessary
55
- if (!this.relatedTarget && this.fromElement) {
56
- this.relatedTarget = (this.fromElement === this.target) ? this.toElement : this.fromElement;
57
- }
58
-
59
- // Calculate pageX/Y if missing and clientX/Y available
60
- if (SC.none(this.pageX) && !SC.none(this.clientX)) {
61
- var doc = document.documentElement, body = document.body;
62
- this.pageX = this.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
63
- this.pageY = this.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
64
- }
65
-
66
- // Add which for key events
67
- if (!this.which && ((this.charCode || originalEvent.charCode === 0) ? this.charCode : this.keyCode)) {
68
- this.which = this.charCode || this.keyCode;
69
- }
70
-
71
- // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
72
- if (!this.metaKey && this.ctrlKey) this.metaKey = this.ctrlKey;
73
-
74
- // Add which for click: 1 == left; 2 == middle; 3 == right
75
- // Note: button is not normalized, so don't use it
76
- if (!this.which && this.button) {
77
- this.which = ((this.button & 1) ? 1 : ((this.button & 2) ? 3 : ( (this.button & 4) ? 2 : 0 ) ));
78
- }
79
-
80
- // Normalize wheel delta values for mousewheel events
81
- if (this.type === 'mousewheel' || this.type === 'DOMMouseScroll' || this.type === 'MozMousePixelScroll') {
82
- var deltaMultiplier = SC.Event.MOUSE_WHEEL_MULTIPLIER;
83
-
84
- // normalize wheelDelta, wheelDeltaX, & wheelDeltaY for Safari
85
- if (SC.browser.isWebkit && originalEvent.wheelDelta !== undefined) {
86
- this.wheelDelta = 0-(originalEvent.wheelDeltaY || originalEvent.wheelDeltaX);
87
- this.wheelDeltaY = 0-(originalEvent.wheelDeltaY||0);
88
- this.wheelDeltaX = 0-(originalEvent.wheelDeltaX||0);
89
-
90
- // normalize wheelDelta for Firefox (all Mozilla browsers)
91
- // note that we multiple the delta on FF to make it's acceleration more
92
- // natural.
93
- } else if (!SC.none(originalEvent.detail) && SC.browser.isMozilla) {
94
- if (originalEvent.axis && (originalEvent.axis === originalEvent.HORIZONTAL_AXIS)) {
95
- this.wheelDeltaX = originalEvent.detail;
96
- this.wheelDeltaY = this.wheelDelta = 0;
97
- } else {
98
- this.wheelDeltaY = this.wheelDelta = originalEvent.detail ;
99
- this.wheelDeltaX = 0 ;
100
- }
101
-
102
- // handle all other legacy browser
103
- } else {
104
- this.wheelDelta = this.wheelDeltaY = SC.browser.isIE || SC.browser.isOpera ? 0-originalEvent.wheelDelta : originalEvent.wheelDelta ;
105
- this.wheelDeltaX = 0 ;
106
- }
107
-
108
- this.wheelDelta *= deltaMultiplier;
109
- this.wheelDeltaX *= deltaMultiplier;
110
- this.wheelDeltaY *= deltaMultiplier;
111
- }
31
+ this._sc_updateNormalizedEvent(originalEvent);
112
32
 
113
33
  return this;
114
- } ;
34
+ };
115
35
 
116
36
  SC.mixin(SC.Event, /** @scope SC.Event */ {
117
37
 
@@ -187,7 +107,7 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
187
107
  named element. You can optionally pass an additional context object which
188
108
  will be included on the event in the event.data property.
189
109
 
190
- When your handler function is called the, the function's "this" property
110
+ When your handler function is called, the function's "this" property
191
111
  will point to the element the event occurred on.
192
112
 
193
113
  The click handler for this method must have a method signature like:
@@ -260,21 +180,24 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
260
180
 
261
181
  // if target is a function, treat it as the method, with optional context
262
182
  if (SC.typeOf(target) === SC.T_FUNCTION) {
263
- context = method; method = target; target = null;
183
+ context = method;
184
+ method = target;
185
+ target = null;
264
186
 
265
187
  // handle case where passed method is a key on the target.
266
188
  } else if (target && SC.typeOf(method) === SC.T_STRING) {
267
- method = target[method] ;
189
+ method = target[method];
268
190
  }
269
191
 
270
192
  // Get the handlers queue for this element/eventType. If the queue does
271
193
  // not exist yet, create it and also setup the shared listener for this
272
194
  // eventType.
273
- var events = SC.data(elem, "sc_events") || SC.data(elem, "sc_events", {}) ,
195
+ var events = SC.data(elem, "sc_events") || SC.data(elem, "sc_events", {}),
274
196
  handlers = events[eventType];
197
+
275
198
  if (!handlers) {
276
- handlers = events[eventType] = {} ;
277
- this._addEventListener(elem, eventType, useCapture) ;
199
+ handlers = events[eventType] = {};
200
+ this._addEventListener(elem, eventType, useCapture);
278
201
  }
279
202
 
280
203
  // Build the handler array and add to queue
@@ -420,7 +343,8 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
420
343
  timeStamp: Date.now(),
421
344
  bubbles: (this.NO_BUBBLE.indexOf(eventType)<0),
422
345
  cancelled: NO,
423
- normalized: YES
346
+ normalized: YES,
347
+ simulated: true
424
348
  });
425
349
  if (attrs) SC.mixin(ret, attrs) ;
426
350
  return ret ;
@@ -445,18 +369,18 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
445
369
 
446
370
  @param elem {Element} the target element
447
371
  @param eventType {String} the event type
448
- @param args {Array} optional argument or arguments to pass to handler.
372
+ @param event {SC.Event} [event] pre-normalized event to pass to handler
449
373
  @param donative ??
450
374
  @returns {Boolean} Return value of trigger or undefined if not fired
451
375
  */
452
- trigger: function(elem, eventType, args, donative) {
376
+ trigger: function(elem, eventType, event, donative) {
453
377
 
454
378
  // if a CQ object is passed in, either call add on each item in the
455
379
  // matched set, or simply get the first element and use that.
456
380
  if (elem && elem.isCoreQuery) {
457
381
  if (elem.length > 0) {
458
382
  elem.forEach(function(e) {
459
- this.trigger(e, eventType, args, donative);
383
+ this.trigger(e, eventType, event, donative);
460
384
  }, this);
461
385
  return this;
462
386
  } else elem = elem[0];
@@ -466,25 +390,23 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
466
390
  // don't do events on text and comment nodes
467
391
  if ( elem.nodeType === 3 || elem.nodeType === 8 ) return undefined;
468
392
 
469
- // Normalize to an array
470
- args = SC.A(args) ;
393
+ // Backwards-compatibility. Normalize from an Array.
394
+ if (SC.typeOf(event) === SC.T_ARRAY) { event = event[0]; }
471
395
 
472
396
  var ret, fn = SC.typeOf(elem[eventType] || null) === SC.T_FUNCTION ,
473
- event, current, onfoo, isClick;
397
+ current, onfoo, isClick;
474
398
 
475
399
  // Get the event to pass, creating a fake one if necessary
476
- event = args[0];
477
400
  if (!event || !event.preventDefault) {
478
- event = this.simulateEvent(elem, eventType) ;
479
- args.unshift(event) ;
401
+ event = this.simulateEvent(elem, eventType);
480
402
  }
481
403
 
482
- event.type = eventType ;
404
+ event.type = eventType;
483
405
 
484
406
  // Trigger the event - bubble if enabled
485
407
  current = elem;
486
408
  do {
487
- ret = SC.Event.handle.apply(current, args);
409
+ ret = SC.Event.handle.call(current, event);
488
410
  current = (current===document) ? null : (current.parentNode || document);
489
411
  } while(!ret && event.bubbles && current);
490
412
  current = null ;
@@ -492,7 +414,7 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
492
414
  // Handle triggering native .onfoo handlers
493
415
  onfoo = elem["on" + eventType] ;
494
416
  isClick = SC.$.nodeName(elem, 'a') && eventType === 'click';
495
- if ((!fn || isClick) && onfoo && onfoo.apply(elem, args) === NO) ret = NO;
417
+ if ((!fn || isClick) && onfoo && onfoo.call(elem, event) === NO) ret = NO;
496
418
 
497
419
  // Trigger the native events (except for clicks on links)
498
420
  if (fn && donative !== NO && ret !== NO && !isClick) {
@@ -520,50 +442,55 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
520
442
  Note that like other parts of this library, the handle function does not
521
443
  support namespaces.
522
444
 
523
- @param event {Event} the event to handle
445
+ @param event {DOMEvent} the event to handle
524
446
  @returns {Boolean}
525
447
  */
526
- handle: function(event) {
448
+ handle: function (event) {
527
449
 
528
450
  // ignore events triggered after window is unloaded or if double-called
529
451
  // from within a trigger.
530
- if ((typeof SC === "undefined") || SC.Event.triggered) return YES ;
531
-
532
- // returned undefined or NO
533
- var val, ret, handlers, args, key, handler, method, target;
452
+ if ((typeof SC === "undefined") || SC.Event.triggered) return true;
534
453
 
535
- // normalize event across browsers. The new event will actually wrap the
536
- // real event with a normalized API.
537
- args = SC.A(arguments);
538
- args[0] = event = SC.Event.normalizeEvent(event || window.event);
454
+ // returned undefined or false
455
+ var val, ret, handlers, method, target;
539
456
 
540
457
  // get the handlers for this event type
541
458
  handlers = (SC.data(this, "sc_events") || {})[event.type];
542
- if (!handlers) return NO ; // nothing to do
543
-
544
- // invoke all handlers
545
- for (key in handlers ) {
546
- handler = handlers[key];
547
- // handler = [target, method, context]
548
- method = handler[1];
549
-
550
- // Pass in a reference to the handler function itself
551
- // So that we can later remove it
552
- event.handler = method;
553
- event.data = event.context = handler[2];
554
-
555
- target = handler[0] || this;
556
- ret = method.apply( target, args );
557
-
558
- if (val !== NO) val = ret;
559
-
560
- // if method returned NO, do not continue. Stop propagation and
561
- // return default. Note that we test explicitly for NO since
562
- // if the handler returns no specific value, we do not want to stop.
563
- if ( ret === NO ) {
564
- event.preventDefault();
565
- event.stopPropagation();
459
+
460
+ // no handlers for the event
461
+ if (!handlers) {
462
+ val = false; // nothing to do
463
+
464
+ } else {
465
+ // normalize event across browsers. The new event will actually wrap the real event with a normalized API.
466
+ event = SC.Event.normalizeEvent(event || window.event);
467
+
468
+ // invoke all handlers
469
+ for (var key in handlers) {
470
+ var handler = handlers[key];
471
+
472
+ method = handler[1];
473
+
474
+ // Pass in a reference to the handler function itself so that we can remove it later.
475
+ event.handler = method;
476
+ event.data = event.context = handler[2];
477
+
478
+ target = handler[0] || this;
479
+ ret = method.call(target, event);
480
+
481
+ if (val !== false) val = ret;
482
+
483
+ // if method returned NO, do not continue. Stop propagation and
484
+ // return default. Note that we test explicitly for NO since
485
+ // if the handler returns no specific value, we do not want to stop.
486
+ if ( ret === false ) {
487
+ event.preventDefault();
488
+ event.stopPropagation();
489
+ }
566
490
  }
491
+
492
+ // Clean up the cached normalized SC.Event so that it's not holding onto extra memory.
493
+ if (event.originalEvent && !event.originalEvent.simulated) { event._sc_clearNormalizedEvent(); }
567
494
  }
568
495
 
569
496
  return val;
@@ -619,12 +546,13 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
619
546
  return YES;
620
547
  },
621
548
 
622
- handler: function(event) {
549
+ handler: function (event) {
623
550
  // If we actually just moused on to a sub-element, ignore it
624
551
  if ( SC.Event._withinElement(event, this) ) return YES;
625
552
  // Execute the right handlers by setting the event type to mouseenter
626
553
  event.type = "mouseenter";
627
- return SC.Event.handle.apply(this, arguments);
554
+
555
+ return SC.Event.handle.call(this, event);
628
556
  }
629
557
  },
630
558
 
@@ -643,12 +571,12 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
643
571
  return YES;
644
572
  },
645
573
 
646
- handler: function(event) {
574
+ handler: function (event) {
647
575
  // If we actually just moused on to a sub-element, ignore it
648
576
  if ( SC.Event._withinElement(event, this) ) return YES;
649
577
  // Execute the right handlers by setting the event type to mouseleave
650
578
  event.type = "mouseleave";
651
- return SC.Event.handle.apply(this, arguments);
579
+ return SC.Event.handle.call(this, event);
652
580
  }
653
581
  }
654
582
  },
@@ -693,16 +621,17 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
693
621
  @param eventType {String} the event type
694
622
  */
695
623
  _addEventListener: function(elem, eventType, useCapture) {
696
- var listener, special = this.special[eventType] ;
624
+ var listener,
625
+ special = this.special[eventType] ;
697
626
 
698
627
  if (!useCapture) {
699
- useCapture = NO;
628
+ useCapture = false;
700
629
  }
701
630
 
702
631
  // Check for a special event handler
703
632
  // Only use addEventListener/attachEvent if the special
704
- // events handler returns NO
705
- if ( !special || special.setup.call(elem)===NO) {
633
+ // events handler returns false
634
+ if ( !special || special.setup.call(elem) === false) {
706
635
 
707
636
  // Save element in cache. This must be removed later to avoid
708
637
  // memory leaks.
@@ -711,9 +640,9 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
711
640
 
712
641
  // Either retrieve the previously cached listener or cache a new one.
713
642
  listener = SC.data(elem, "listener") || SC.data(elem, "listener",
714
- function() {
715
- return SC.Event.handle.apply(SC.Event._elements[guid], arguments);
716
- }) ;
643
+ function handle_event (event) {
644
+ return SC.Event.handle.call(SC.Event._elements[guid], event);
645
+ });
717
646
 
718
647
  // Bind the global event handler to the element
719
648
  if (elem.addEventListener) {
@@ -723,13 +652,9 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
723
652
  // there is currently a hack in request , but it needs to fixed here.
724
653
  elem.attachEvent("on" + eventType, listener);
725
654
  }
726
- //
727
- // else {
728
- // elem.onreadystatechange = listener;
729
- // }
730
655
  }
731
656
 
732
- elem = special = listener = null ; // avoid memory leak
657
+ elem = special = listener = null; // avoid memory leak
733
658
  },
734
659
 
735
660
  /** @private
@@ -764,24 +689,51 @@ SC.mixin(SC.Event, /** @scope SC.Event */ {
764
689
 
765
690
  _elements: {},
766
691
 
692
+ _sc_normalizedEvents: null,
693
+
767
694
  // implement preventDefault() in a cross platform way
768
695
 
769
696
  /** @private Take an incoming event and convert it to a normalized event. */
770
- normalizeEvent: function(event) {
771
- if (event === window.event) {
772
- // IE can't do event.normalized on an Event object
773
- return SC.Event.create(event) ;
697
+ normalizeEvent: function (event) {
698
+ var ret;
699
+
700
+ // Create the cache the first time.
701
+ if (!this._sc_normalizedEvents) { this._sc_normalizedEvents = {}; }
702
+ ret = this._sc_normalizedEvents[event.type];
703
+
704
+ // Create a new normalized SC.Event.
705
+ if (!ret) {
706
+ if (event === window.event) {
707
+ // IE can't do event.normalized on an Event object
708
+ ret = SC.Event.create(event) ;
709
+ } else {
710
+ ret = event.normalized ? event : SC.Event.create(event) ;
711
+ }
712
+
713
+ // When passed an SC.Event, don't re-normalize it.
714
+ // TODO: This is hacky nonsense left over from a whole pile of bad decisions in SC.Event—
715
+ } else if (event.normalized) {
716
+ ret = event;
717
+
718
+ // Update the cached normalized SC.Event with the new DOM event.
774
719
  } else {
775
- return event.normalized ? event : SC.Event.create(event) ;
720
+ ret._sc_updateNormalizedEvent(event);
776
721
  }
722
+
723
+ // Cache the normalized event object for this type of event. This allows us to avoid recreating
724
+ // SC.Event objects constantly for noisy events such as 'mousemove' or 'mousewheel'.
725
+ this._sc_normalizedEvents[event.type] = ret;
726
+
727
+ return ret;
777
728
  },
778
729
 
779
730
  _global: {},
780
731
 
781
732
  /** @private properties to copy from native event onto the event */
782
- _props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view which touches targetTouches changedTouches animationName elapsedTime dataTransfer".split(" ")
733
+ // TODO: Remove this needless copy.
734
+ _props: ['altKey', 'attrChange', 'attrName', 'bubbles', 'button', 'cancelable', 'charCode', 'clientX', 'clientY', 'ctrlKey', 'currentTarget', 'data', 'detail', 'fromElement', 'handler', 'keyCode', 'metaKey', 'newValue', 'originalTarget', 'pageX', 'pageY', 'prevValue', 'relatedNode', 'relatedTarget', 'screenX', 'screenY', 'shiftKey', 'srcElement', 'target', 'timeStamp', 'toElement', 'type', 'view', 'which', 'touches', 'targetTouches', 'changedTouches', 'animationName', 'elapsedTime', 'dataTransfer']
783
735
 
784
- }) ;
736
+ });
785
737
 
786
738
  SC.Event.prototype = {
787
739
 
@@ -792,7 +744,175 @@ SC.Event.prototype = {
792
744
 
793
745
  @type Boolean
794
746
  */
795
- hasCustomEventHandling: NO,
747
+ hasCustomEventHandling: false,
748
+
749
+ /** @private Clear out the originalEvent from the SC.Event instance. */
750
+ _sc_clearNormalizedEvent: function () {
751
+ // Remove the original event.
752
+ this.originalEvent = null;
753
+
754
+ // Reset the custom event handling and normalized flag.
755
+ this.hasCustomEventHandling = false;
756
+ this.normalized = false;
757
+
758
+ // Remove non-primitive properties copied over from the original event. While these will
759
+ // be overwritten, it's best to quickly null them out to avoid any issues.
760
+ var props = SC.Event._props,
761
+ idx;
762
+
763
+ idx = props.length;
764
+ while(--idx >= 0) {
765
+ var key = props[idx];
766
+ this[key] = null;
767
+ }
768
+
769
+ // Remove the custom properties associated with the previous original event. While these will
770
+ // be overwritten, it's best to quickly null them out to avoid any issues.
771
+ this.timeStamp = null;
772
+ this.target = null;
773
+ this.relatedTarget = null;
774
+ this.pageX = null;
775
+ this.pageY = null;
776
+ this.which = null;
777
+ this.metaKey = null;
778
+ this.wheelDelta = null;
779
+ this.wheelDeltaY = null;
780
+ this.wheelDeltaX = null;
781
+ },
782
+
783
+ /** @private Update the SC.Event instance with the new originalEvent. */
784
+ _sc_updateNormalizedEvent: function (originalEvent) {
785
+ var idx, len;
786
+
787
+ // Flag.
788
+ this.normalized = true;
789
+
790
+ // copy properties from original event, if passed in.
791
+ if (originalEvent) {
792
+ this.originalEvent = originalEvent ;
793
+ var props = SC.Event._props,
794
+ key;
795
+
796
+ len = props.length;
797
+ idx = len;
798
+ while(--idx >= 0) {
799
+ key = props[idx] ;
800
+ this[key] = originalEvent[key] ;
801
+ }
802
+ }
803
+
804
+ // Fix timeStamp
805
+ this.timeStamp = this.timeStamp || Date.now();
806
+
807
+ // Fix target property, if necessary
808
+ // Fixes #1925 where srcElement might not be defined either
809
+ if (!this.target) this.target = this.srcElement || document;
810
+
811
+ // check if target is a textnode (safari)
812
+ if (this.target.nodeType === 3 ) this.target = this.target.parentNode;
813
+
814
+ // Add relatedTarget, if necessary
815
+ if (!this.relatedTarget && this.fromElement) {
816
+ this.relatedTarget = (this.fromElement === this.target) ? this.toElement : this.fromElement;
817
+ }
818
+
819
+ // Calculate pageX/Y if missing and clientX/Y available
820
+ if (SC.none(this.pageX) && !SC.none(this.clientX)) {
821
+ var doc = document.documentElement, body = document.body;
822
+ this.pageX = this.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
823
+ this.pageY = this.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
824
+ }
825
+
826
+ // Add which for key events
827
+ if (!this.which && ((this.charCode || originalEvent.charCode === 0) ? this.charCode : this.keyCode)) {
828
+ this.which = this.charCode || this.keyCode;
829
+ }
830
+
831
+ // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
832
+ if (!this.metaKey && this.ctrlKey) this.metaKey = this.ctrlKey;
833
+
834
+ // Add which for click: 1 == left; 2 == middle; 3 == right
835
+ // Note: button is not normalized, so don't use it
836
+ if (!this.which && this.button) {
837
+ this.which = ((this.button & 1) ? 1 : ((this.button & 2) ? 3 : ( (this.button & 4) ? 2 : 0 ) ));
838
+ }
839
+
840
+ // Normalize wheel delta values for mousewheel events.
841
+ /*
842
+ Taken from https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel
843
+ IE and Opera (Presto) only support wheelDelta attribute and do not support horizontal scroll.
844
+
845
+ The wheelDeltaX attribute value indicates the wheelDelta attribute value along the horizontal axis. When a user operates the device for scrolling to right, the value is negative. Otherwise, i.e., if it's to left, the value is positive.
846
+
847
+ The wheelDeltaY attribute value indicates the wheelDelta attribute value along the vertical axis. The sign of the value is the same as the wheelDelta attribute value.
848
+
849
+ IE
850
+
851
+ The value is the same as the delta value of WM_MOUSEWHEEL or WM_MOUSEHWHEEL. It means that if the mouse wheel doesn't support high resolution scroll, the value is 120 per notch. The value isn't changed even if the scroll amount of system settings is page scroll.
852
+
853
+ ## Chrome
854
+
855
+ On Windows, the value is the same as the delta value of WM_MOUSEWHEEL or WM_MOUSEHWHEEL. And also, the value isn't changed even if the scroll amount of system settings is page scroll, i.e., the value is the same as IE on Windows.
856
+
857
+ On Linux, the value is 120 or -120 per native wheel event. This makes the same behavior as IE and Chrome for Windows.
858
+
859
+ On Mac, the value is complicated. The value is changed if the device that causes the native wheel event supports continuous scroll.
860
+
861
+ If the device supports continuous scroll (e.g., trackpad of MacBook or mouse wheel which can be turned smoothly), the value is computed from accelerated scroll amount. In this case, the value is the same as Safari.
862
+
863
+ If the device does not support continuous scroll (typically, old mouse wheel which cannot be turned smoothly), the value is computed from non-accelerated scroll amount (120 per notch). In this case, the value is different from Safari.
864
+
865
+ This difference makes a serious issue for web application developers. That is, web developers cannot know if mousewheel event is caused by which device.
866
+
867
+ See WebInputEventFactory::mouseWheelEvent of the Chromium's source code for the detail.
868
+
869
+ ## Safari
870
+
871
+ The value is always computed from accelerated scroll amount. This is really different from other browsers except Chrome with continuous scroll supported device.
872
+
873
+ Note: tested with the Windows package, the earliest available version was Safari 3.0 from 2007. It could be that earlier versions (on Mac) support the properties too.
874
+
875
+ ## Opera (Presto)
876
+
877
+ The value is always the detail attribute value ✕ 40.
878
+
879
+ On Windows, since the detail attribute value is computed from actual scroll amount, the value is different from other browsers except the scroll amount per notch is 3 lines in system settings or a page.
880
+
881
+ On Linux, the value is 80 or -80 per native wheel event. This is different from other browsers.
882
+
883
+ On Mac, the detail attribute value is computed from accelerated scroll amout of native event. The value is usually much bigger than Safari's or Chrome's value.
884
+ */
885
+ if (this.type === 'mousewheel' || this.type === 'DOMMouseScroll' || this.type === 'MozMousePixelScroll') {
886
+ var deltaMultiplier = SC.Event.MOUSE_WHEEL_MULTIPLIER;
887
+
888
+ // normalize wheelDelta, wheelDeltaX, & wheelDeltaY for Safari
889
+ if (SC.browser.isWebkit && originalEvent.wheelDelta !== undefined) {
890
+ this.wheelDelta = 0 - (originalEvent.wheelDeltaY || originalEvent.wheelDeltaX);
891
+ this.wheelDeltaY = 0 - (originalEvent.wheelDeltaY || 0);
892
+ this.wheelDeltaX = 0 - (originalEvent.wheelDeltaX || 0);
893
+
894
+ // normalize wheelDelta for Firefox (all Mozilla browsers)
895
+ // note that we multiple the delta on FF to make it's acceleration more natural.
896
+ } else if (!SC.none(originalEvent.detail) && SC.browser.isMozilla) {
897
+ if (originalEvent.axis && (originalEvent.axis === originalEvent.HORIZONTAL_AXIS)) {
898
+ this.wheelDeltaX = originalEvent.detail;
899
+ this.wheelDelta = this.wheelDeltaY = 0;
900
+ } else {
901
+ this.wheelDelta = this.wheelDeltaY = originalEvent.detail;
902
+ this.wheelDeltaX = 0;
903
+ }
904
+
905
+ // handle all other legacy browser
906
+ } else {
907
+ this.wheelDelta = this.wheelDeltaY = SC.browser.isIE || SC.browser.isOpera ? 0 - originalEvent.wheelDelta : originalEvent.wheelDelta;
908
+ this.wheelDeltaX = 0;
909
+ }
910
+
911
+ this.wheelDelta *= deltaMultiplier;
912
+ this.wheelDeltaX *= deltaMultiplier;
913
+ this.wheelDeltaY *= deltaMultiplier;
914
+ }
915
+ },
796
916
 
797
917
  /**
798
918
  Returns the touches owned by the supplied view.