Skip to content

Commit 1f569ae

Browse files
Merge remote branch 'upstream/master' into ecma5
2 parents 1b8538c + ece6e0e commit 1f569ae

File tree

8 files changed

+237
-50
lines changed

8 files changed

+237
-50
lines changed

CHANGELOG

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
*1.7_rc1* (April 1, 2010)
2+
3+
* Add an `Event.Handler` class, plus `Event.on` and `Element#on` methods, for simplified event delegation. (sam, Tobie Langel, Andrew Dupont)
4+
5+
* Make `Element.Layout` properly interpret computed non-integer pixel values. (e.g., Firefox can report "12.5px" as a computed style value.) (henrymazza)
6+
17
* Fix deprecated Selector.matchElements. (Tobie Langel)
28

39
* Make Object.keys ES5 compliant. (Tobie Langel)

src/constants.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
PROTOTYPE_VERSION: 1.6.1
1+
PROTOTYPE_VERSION: 1.7_rc1

src/dom/event.js

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -852,14 +852,98 @@
852852

853853
return Event.extend(event);
854854
}
855+
856+
/**
857+
* class Event.Handler
858+
*
859+
* Creates an observer on an element that listens for a particular event on
860+
* that element's descendants, optionally filtering by a CSS selector.
861+
*
862+
* This class simplifies the common "event delegation" pattern, in which one
863+
* avoids adding an observer to a number of individual elements and instead
864+
* listens on a _common ancestor_ element.
865+
*
866+
**/
867+
Event.Handler = Class.create({
868+
/**
869+
* new Event.Handler(element, eventName[, selector], callback)
870+
* - element (Element): The element to listen on.
871+
* - eventName (String): An event to listen for. Can be a standard browser
872+
* event or a custom event.
873+
* - selector (String): A CSS selector. If specified, will call `callback`
874+
* _only_ when it can find an element that matches `selector` somewhere
875+
* in the ancestor chain between the event's target element and the
876+
* given `element`.
877+
* - callback (Function): The event handler function. Should expect two
878+
* arguments: the event object _and_ the element that received the
879+
* event. (If `selector` was given, this element will be the one that
880+
* satisfies the criteria described just above; if not, it will be the
881+
* one specified in the `element` argument).
882+
*
883+
* Instantiates an `Event.Handler`. **Will not** begin observing until
884+
* [[Event.Handler#start]] is called.
885+
**/
886+
initialize: function(element, eventName, selector, callback) {
887+
this.element = $(element);
888+
this.eventName = eventName;
889+
this.selector = selector;
890+
this.callback = callback;
891+
this.handler = this.handleEvent.bind(this);
892+
},
855893

894+
/**
895+
* Event.Handler#start -> Event.Handler
896+
*
897+
* Starts listening for events. Returns itself.
898+
**/
899+
start: function() {
900+
Event.observe(this.element, this.eventName, this.handler);
901+
return this;
902+
},
903+
904+
/**
905+
* Event.Handler#stop -> Event.Handler
906+
*
907+
* Stops listening for events. Returns itself.
908+
**/
909+
stop: function() {
910+
Event.stopObserving(this.element, this.eventName, this.handler);
911+
return this;
912+
},
913+
914+
handleEvent: function(event) {
915+
var element = this.selector ? event.findElement(this.selector) :
916+
this.element;
917+
if (element) this.callback.call(element, event, element);
918+
}
919+
});
920+
921+
/**
922+
* Event.on(element, eventName, selector, callback) -> Event.Handler
923+
*
924+
* Listens for events on an element's descendants, optionally filtering
925+
* to match a given CSS selector.
926+
*
927+
* Creates an instance of [[Event.Handler]], calls [[Event.Handler#start]],
928+
* then returns that instance. Keep a reference to this returned instance if
929+
* you later want to unregister the observer.
930+
**/
931+
function on(element, eventName, selector, callback) {
932+
element = $(element);
933+
if (Object.isFunction(selector) && Object.isUndefined(callback)) {
934+
callback = selector, selector = null;
935+
}
936+
937+
return new Event.Handler(element, eventName, selector, callback).start();
938+
}
856939

857940
Object.extend(Event, Event.Methods);
858941

859942
Object.extend(Event, {
860943
fire: fire,
861944
observe: observe,
862-
stopObserving: stopObserving
945+
stopObserving: stopObserving,
946+
on: on
863947
});
864948

865949
Element.addMethods({
@@ -918,7 +1002,13 @@
9181002
* Element.stopObserving(@element[, eventName[, handler]]) -> Element
9191003
* See [[Event.stopObserving]].
9201004
**/
921-
stopObserving: stopObserving
1005+
stopObserving: stopObserving,
1006+
1007+
/**
1008+
* Element.on(@element, eventName[, selector], callback) -> Element
1009+
* See [[Event.on]].
1010+
**/
1011+
on: on
9221012
});
9231013

