Skip to content

Instantly share code, notes, and snippets.

@mpneuried
Last active December 10, 2015 23:28
Show Gist options
  • Save mpneuried/4509358 to your computer and use it in GitHub Desktop.
Save mpneuried/4509358 to your computer and use it in GitHub Desktop.
Simple namespaced Pub/Sub coffee class to use as extendable module for NodeJS and the browser.
# # PubSub
#
# Is a small helper to simply realize a pub/sub pattern to a coffee class.
#
# A namespaceing of the topics is also included.
# This means you can subscribe to `a` and also get the `a.b`. But if you subscribe to `a.b` you will not get a `a`.
#
# **required module**: `underscore`
#
class PubSub
# **_ps_handles** *Object* The handle store. It stores the handles unter each topic as array
_ps_handles: {}
# **_ps_count** *Number* Small helper to stop if no subscribers are listening
_ps_count: 0
# **ps_delimiter** *String* Namespace delimiter String. You can change this to your prefered delimiter
ps_delimiter: "."
###
## subscribe
`pubsub.subscribe( topic, handle )`
subscribe to a topic
@param { String } topic A Topic you want to subscribe. It can be namespaced. The namesapce delimiter can be defined by the var `ps_delimiter`
@param { Function } handle The handle function wich will be called by a `publish`
@api public
###
subscribe: ( topic, handle )=>
# just for a fast exit if no subscribers exists
@_ps_count++
# fire change
@listenerCountChanged( @_ps_count )
# save the handle to the handle store
@_ps_handles[ topic ] or= []
@_ps_handles[ topic ].push( handle )
return
###
## unsubscribe
`pubsub.unsubscribe( handle )`
unsubscribe from a topic
@param { Function } handle The same handle used by `subscribe`
@api public
###
unsubscribe: ( handle )=>
# just for a fast exit if no subscribers exists
@_ps_count--
# fire change
@listenerCountChanged( @_ps_count )
# try to find the handle and remove it
for topic, _thandles of @_ps_handles
if handle in _thandles
@_ps_handles[ topic ] = _.without( _thandles, handle )
# cleanup if topic is empty
if not @_ps_handles[ topic ].length
@_ps_handles = _.omit( @_ps_handles, topic );
break
return
###
## publish
`pubsub.publish( topic, data )`
publish from a topic
@param { String } topic The topic you want to publish.
@param { Any } data Additional data to send the the subscriber.
@api public
###
publish: ( topic, data )=>
# find the handles and call the handles
for handle in @_ps_findHandles( topic )
handle( topic, data )
return
###
## listenerCount
`pubsub.listenerCount( )`
Get the count of listeners. Helps you the find out if anyone listens
@return { Number } Number of listeners
@api public
###
listenerCount: =>
@_ps_count
###
## listenerCountChanged
`pubsub.listenerCountChanged( count )`
Overridable method fired if a listener subscribes or unsubscribes. Can be used the activate or deactivate something.
@param { Number } count New number of listener
@api public
###
listenerCountChanged: ( count )=>
return
###
## _ps_findHandles
`pubsub._ps_findHandles( topic )`
Find all matching handles of a topic. This also inculdes the use of namespacing
@param { String } topic The topic to find the matiching handles
@return { Array } An array of handles
@api private
###
_ps_findHandles: ( topic )=>
if @_ps_count
_keys = _.keys( @_ps_handles )
_matched = []
# loop through all cached topics and check if they matching the topic
for _kn in _keys
if @_ps_match( _kn.split( @ps_delimiter ), topic.split( @ps_delimiter ) )
_matched = _.union( _matched, @_ps_handles[ _kn ] )
_matched
else
[]
###
## _ps_match
`pubsub._ps_match( inp, test )`
Matching helper to find the handles by namespace. This will be calles recrusive through the depth of the topic namespace
@param { Array } inp Array of namespace cache handles to check against
@param { Array } test Array of topic namespace to check against `inp`
@return { Boolean } First `inp` level is matching first `test` level.
@api private
###
_ps_match: ( inp = [], test = [] )->
_tk = test[ 0 ]
_ik = inp[ 0 ]
if test.length and _tk is _ik
# if the first level matches return true or if possible go deeper
if inp.length > 1
@_ps_match( inp.splice( 1 ), test.splice( 1 ) )
else
true
else
# return false on a mismatch
false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment