From e5bbbd450ccab08c88a2e45137f41fcbf387c02f Mon Sep 17 00:00:00 2001 From: Ross Brown Date: Fri, 25 Apr 2014 16:12:13 -0500 Subject: [PATCH 1/2] listen object binds events for controllers and models --- src/chaplin/controllers/controller.coffee | 4 ++ src/chaplin/lib/event_broker.coffee | 32 ++++++++++++++++ src/chaplin/models/model.coffee | 7 ++++ src/chaplin/views/view.coffee | 31 ---------------- test/spec/controller_spec.coffee | 45 +++++++++++++++++++++++ test/spec/model_spec.coffee | 41 +++++++++++++++++++++ 6 files changed, 129 insertions(+), 31 deletions(-) diff --git a/src/chaplin/controllers/controller.coffee b/src/chaplin/controllers/controller.coffee index 393ce17c..8a6a8553 100644 --- a/src/chaplin/controllers/controller.coffee +++ b/src/chaplin/controllers/controller.coffee @@ -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. diff --git a/src/chaplin/lib/event_broker.coffee b/src/chaplin/lib/event_broker.coffee index 59cad390..4bcbc496 100644 --- a/src/chaplin/lib/event_broker.coffee +++ b/src/chaplin/lib/event_broker.coffee @@ -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 @@ -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 diff --git a/src/chaplin/models/model.coffee b/src/chaplin/models/model.coffee index 62c86485..12a0f788 100644 --- a/src/chaplin/models/model.coffee +++ b/src/chaplin/models/model.coffee @@ -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. diff --git a/src/chaplin/views/view.coffee b/src/chaplin/views/view.coffee index 2930ee62..f7f1f27c 100644 --- a/src/chaplin/views/view.coffee +++ b/src/chaplin/views/view.coffee @@ -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 # ----------------- diff --git a/test/spec/controller_spec.coffee b/test/spec/controller_spec.coffee index a4c0b19b..6acc638e 100644 --- a/test/spec/controller_spec.coffee +++ b/test/spec/controller_spec.coffee @@ -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', -> diff --git a/test/spec/model_spec.coffee b/test/spec/model_spec.coffee index ebd0de5a..9ed9a62e 100644 --- a/test/spec/model_spec.coffee +++ b/test/spec/model_spec.coffee @@ -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' From 4fef6622e7ee4afa6e7d35a2f25d59797c611c76 Mon Sep 17 00:00:00 2001 From: Ross Brown Date: Fri, 25 Apr 2014 16:27:26 -0500 Subject: [PATCH 2/2] declarative bindings on collections --- src/chaplin/models/collection.coffee | 6 ++++ test/spec/collection_spec.coffee | 45 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/chaplin/models/collection.coffee b/src/chaplin/models/collection.coffee index d53c2eef..737d4871 100644 --- a/src/chaplin/models/collection.coffee +++ b/src/chaplin/models/collection.coffee @@ -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 diff --git a/test/spec/collection_spec.coffee b/test/spec/collection_spec.coffee index 6c14436b..82bb04ce 100644 --- a/test/spec/collection_spec.coffee +++ b/test/spec/collection_spec.coffee @@ -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'