-
Notifications
You must be signed in to change notification settings - Fork 3
/
Event.coffee
301 lines (217 loc) · 9.33 KB
/
Event.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
package "cafe"
# Event is based on DOM3 Events as specified by the ECMAScript Language Binding
# http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
Event: class Event
constructor: (src) ->
# Allow instantiation without the 'new' keyword
return new Event(src) unless @preventDefault
# Event object
if src and src.type
@originalEvent = src
@type = src.type
else
# Event type
@type = src
preventDefault: () ->
@isDefaultPrevented = -> yes
return unless e = @originalEvent
if e.preventDefault
# if preventDefault exists run it on the original event
e.preventDefault()
else
# otherwise set the returnValue property of the original event to false (IE)
e.returnValue = no
stopPropagation: () ->
@isPropagationStopped = -> yes
return unless e = @originalEvent
# if stopPropagation exists run it on the original event
if e.stopPropagation
e.stopPropagation()
else
# otherwise set the cancelBubble property of the original event to true (IE)
e.cancelBubble = yes
isDefaultPrevented: () ->
return no
isPropagationStopped: () ->
return no
@fix: (event) ->
return event if event instanceof Event
# store a copy of the original event object
# and "clone" to set read-only properties
originalEvent = event
event = new Event(originalEvent)
for prop in "altKey bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement keyCode layerX layerY metaKey offsetX offsetY pageX pageY relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" ")
event[prop] = originalEvent[prop] if prop of originalEvent
# Fix target property, if necessary
unless event.target
event.target = event.srcElement or document
# check if target is a textnode (safari)
if event.target.nodeType is 3
event.target = event.target.parentNode
# Add relatedTarget, if necessary
if not event.relatedTarget and event.fromElement
event.relatedTarget = if event.fromElement is event.target then event.toElement else event.fromElement
# Calculate pageX/Y if missing and clientX/Y available
unless event.pageX? and event.clientX?
doc = document.documentElement
body = document.body
event.pageX = event.clientX + (doc and doc.scrollLeft or body and body.scrollLeft or 0) - (doc and doc.clientLeft or body and body.clientLeft or 0)
event.pageY = event.clientY + (doc and doc.scrollTop or body and body.scrollTop or 0) - (doc and doc.clientTop or body and body.clientTop or 0)
# Calculate wheelDelta if missing
if event.wheelDelta?
event.wheelDelta = event.wheelDelta / 120
else if event.detail > 2
event.wheelDelta = -1 * event.detail / 3
# Add which for key events
if not event.which? and (event.charCode? or event.keyCode?)
event.which = if event.charCode? then event.charCode else event.keyCode
# Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
event.metaKey = event.ctrlKey if not event.metaKey and event.ctrlKey
# Add which for click: 1 is left; 2 is middle; 3 is right
# Note: button is not normalized, so don't use it
if not event.which? and event.button?
if event.button & 1
event.which = 1
else if event.button & 2
event.which = 3
else if event.button & 4
event.which = 2
else
event.which = 0
return event
@events: {}
@hashCodes: 0
@dispatch: (elem, event, args) ->
isObservable = elem instanceof cafe.event.Observable if cafe.event?.Observable
if isObservable
return unless hashCode = elem.____hashCodes
return unless hashCode of @events
elemData = @events[hashCode]
events = elemData.events
eventHandle = elemData.handle
return eventHandle(event, args)
return unless elem and typeof elem.nodeType is "number"
return if elem.nodeType is 3 or elem.nodeType is 8 or not elem.parentNode
# dispatch for IE
if document.createEventObject
return elem.fireEvent("on" + event, document.createEventObject())
else
# dispatch for firefox + others
htmlEvent = document.createEvent("HTMLEvents")
# event type, bubbling, cancelable
htmlEvent.initEvent(event, yes, yes)
# canceled or not
return not elem.dispatchEvent(htmlEvent)
@handle: (hashCode, event, args) ->
return unless hashCode of @events
elem = @events[hashCode].currentTarget
isObservable = elem instanceof cafe.event.Observable if cafe.event?.Observable
if isObservable
event = new Event(event)
else
event = @fix(event or window.event)
event.currentTarget = elem
typeKey = event.type
typeKey = typeKey.toLowerCase() unless typeKey.indexOf("DOM") is 0
propagationStopped = no
if typeKey of @events[hashCode].events
# Clone the handlers to prevent manipulation
for handler in @events[hashCode].events[typeKey].slice(0)
try
handler.call(null, event, args)
catch ex
setTimeout(
-> throw ex
10
)
break if propagationStopped = event.isPropagationStopped()
return propagationStopped if isObservable
@add: (elem, types, handler) ->
isObservable = elem instanceof cafe.event.Observable if cafe.event?.Observable
if isObservable
elem.____hashCodes = ++@hashCodes unless elem.____hashCodes
hashCode = elem.____hashCodes
else
# For whatever reason, IE has trouble passing the window object
# around, causing it to be cloned in the process
isWindow = yes if elem is window or elem and typeof elem is "object" and "setInterval" of elem and elem isnt window and not elem.frameElement
if isWindow
elem = window
hashCode = 0
else
return unless elem and typeof elem.nodeType is "number"
return if elem.nodeType is 3 or elem.nodeType is 8
elem.____hashCodes = ++@hashCodes unless elem.____hashCodes
hashCode = elem.____hashCodes
unless hashCode of @events
@events[hashCode] = {
events: {}
# Add elem as a property of the handle obj
# This is to prevent a memory leak with non-native events in IE.
currentTarget: elem
handle: (event, args) -> Event.handle(hashCode, event, args)
}
elemData = @events[hashCode]
events = elemData.events
eventHandle = elemData.handle
for type in types.split(" ")
type = type.toLowerCase() unless type.indexOf("DOM") is 0
# Init the event handler queue
unless type of events and not isObservable
events[type] = []
# Bind the global event handler to the element
if elem.addEventListener
elem.addEventListener(type, eventHandle, no)
else if elem.attachEvent
elem.attachEvent("on" + type, eventHandle)
# Add the function to the element's handler list
events[type].push(handler)
# Nullify elem to prevent memory leaks in IE
elem = null
@remove: (elem, types, handler) ->
isObservable = elem instanceof cafe.event.Observable if cafe.event?.Observable
if isObservable
return unless hashCode = elem.____hashCodes
else
isWindow = yes if elem is window or elem and typeof elem is "object" and "setInterval" of elem and elem isnt window and not elem.frameElement
if isWindow
elem = window
hashCode = 0
else
return unless elem and typeof elem.nodeType is "number"
return if elem.nodeType is 3 or elem.nodeType is 8
return unless hashCode = elem.____hashCodes
return unless hashCode of @events
elemData = @events[hashCode]
events = elemData.events
eventHandle = elemData.handle
# Unbind all events for the element
unless types
Event.remove(elem, type) for type of events
return
for type in types.split(" ")
type = type.toLowerCase() unless type.indexOf("DOM") is 0
continue unless type of events
eventHandlers = events[type]
handlers = eventHandlers.splice(0, eventHandlers.length)
if handler
for handleFunc in handlers
# remove the given handler for the given type
eventHandlers.push(handleFunc) unless handler is handleFunc
# remove generic event handler if no more handlers exist
if eventHandlers.length is 0 and not isObservable
if elem.removeEventListener
elem.removeEventListener(type, eventHandle, no)
else if elem.detachEvent
elem.detachEvent("on" + type, eventHandle)
delete events[type]
empty = yes
for event of events
empty = no
break
# Remove the expando if it's no longer used
if empty
delete elemData.events
delete elemData.currentTarget
delete elemData.handle
delete @events[hashCode]