Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/chaplin/controllers/controller.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ module.exports = class Controller
constructor: ->
@initialize arguments...

# Set up declarative bindings after `initialize` has been called
# so initialize may create or bind methods.
@delegateListeners()

initialize: ->
# Empty per default.

Expand Down
32 changes: 32 additions & 0 deletions src/chaplin/lib/event_broker.coffee
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

mediator = require 'chaplin/mediator'
utils = require 'chaplin/lib/utils'

# Add functionality to subscribe and publish to global
# Publish/Subscribe events so they can be removed afterwards
Expand Down Expand Up @@ -53,6 +54,37 @@ EventBroker =
# Publish global handler.
mediator.publish type, args...

# Handle declarative event bindings from `listen`
delegateListeners: ->
return unless @listen

# Walk all `listen` hashes in the prototype chain.
for version in utils.getAllPropertyVersions this, 'listen'
for key, method of version
# Get the method, ensure it is a function.
if typeof method isnt 'function'
method = this[method]
if typeof method isnt 'function'
throw new Error 'EventBroker#delegateListeners: ' +
"#{method} must be function"

# Split event name and target.
[eventName, target] = key.split ' '
@delegateListener eventName, target, method

return

delegateListener: (eventName, target, callback) ->
if target in ['model', 'collection']
prop = this[target]
@listenTo prop, eventName, callback if prop
else if target is 'mediator'
@subscribeEvent eventName, callback
else if not target
@on eventName, callback, this

return

# You’re frozen when your heart’s not open.
Object.freeze? EventBroker

Expand Down
6 changes: 6 additions & 0 deletions src/chaplin/models/collection.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ module.exports = class Collection extends Backbone.Collection
# Use the Chaplin model per default, not Backbone.Model.
model: Model

constructor: ->
super
# Set up declarative bindings after `initialize` has been called
# so initialize may create or bind methods.
@delegateListeners()

# Serializes collection.
serialize: ->
@map utils.serialize
Expand Down
7 changes: 7 additions & 0 deletions src/chaplin/models/model.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ module.exports = class Model extends Backbone.Model
# Mixin an EventBroker.
_.extend @prototype, EventBroker


constructor: ->
super
# Set up declarative bindings after `initialize` has been called
# so initialize may create or bind methods.
@delegateListeners()

# This method is used to get the attributes for the view template
# and might be overwritten by decorators which cannot create a
# proper `attributes` getter due to ECMAScript 3 limits.
Expand Down
31 changes: 0 additions & 31 deletions src/chaplin/views/view.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -276,37 +276,6 @@ module.exports = class View extends Backbone.View
else
@$el.off ".delegate#{@cid}"

# Handle declarative event bindings from `listen`
delegateListeners: ->
return unless @listen

# Walk all `listen` hashes in the prototype chain.
for version in utils.getAllPropertyVersions this, 'listen'
for key, method of version
# Get the method, ensure it is a function.
if typeof method isnt 'function'
method = this[method]
if typeof method isnt 'function'
throw new Error 'View#delegateListeners: ' +
"#{method} must be function"

# Split event name and target.
[eventName, target] = key.split ' '
@delegateListener eventName, target, method

return

delegateListener: (eventName, target, callback) ->
if target in ['model', 'collection']
prop = this[target]
@listenTo prop, eventName, callback if prop
else if target is 'mediator'
@subscribeEvent eventName, callback
else if not target
@on eventName, callback, this

return

# Region management
# -----------------

Expand Down
45 changes: 45 additions & 0 deletions test/spec/collection_spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,51 @@ define [
expect(actual[1].id).to.be expected[1].id
expect(actual[1].foo).to.be expected[1].foo

describe 'Events', ->
class EventedCollection extends Collection
listen:
# self
'ns:a': 'a1Handler'
'add': ->
@a2Handler arguments...

# mediator
'ns:a mediator': 'a1Handler'
'ns:b mediator': 'a2Handler'

initialize: ->
super
@a1Handler = sinon.spy()
@a2Handler = sinon.spy()

it 'should bind to own events declaratively', ->
collection = new EventedCollection()

expect(collection.a1Handler).was.notCalled()
expect(collection.a2Handler).was.notCalled()

collection.trigger 'ns:a'
expect(collection.a1Handler).was.calledOnce()
expect(collection.a2Handler).was.notCalled()

collection.trigger 'add'
expect(collection.a1Handler).was.calledOnce()
expect(collection.a2Handler).was.calledOnce()

it 'should bind to mediator events declaratively', ->
collection = new EventedCollection()

expect(collection.a1Handler).was.notCalled()
expect(collection.a2Handler).was.notCalled()

mediator.publish 'ns:a'
expect(collection.a1Handler).was.calledOnce()
expect(collection.a2Handler).was.notCalled()

mediator.publish 'ns:b'
expect(collection.a1Handler).was.calledOnce()
expect(collection.a2Handler).was.calledOnce()

describe 'Disposal', ->
it 'should dispose itself correctly', ->
expect(collection.dispose).to.be.a 'function'
Expand Down
45 changes: 45 additions & 0 deletions test/spec/controller_spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,51 @@ define [
expect(spy).was.calledOnce()
expect(spy).was.calledWith 'meh'

describe 'Events', ->
class EventedController extends Controller
listen:
# self
'ns:a': 'a1Handler'
'ns:b': ->
@a2Handler arguments...

# mediator
'ns:a mediator': 'a1Handler'
'ns:b mediator': 'a2Handler'

initialize: ->
super
@a1Handler = sinon.spy()
@a2Handler = sinon.spy()

it 'should bind to own events declaratively', ->
controller = new EventedController()

expect(controller.a1Handler).was.notCalled()
expect(controller.a2Handler).was.notCalled()

controller.trigger 'ns:a'
expect(controller.a1Handler).was.calledOnce()
expect(controller.a2Handler).was.notCalled()

controller.trigger 'ns:b'
expect(controller.a1Handler).was.calledOnce()
expect(controller.a2Handler).was.calledOnce()

it 'should bind to mediator events declaratively', ->
controller = new EventedController()

expect(controller.a1Handler).was.notCalled()
expect(controller.a2Handler).was.notCalled()

mediator.publish 'ns:a'
expect(controller.a1Handler).was.calledOnce()
expect(controller.a2Handler).was.notCalled()

mediator.publish 'ns:b'
expect(controller.a1Handler).was.calledOnce()
expect(controller.a2Handler).was.calledOnce()

describe 'Disposal', ->
mediator.setHandler 'region:unregister', ->

Expand Down
41 changes: 41 additions & 0 deletions test/spec/model_spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,47 @@ define [
expect(actual.collection[0].number).to.be 'four'
expect(actual.collection[1].number).to.be 'five'

describe 'Events', ->
class EventedModel extends Model
listen:
# self
'ns:a': 'a1Handler'
'change:test': ->
@a2Handler arguments...

# mediator
'ns:a mediator': 'a1Handler'
'ns:b mediator': 'a2Handler'

initialize: ->
super
@a1Handler = sinon.spy()
@a2Handler = sinon.spy()

it 'should bind to own events declaratively', ->
model = new EventedModel()

expect(model.a1Handler).was.notCalled()
expect(model.a2Handler).was.notCalled()

model.trigger 'ns:a'
expect(model.a1Handler).was.calledOnce()
expect(model.a2Handler).was.notCalled()

model.trigger 'change:test'
expect(model.a1Handler).was.calledOnce()
expect(model.a2Handler).was.calledOnce()

it 'should bind to mediator events declaratively', ->
model = new EventedModel()

expect(model.a1Handler).was.notCalled()
expect(model.a2Handler).was.notCalled()

mediator.publish 'ns:a'
expect(model.a1Handler).was.calledOnce()
expect(model.a2Handler).was.notCalled()

describe 'Disposal', ->
it 'should dispose itself correctly', ->
expect(model.dispose).to.be.a 'function'
Expand Down