Skip to content

Instantly share code, notes, and snippets.

@kixxauth
Created May 30, 2011 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kixxauth/998935 to your computer and use it in GitHub Desktop.
Save kixxauth/998935 to your computer and use it in GitHub Desktop.
Namespaced and "stateful" event emitter.
###
# Kris Walker <kixxauth@gmail.com>
# Copyright 2011 Licensed under the MIT License http://opensource.org/licenses/mit-license.php
#
# Namespaced and Stateful events
# * Event names are namespaced
# * Event name specificity is separated by a dot "."
# * Event names get more specific from left to right.
#
# - So when the event named "foo.bar" is emitted handlers attached to "foo" and "foo.bar"
# will be triggered. But if the "foo" event is emitted, only the handler attached to
# "foo" will be triggered. If the event "bar" is emitted, neither will be triggered.
#
# * Also, the emitter is stateful; it remembers the last data fired on each event.
#
# - So, when you attach a handler to the event "foo.bar" after it has already been fired,
# your handler will be triggered even though it technically missed the event.
#
# Gotcha: These events are not emitted or triggered on another event loop, which
# can wreak havoc on existing architecture. .emit() should default to async and there
# should be another method created called .emitSync() to avoid confusion.
#
# This code depends on jQuery ($) and a locally contrived "require()" system... but you
# can easily replace that code.
#
# And this could probably be optimized too.
###
declare (require, exports) ->
logging = require 'logging'
walkPath = (path, callback) ->
agg = []
for part in path.split('.')
agg.push(part)
callback(agg.join('.'))
return
invoke = (fn, data, context) ->
data = $.isArray(data) and data or (data and [data] or [])
try
fn.apply(context or {}, data)
catch e
return e
return
class Registry
dict: {}
bind: (path, fn) ->
if not $.isArray(@dict[path])
@dict[path] = []
@dict[path].push(fn)
return @
broadcast: (path, data, context) ->
callbacks = @dict[path]
if $.isArray(callbacks)
for fn in callbacks
err = invoke(fn, data, context)
if err
logging.log("callback error on '#{ path }'")
logging.log(err)
class Emitter
constructor: ->
@callbackRegistry = new Registry()
@eventMemo = {}
emit: (path, data, context) ->
context or= @
walkPath path, (namespace) =>
@callbackRegistry.broadcast(namespace, data, context)
@eventMemo[namespace] = {data: data, context: context}
on: (path, fn) ->
@callbackRegistry.bind(path, fn)
history = @eventMemo[path]
if history
{data, context} = history
err = invoke(fn, data, context)
if err
logging.log("callback error on '#{ path }'")
logging.log(err)
exports.Emitter = Emitter
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment