Skip to content

Commit

Permalink
Merge branch 'release-0.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
millermedeiros committed Nov 27, 2010
2 parents ce5106e + e6e4893 commit ad5e3eb
Show file tree
Hide file tree
Showing 9 changed files with 405 additions and 44 deletions.
34 changes: 34 additions & 0 deletions CHANGELOG.markdown
@@ -0,0 +1,34 @@
# JS-Signals Changelog #


## v0.2 (2010/11/26) ##

### API changes ###

- Added:
- `Signal.prototype.pause()`
- `Signal.prototype.resume()`
- `Signal.prototype.isPaused()`
- `Signal.prototype.stopPropagation()`

### Fixes ###

- `SignalBinding.prototype.isPaused()`

### Test Changes ###

- Increased test coverage a lot.
- Tests added:
- pause/resume (for individual bindings and signal)
- stopPropagation (using `return false` and `Signal.prototype.stopPropagation()`)
- `SignalBindings.prototype.isOnce()`
- if same listener added twice returns same binding

### Other ###

Small refactoring and code cleaning.


## v0.1 (2010/11/26) ##

- initial release, support of basic features.
115 changes: 113 additions & 2 deletions README.markdown
Expand Up @@ -3,15 +3,95 @@

Custom event/messaging system for JavaScript inspired by [AS3-Signals](https://github.com/robertpenner/as3-signals).


## Introduction ##

A Signal is similar to a EventTarget/EventDispatcher or a pub/sub system, the main difference is that each event kind has it's own controller and doesn't rely on strings to call proper callbacks.

Another advantage is that you can pass arbitrary parameters to callbacks and it also have some convenience methods that aren't present on other *observer pattern* implementations.

This implementation is heavily inspired by Robert Penner's AS3-Signals but it has a different set of features/classes, the main target of this implementation is *custom events* and not replacing *native DOM events*.


## Comparisson between *observers* ##

The comparisson below is just about the basic features of subscribing to an event type, dispatching and removing an event listener. It isn't based on available features but on differences between each concept and pros and cons of using each one.

### Event Target / Event Dispatcher ###

- Each Object that dispatches custom events needs to inherit from an EventTarget/EventDispatcher object or implement the proper interface.
- Use strings to identify the event type.
- DOM2/DOM3 Events are based on this principle.

#### Code sample ####

myObject.addEventListener('myCustomEventTypeString', handler);
myObject.dispatch(new Event('myCustomEventTypeString'));
myObject.removeEventListener('myCustomEventTypeString', handler);

#### Pros ####

- You have total control of the *target object* and make sure you are listening only to the events dispatched by the specific target.
- Can dispatch arbitrary event types without modifying the target object.

#### Cons ####

- Favors inheritance over composition.
- Uses strings to identify event types, prone to typo errors and autocomplete doens't work properly.


### Publish / Subscribe ###

- Uses a single object to *broadcast messages* to multiple *subscribers*.
- Use strings to identify the event type.

#### Code sample ####

globalBroadcaster.subscribe('myCustomEventTypeString', handler);
globalBroadcaster.publish('myCustomEventTypeString', paramsArray);
globalBroadcaster.unsubscribe('myCustomEventTypeString', handler);

#### Pros ####

- Any object can publish/subscribe to any event type.
- Light-weigth.
- Easy to use.

#### Cons ####

- Any object can publish/subscribe to any event type. *(yeap, it's pro and con at the same time)*
- Uses strings to identify event types (error prone and no auto-complete).


### Signals ###

- Each event type has it's own controller.
- Doesn't rely on strings for event types.

#### Code sample ####

myObject.myCustomEventType.add(handler);
myObject.myCustomEventType.dispatch(param1, param2, ...);
myObject.myCustomEventType.remove(handler);

#### Pros ####

- Doesn't rely on strings.
- auto-complete works properly.
- easy do identify wich *signals* the object dispatch.
- less error prone.
- Granular control over each listener and event type (easily).

#### Cons ####

- Can't dispatch arbitrary events. *(which is also a pro in most cases)*
- Each event-type is an object member. *(which is also a pro in most cases)*


## Examples ##

### Setup code (required for all examples) ###

//store local reference for brevity
var Signal = signals.Signal;

Expand All @@ -20,7 +100,10 @@ Another advantage is that you can pass arbitrary parameters to callbacks and it
started : new Signal(),
stopped : new Signal()
};



### Single Listener ###

function onStarted(param1, param2){
alert(param1 + param2);
}
Expand All @@ -33,7 +116,10 @@ Another advantage is that you can pass arbitrary parameters to callbacks and it

//remove a single listener
myObject.started.remove(onStarted);



### Multiple Listeners ###

function onStopped(){
alert('stopped');
}
Expand All @@ -51,3 +137,28 @@ Another advantage is that you can pass arbitrary parameters to callbacks and it

//remove all listeners of the `stopped` signal
myObject.stopped.removeAll();


### Stop Propagation (method 1) ###

myObject.started.add(function(){
myObject.started.stopPropagation();
});
myObject.started.add(function(){
//won't be called since first listener stops propagation
alert('second listener');
});
myObject.started.dispatch();


### Stop Propagation (method 2) ###

myObject.started.add(function(){
return false; //stop propagation
});
myObject.started.add(function(){
//won't be called since first listener stops propagation
alert('second listener');
});
myObject.started.dispatch();

4 changes: 2 additions & 2 deletions dev/build/build.number
@@ -1,3 +1,3 @@
#Build Number for ANT. Do not edit!
#Fri Nov 26 03:47:05 EST 2010
build.number=30
#Fri Nov 26 19:15:08 EST 2010
build.number=47
2 changes: 1 addition & 1 deletion dev/build/build.properties
Expand Up @@ -4,6 +4,6 @@ build.dir = ${dev.dir}/build
dist.dir = dist
yuicompressor.jar = ${build.dir}/yuicompressor/yuicompressor-2.4.2.jar
product.name = js-signals
version.number = 0.0.1
version.number = 0.2
dist.name = ${product.name}.js
dist.min.name = ${product.name}.min.js
52 changes: 37 additions & 15 deletions dev/src/Signal.js
Expand Up @@ -16,21 +16,23 @@

signals.Signal.prototype = {

_registerListener : function(listener, isOnce, scope){
var prevBinding,
prevIndex = this._indexOfListener(listener),
_shouldPropagate : true,

_isPaused : false,

_registerListener : function _registerListener(listener, isOnce, scope){
var prevIndex = this._indexOfListener(listener),
binding;

if(prevIndex != -1){ //avoid creating a new Binding if already added to list
prevBinding = this._bindings[prevIndex];
if(prevIndex !== -1){ //avoid creating a new Binding for same listener if already added to list
binding = this._bindings[prevIndex];

if(prevBinding.isOnce() && !isOnce){
if(binding.isOnce() && !isOnce){
throw new Error('You cannot addOnce() then add() the same listener without removing the relationship first.');
}else if(!prevBinding.isOnce() && isOnce){
}else if(!binding.isOnce() && isOnce){
throw new Error('You cannot add() then addOnce() the same listener without removing the relationship first.');
}

binding = prevBinding;
} else {
binding = new signals.SignalBinding(listener, isOnce, scope, this);
this._bindings.push(binding);
Expand All @@ -47,17 +49,17 @@
return -1;
},

add : function(listener, scope){
add : function add(listener, scope){
return this._registerListener(listener, false, scope);
},

addOnce : function(listener, scope){
addOnce : function addOnce(listener, scope){
return this._registerListener(listener, true, scope);
},

remove : function remove(listener){
var i = this._indexOfListener(listener);
if(i != -1){
if(i !== -1){
this._bindings.splice(i, 1);
}
return listener;
Expand All @@ -71,19 +73,39 @@
return this._bindings.length;
},

dispatch : function(){
pause : function pause(){
this._isPaused = true;
},

resume : function resume(){
this._isPaused = false;
},

isPaused : function isPaused(){
return this._isPaused;
},

stopPropagation : function stopPropagation(){
this._shouldPropagate = false;
},

dispatch : function dispatch(params){
if(this._isPaused) return;

var paramsArr = Array.prototype.slice.call(arguments),
i = 0,
bindings = this._bindings.slice(), //clone array in case add/remove items during dispatch
i = 0,
cur;

while(cur = bindings[i++]){
cur.execute(paramsArr);
if(cur.execute(paramsArr) === false || !this._shouldPropagate) break; //execute all callbacks until end of the list or until a callback returns `false`
}

this._shouldPropagate = true;
},

toString : function toString(){
return '[Signal numListeners: '+ this.getNumListeners() +']';
return '[Signal isPaused: '+ this._isPaused +' numListeners: '+ this.getNumListeners() +']';
}

};
4 changes: 2 additions & 2 deletions dev/src/SignalBinding.js
Expand Up @@ -20,7 +20,7 @@
execute : function execute(paramsArr){
if(! this._isPaused){
if(this._isOnce) this._signal.remove(this.listener);
this.listener.apply(this.listenerScope, paramsArr);
return this.listener.apply(this.listenerScope, paramsArr);
}
},

Expand All @@ -33,7 +33,7 @@
},

isPaused : function isPaused(){
return this._paused;
return this._isPaused;
},

isOnce : function isOnce(){
Expand Down

0 comments on commit ad5e3eb

Please sign in to comment.