Skip to content

Instantly share code, notes, and snippets.

@janv
Created August 8, 2012 13:28
Show Gist options
  • Select an option

  • Save janv/3295022 to your computer and use it in GitHub Desktop.

Select an option

Save janv/3295022 to your computer and use it in GitHub Desktop.
CoffeeScript/Backbone recursive change watcher
define [
'underscore'
], (_) ->
# Support for listening to change events of attributes through multiple indirection.
class ChangeWatcher
# The initialTarget becomes the first event dispatcher and also the calling context of the callback.
# It is usually initialized with `this`. The initialChain is an array of attribues to follow.
# Each attribute is looked for in its predecessor.
# The callback is called in the context of initialTarget and receives as parameters the old and
# the new Value of the end of the chain
constructor: (@initialTarget, @initialChain, @callback) ->
@watching = false
@watch()
# Start watching for changes. Executed automatically after construction of the Watcher
# Does NOT trigger the callback initally. Use the trigger() function for that
watch: ->
return if @watching
@oldValue = @getCurrentValue()
@bind(@initialTarget, @initialChain)
@watching = true
# Stop watching for changes
unwatch: ->
return if not @watching
@unbind(@initialTarget, @initialChain)
delete @oldValue
@watching = false
# Trigger the callback
trigger: ->
@runCallback @initialTarget, @initialChain
# Retrieve the current value at the end of the chain
getCurrentValue: ->
@get @initialTarget, @initialChain
# Internal Methods
# ------------------------------------------------------------------------
get: (target, chain) ->
head = _(chain).head()
tail = _(chain).tail()
if tail.length > 0
@get target?.get(head), tail
else
target?.get(head)
bind: (target, chain) ->
if not target
# console.log "Changewatcher", this, "not binding target", target, "with chain", chain
return
# console.log "Changewatcher", this, "binding target", target, "with chain", chain
head = _(chain).head()
tail = _(chain).tail()
handler = ->
changedThingOld = target.previous(head)
changedThingNew = target.get(head)
# console.log "Changewatcher", this, "executing callback with target", target, "and chain", chain, "values:", changedThingOld, changedThingNew
@runCallback(changedThingNew, tail)
if tail.length > 0
@unbind(changedThingOld, tail)
@bind(changedThingNew, tail)
target.on "change:#{head}", handler, this
unbind: (target, chain) ->
if not target
# console.log "Changewatcher", this, "not unbinding target", target, "with chain", chain
return
head = _(chain).head()
tail = _(chain).tail()
if tail.length > 0
changedThing = target.get(head)
@unbind(changedThing, tail)
# console.log "Changewatcher", this, "unbinding target", target, "with chain", chain
target.off "change:#{head}", null, this
runCallback: (changedThingNew, chain) ->
# console.log this, "attempting to run callback with chain", chain
if chain.length == 0
if @oldValue != changedThingNew
# console.log this, "running callback with", @oldValue, "-->", changedThingNew
try
@callback.call(@initialTarget, @oldValue, changedThingNew)
finally
@oldValue = changedThingNew
else
head = _(chain).head()
tail = _(chain).tail()
@runCallback changedThingNew?.get(head), tail
dispose: ->
return if @disposed
@unwatch() if @watching
delete @initialTarget
delete @initialChain
delete @callback
@disposed = true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment