-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathobserver.coffee
209 lines (174 loc) · 6.3 KB
/
observer.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
###
dry-observer
v0.2.2
LICENSE: http://github.com/arbales/dry-observer/raw/master/LICENSE
###
root = this
_ = root._
if (!_ && require?)
_ = require('underscore')
# InvalidBindingError is thrown when an object does not
# implement a handler method.
#
class InvalidBindingError extends Error
constructor: (event, handler) ->
@name = "InvalidBindingError"
@message = "Unable create binding for `#{event}` due unimplemented handler: `##{handler}`"
# Internal: Capitalizes a string or returns an empty string if
# one wasn't provided.
#
# Returns a String.
capitalize = (string) ->
return "" unless string
string.charAt(0).toUpperCase() + string.slice(1)
deprecatedBackbone = Backbone? && parseFloat(Backbone.VERSION) < 0.9
eventSplitter = /\s+/
createListener = (target, events, callback, context) ->
# EventEmitter
if target.addListener
for event in events.split(eventSplitter)
target.addListener(event, callback)
# Backbone < 0.9.0
else if deprecatedBackbone
for event in events.split(eventSplitter)
target.bind(event, callback, context)
# Backbone >= 0.9.0, Backbone-compatible objects
else if target.on
target.on(events, callback, context)
else
throw new TypeError "Expected an EventEmitter or Backbone.Events-compatible target."
# Internal: Use the Backbone 0.9.0 interface for unbinding
# events for EventEmitter and all versions of Backbone.
#
destroyListener = (target, events, callback) ->
# EventEmitter
if target.removeListener
for event in events.split(eventSplitter)
target.removeListener(event, callback)
# Backbone < 0.9.0
else if deprecatedBackbone
for event in events.split(eventSplitter)
target.unbind(event, callback)
# Backbone >= 0.9.0, Backbone-compatible objects
else if target.off
target.off(events, callback)
else
throw new TypeError "Expected an EventEmitter or Backbone.Events-compatible target."
Observers =
# Internal: An array of objects for which `#observe`
# was called on. Used for automatic cleanup via `#stopObserving`.
_observedObjects: null
# Internal: A plain object contianing the events and handlers
# for each observedObject.
#
# Example:
#
# @_observers =
# 'c123':
# 'change:assignee': [Function, Function]
# 'focus': [Function]
#
_observers: null
# Public: Bind events on a given object to handlers.
#
# object - The Object to stop observing. Must conform to the EventEmitter API.
# events - A plain Object with events and handlers and key/value pairs OR
# An Array or space-separated String of event names for which
# handlers will be attached based on standard naming convention.
#
# Examples
#
# @observe model,
# "focus" : @onFocus
# "send:task" : @onSendTask
# "add:comment" : @onAddComment
#
# @observe model, ['focus', 'send:task', 'add:comment']
#
# @observe model, 'focus send:task add:comment'
#
# Returns nothing.
# Raises InvalidBindingError if an implicit handler function could not be found.
observe: (target, events...) ->
context = null
@_eventHandlerPrefix ||= 'on'
if events.length is 1
if (_ events[0]).isString()
events = events[0].split(" ")
if (_ events[0]).isObject()
events = events[0]
# if the last element is an object, its the context
else if _.isObject(_.last(events))
context = events.pop()
# If only strings were passed, intuit proper handler
# names, and verify that they exist at runtime.
#
if _.isString(events[0])
parsedEvents = {}
_.each events, (e) =>
# Determine event handlers based on the event name.
[action, scope] = e.split(':')
# `send:task` -> `onSendTask`
handler = [@_eventHandlerPrefix, capitalize(action), capitalize(scope)].join('')
# If the handler function does not exist, bail out of
# binding and throw an error.
handler = @[handler] || @[e] || throw new InvalidBindingError(e, handler)
parsedEvents[e] = handler
events = parsedEvents
unless _.isObject(events)
throw new TypeError "Observe accepts either a String, an Array of Strings, or an Object."
# Target objects must have a CID for use registering events.
target.cid = _.uniqueId('observed') unless target.cid
# Create or append to the list of observed objects.
@_observedObjects = _.union (@_observedObjects ||= []), target
# Conditionally create a hash of events that will be registered
# by this call to observe.
targetEvents = (@_observers ||= {})[target.cid] ||= {}
# Create event listeners for each event and handler specified.
#
for own event, handler of events
createListener target, event, handler, context
(targetEvents[event] ||= []).push handler
true
# Public: Stop observing an object and remove all event listeners created
# through the `#observe` call.
#
# object - The Object to stop observing. If null, all observers will be
# removed. (Optional)
#
# Returns nothing.
stopObserving: (target = false) ->
return false unless @_observedObjects && @_observers
unless target
for target in @_observedObjects
@stopObserving target if target
return false
events = @_observers[target.cid]
for own event, handlers of events
for handler, index in handlers
destroyListener target, event, handler
delete [handlers][index]
# Remove the target object from the list of observed objects.
delete @_observedObjects[_.indexOf(@_observedObjects, target)]
delete @_observers[target.cid]
true
# Public: Remove an observer by event name.
#
# object - The object to stop observing.
# event - The name of the event.
#
# Returns nothing.
removeObserver: (target, event, handlerToRemove) ->
unless target
@removeObserver target for target in @_observedObjects
events = _observers[target.cid]
for own event, handlers of events
for handler, index in handlers
# If we've specified a handler to remove, only remove it.
continue if handlerToRemove and handler isnt handlerToRemove
target.off event, handler
events[event][index] = null
delete events[event][index]
true
exports = module?.exports || this
exports.Observers = Observers