Created
August 8, 2012 13:28
-
-
Save janv/3295022 to your computer and use it in GitHub Desktop.
CoffeeScript/Backbone recursive change watcher
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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