9241014
/** section: DOM
@@ -982,6 +1072,8 @@
9821072
* [[Element.stopObserving]].
9831073
**/
9841074
stopObserving: stopObserving.methodize(),
1075+
1076+
on: on.methodize(),
9851077

9861078
/**
9871079
* document.loaded -> Boolean

src/dom/layout.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
if (value === null) {
2222
return null;
2323
}
24-
24+
2525
// Non-IE browsers will always return pixels if possible.
26-
if ((/^\d+(px)?$/i).test(value)) {
27-
return window.parseInt(value, 10);
26+
// (We use parseFloat instead of parseInt because Firefox can return
27+
// non-integer pixel values.)
28+
if ((/^\d+(\.\d+)?(px)?$/i).test(value)) {
29+
return window.parseFloat(value);
2830
}
2931

3032
// When IE gives us something other than a pixel value, this technique
@@ -36,7 +38,7 @@
3638
value = element.style.pixelLeft;
3739
element.style.left = style;
3840
element.runtimeStyle.left = rStyle;
39-
41+
4042
return value;
4143
}
4244

@@ -238,11 +240,6 @@
238240
// TODO: Investigate.
239241
set: function(property, value) {
240242
throw "Properties of Element.Layout are read-only.";
241-
// if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) {
242-
// throw "Cannot set a composite property.";
243-
// }
244-
//
245-
// return this._set(property, toCSSPixels(value));
246243
},
247244

248245
/**
@@ -302,9 +299,9 @@
302299
// (b) it has an explicitly-set width, instead of width: auto.
303300
// Either way, it means the element is the width it needs to be
304301
// in order to report an accurate height.
305-
newWidth = window.parseInt(width, 10);
302+
newWidth = getPixelValue(width);
306303
} else if (width && (position === 'absolute' || position === 'fixed')) {
307-
newWidth = window.parseInt(width, 10);
304+
newWidth = getPixelValue(width);
308305
} else {
309306
// If not, that means the element's width depends upon the width of
310307
// its parent.
@@ -714,7 +711,7 @@
714711
* // -> 100
715712
**/
716713
function getDimensions(element) {
717-
var layout = $(element).getLayout();
714+
var layout = $(element).getLayout();
718715
return {
719716
width: layout.get('width'),
720717
height: layout.get('height')
@@ -728,7 +725,11 @@
728725
* `body` element is returned.
729726
**/
730727
function getOffsetParent(element) {
731-
if (element.offsetParent) return $(element.offsetParent);
728+
if (isDetached(element)) return $(document.body);
729+
730+
// IE reports offset parent incorrectly for inline elements.
731+
var isInline = (Element.getStyle(element, 'display') === 'inline');
732+
if (!isInline && element.offsetParent) return $(element.offsetParent);
732733
if (element === document.body) return $(element);
733734

734735
while ((element = element.parentNode) && element !== document.body) {
@@ -908,13 +909,20 @@
908909
return element.nodeName.toUpperCase() === 'BODY';
909910
}
910911

912+
function isDetached(element) {
913+
return element !== document.body &&
914+
!Element.descendantOf(element, document.body);
915+
}
916+
911917
// If the browser supports the nonstandard `getBoundingClientRect`
912918
// (currently only IE and Firefox), it becomes far easier to obtain
913919
// true offsets.
914920
if ('getBoundingClientRect' in document.documentElement) {
915921
Element.addMethods({
916922
viewportOffset: function(element) {
917-
element = $(element);
923+
element = $(element);
924+
if (isDetached(element)) return new Element.Offset(0, 0);
925+
918926
var rect = element.getBoundingClientRect(),
919927
docEl = document.documentElement;
920928
// The HTML element on IE < 8 has a 2px border by default, giving
@@ -926,14 +934,17 @@
926934

927935
cumulativeOffset: function(element) {
928936
element = $(element);
937+
if (isDetached(element)) return new Element.Offset(0, 0);
938+
929939
var docOffset = $(document.documentElement).viewportOffset(),
930940
elementOffset = element.viewportOffset();
931941
return elementOffset.relativeTo(docOffset);
932942
},
933943

934944
positionedOffset: function(element) {
935945
element = $(element);
936-
var parent = element.getOffsetParent();
946+
var parent = element.getOffsetParent();
947+
if (isDetached(element)) return new Element.Offset(0, 0);
937948

938949
// When the BODY is the offsetParent, IE6 mistakenly reports the
939950
// parent as HTML. Use that as the litmus test to fix another

test/functional/event.html

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,32 @@
88

99
<style type="text/css" media="screen">
1010
/* <![CDATA[ */
11-
body { margin:1em 2em; padding:0; font-size:0.8em }
11+
body {
12+
margin:1em 2em; padding:0; font-size:0.8em;
13+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
14+
}
1215
hr { width:31.2em; margin:1em 0; text-align:left }
13-
p { width:30em; margin:0.5em 0; padding:0.3em 0.6em; color:#222; background:#eee; border:1px solid silver; }
14-
.subtest { margin-top:-0.5em }
16+
/* p { width:30em; margin:0.5em 0; padding:0.3em 0.6em; color:#222; background:#eee; border:1px solid silver; }
17+
*/ .subtest { margin-top:-0.5em }
1518
.passed { color:green; border-color:olive }
1619
.failed { color:firebrick; border-color:firebrick }
1720
.button { padding:0.2em 0.4em; background:#ccc; border:1px solid #aaa }
18-
#log { position:absolute; left:35em; top:5em; width:20em; font-size:13px !important }
21+
fieldset { position:absolute; left:35em; top:5em; width:400px; font-size:13px !important }
1922
h2 { font:normal 1.1em Verdana,Arial,sans-serif; font-style:italic; color:gray; margin-top:-1.2em }
2023
h2 *, h2 a:visited { color:#444 }
2124
h2 a:hover { color:blue }
2225
a:visited { color:blue }
2326
a:hover { color:red }
27+
28+
.test {
29+
30+
}
31+
32+
.test span {
33+
border: 2px solid #999;
34+
background-color: #ddd;
35+
padding: 2px;
36+
}
2437
/* ]]> */
2538
</style>
2639

@@ -53,7 +66,10 @@
5366
<body>
5467
<h1>Prototype functional tests for the Event module</h1>
5568

56-
<div id="log">log empty</div>
69+
<fieldset>
70+
<legend>Log</legend>
71+
<div id="log"></div>
72+
</fieldset>
5773

5874
<p id="basic">A basic event test - <strong>click here</strong></p>
5975
<p id="basic_remove" class="subtest"><strong>click</strong> to stop observing the first test</p>
@@ -63,13 +79,13 @@ <h1>Prototype functional tests for the Event module</h1>
6379
<script type="text/javascript">
6480
var basic_callback = function(e){
6581
$('basic').passed();
66-
if ($('basic_remove')) $('basic_remove').show()
67-
else $('basic').failed()
82+
if ($('basic_remove')) $('basic_remove').show();
83+
else $('basic').failed();
6884
log(e);
6985
}
7086
$('basic').observe('click', basic_callback)
71-
$('basic_remove').observe('click', function(e){
72-
el = $('basic')
87+
$('basic_remove').observe('click', function(e) {
88+
var el = $('basic');
7389
el.passed('This test should now be inactive (try clicking)')
7490
el.stopObserving('click')
7591
$('basic_remove').remove()
@@ -263,5 +279,62 @@ <h1>Prototype functional tests for the Event module</h1>
263279
log(e);
264280
})
265281
</script>
282+
283+
<div id="delegation_container">
284+
Event delegation
285+
<ul>
286+
<li>
287+
<span class="delegation-child-1">Child 1 (click)</span>
288+
</li>
289+
<li>
290+
<span class="delegation-child-2">Child 2 (mouseover)</span>
291+
</li>
292+
<li class="delegation-child-3">
293+
<span>Child 3 (mouseup)</span>
294+
</li>
295+
</ul>
296+
297+
Results:
298+
299+
<ul id="delegation_results">
300+
<li id="delegation_result_1">Test 1</li>
301+
<li id="delegation_result_2">Test 2</li>
302+
<li id="delegation_result_3">Test 3</li>
303+
</ul>
304+
</div>
305+
<script type="text/javascript">
306+
var msg = "Passed. Click to unregister.";
307+
var clickMsg = "Now try original event again to ensure observation was stopped."
308+
var observer1 = $('delegation_container').on('click', '.delegation-child-1', function() {
309+
var result = $('delegation_results').down('li', 0);
310+
result.passed(msg + " (" + ((new Date).toString())) + ")";
311+
result.observe('click', function() {
312+
this.update(clickMsg);
313+
observer1.stop();
314+
});
315+
});
316+
317+
var observer2 = $(document).on('mouseover', '.delegation-child-2', function() {
318+
var result = $('delegation_results').down('li', 1);
319+
result.passed(msg + " (" + ((new Date).toString())) + ")";
320+
result.observe('click', function() {
321+
this.update(clickMsg);
322+
observer2.stop();
323+
});
324+
})
325+
326+
var observer3 = $('delegation_container').on('mouseup', '.delegation-child-3', function() {
327+
var result = $('delegation_results').down('li', 2);
328+
result.passed(msg + " (" + ((new Date).toString())) + ")";
329+
result.observe('click', function() {
330+
this.update(clickMsg);
331+
observer3.stop();
332+
});
333+
});
334+
335+
336+
</script>
337+
338+
266339
</body>
267340
</html>

0 commit comments

Comments
 (0)