Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 08a33e7

Browse files
committed
feat(scope): support for events
- register listeners with $on - remove listeners with $removeListener - fire event that bubbles to root with $emit - fire event that propagates to all child scopes with $broadcast
1 parent 30753cb commit 08a33e7

File tree

2 files changed

+451
-77
lines changed

2 files changed

+451
-77
lines changed

src/Scope.js

+172-3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ function Scope() {
9999
this.$destructor = noop;
100100
this['this'] = this.$root = this;
101101
this.$$asyncQueue = [];
102+
this.$$listeners = {};
102103
}
103104

104105
/**
@@ -155,8 +156,8 @@ Scope.prototype = {
155156
* the scope and its child scopes to be permanently detached from the parent and thus stop
156157
* participating in model change detection and listener notification by invoking.
157158
*
158-
* @param {function()=} constructor Constructor function which the scope should behave as.
159-
* @param {curryArguments=} ... Any additional arguments which are curried into the constructor.
159+
* @param {function()=} Class Constructor function which the scope should be applied to the scope.
160+
* @param {...*} curryArguments Any additional arguments which are curried into the constructor.
160161
* See {@link guide/dev_guide.di dependency injection}.
161162
* @returns {Object} The newly created child scope.
162163
*
@@ -169,6 +170,7 @@ Scope.prototype = {
169170
Child.prototype = this;
170171
child = new Child();
171172
child['this'] = child;
173+
child.$$listeners = {};
172174
child.$parent = this;
173175
child.$id = nextUid();
174176
child.$$asyncQueue = [];
@@ -392,12 +394,15 @@ Scope.prototype = {
392394
* scope and its children. Removal also implies that the current scope is eligible for garbage
393395
* collection.
394396
*
397+
* The destructing scope emits an `$destroy` {@link angular.scope.$emit event}.
398+
*
395399
* The `$destroy()` is usually used by directives such as
396400
* {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop.
397401
*
398402
*/
399403
$destroy: function() {
400404
if (this.$root == this) return; // we can't remove the root node;
405+
this.$emit('$destroy');
401406
var parent = this.$parent;
402407

403408
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
@@ -413,7 +418,7 @@ Scope.prototype = {
413418
* @function
414419
*
415420
* @description
416-
* Executes the expression on the current scope returning the result. Any exceptions in the
421+
* Executes the `expression` on the current scope returning the result. Any exceptions in the
417422
* expression are propagated (uncaught). This is useful when evaluating engular expressions.
418423
*
419424
* # Example
@@ -520,9 +525,173 @@ Scope.prototype = {
520525
} finally {
521526
this.$root.$digest();
522527
}
528+
},
529+
530+
/**
531+
* @workInProgress
532+
* @ngdoc function
533+
* @name angular.scope.$on
534+
* @function
535+
*
536+
* @description
537+
* Listen on events of a given type. See {@link angular.scope.$emit $emit} for discussion of
538+
* event life cycle.
539+
*
540+
* @param {string} name Event name to listen on.
541+
* @param {function(event)} listener Function to call when the event is emitted.
542+
*
543+
* The event listener function format is: `function(event)`. The `event` object passed into the
544+
* listener has the following attributes
545+
* - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
546+
* - `currentScope` - {Scope}: the current scope which is handling the event.
547+
* - `name` - {string}: Name of the event.
548+
* - `cancel` - {function=}: calling `cancel` function will cancel further event propagation
549+
* (available only for events that were `$emit`-ed).
550+
*/
551+
$on: function(name, listener) {
552+
var namedListeners = this.$$listeners[name];
553+
if (!namedListeners) {
554+
this.$$listeners[name] = namedListeners = [];
555+
}
556+
namedListeners.push(listener);
557+
},
558+
559+
/**
560+
* @workInProgress
561+
* @ngdoc function
562+
* @name angular.scope.$removeListener
563+
* @function
564+
*
565+
* @description
566+
* Remove the on listener registered by {@link angular.scope.$on $on}.
567+
*
568+
* @param {string} name Event name to remove on.
569+
* @param {function} listener Function to remove.
570+
*/
571+
$removeListener: function(name, listener) {
572+
var namedListeners = this.$$listeners[name];
573+
var i;
574+
if (namedListeners) {
575+
i = namedListeners.indexOf(listener);
576+
namedListeners.splice(i, 1);
577+
}
578+
},
579+
580+
/**
581+
* @workInProgress
582+
* @ngdoc function
583+
* @name angular.scope.$emit
584+
* @function
585+
*
586+
* @description
587+
* Dispatches an event `name` upwards through the scope hierarchy notifying the
588+
* registered {@link angular.scope.$on} listeners.
589+
*
590+
* The event life cycle starts at the scope on which `$emit` was called. All
591+
* {@link angular.scope.$on listeners} listening for `name` event on this scope get notified.
592+
* Afterwards, the event traverses upwards toward the root scope and calls all registered
593+
* listeners along the way. The event will stop propagating if one of the listeners cancels it.
594+
*
595+
* Any exception emmited from the {@link angular.scope.$on listeners} will be passed
596+
* onto the {@link angular.service.$exceptionHandler $exceptionHandler} service.
597+
*
598+
* @param {string} name Event name to emit.
599+
* @param {...*} args Optional set of arguments which will be passed onto the event listeners.
600+
*/
601+
$emit: function(name, args) {
602+
var empty = [],
603+
namedListeners,
604+
canceled = false,
605+
scope = this,
606+
event = {
607+
name: name,
608+
targetScope: scope,
609+
cancel: function(){canceled = true;}
610+
},
611+
listenerArgs = concat([event], arguments, 1),
612+
i, length;
613+
614+
do {
615+
namedListeners = scope.$$listeners[name] || empty;
616+
event.currentScope = scope;
617+
for (i=0, length=namedListeners.length; i<length; i++) {
618+
try {
619+
namedListeners[i].apply(null, listenerArgs);
620+
if (canceled) return;
621+
} catch (e) {
622+
scope.$service('$exceptionHandler')(e);
623+
}
624+
}
625+
//traverse upwards
626+
scope = scope.$parent;
627+
} while (scope);
628+
},
629+
630+
631+
/**
632+
* @workInProgress
633+
* @ngdoc function
634+
* @name angular.scope.$broadcast
635+
* @function
636+
*
637+
* @description
638+
* Dispatches an event `name` downwards to all child scopes (and their children) notifying the
639+
* registered {@link angular.scope.$on} listeners.
640+
*
641+
* The event life cycle starts at the scope on which `$broadcast` was called. All
642+
* {@link angular.scope.$on listeners} listening for `name` event on this scope get notified.
643+
* Afterwards, the event propagates to all direct and indirect scopes of the current scope and
644+
* calls all registered listeners along the way. The event cannot be canceled.
645+
*
646+
* Any exception emmited from the {@link angular.scope.$on listeners} will be passed
647+
* onto the {@link angular.service.$exceptionHandler $exceptionHandler} service.
648+
*
649+
* @param {string} name Event name to emit.
650+
* @param {...*} args Optional set of arguments which will be passed onto the event listeners.
651+
*/
652+
$broadcast: function(name, args) {
653+
var targetScope = this,
654+
currentScope = targetScope,
655+
nextScope = targetScope,
656+
event = { name: name,
657+
targetScope: targetScope },
658+
listenerArgs = concat([event], arguments, 1);
659+
660+
//down while you can, then up and next sibling or up and next sibling until back at root
661+
do {
662+
currentScope = nextScope;
663+
event.currentScope = currentScope;
664+
forEach(currentScope.$$listeners[name], function(listener) {
665+
try {
666+
listener.apply(null, listenerArgs);
667+
} catch(e) {
668+
currentScope.$service('$exceptionHandler')(e);
669+
}
670+
});
671+
672+
// down or to the right!
673+
nextScope = currentScope.$$childHead || currentScope.$$nextSibling;
674+
675+
if (nextScope) {
676+
// found child or sibling
677+
continue;
678+
}
679+
680+
// we have to restore nextScope and go up!
681+
nextScope = currentScope;
682+
683+
while (!nextScope.$$nextSibling && (nextScope != targetScope)) {
684+
nextScope = nextScope.$parent;
685+
}
686+
687+
if (nextScope != targetScope) {
688+
nextScope = nextScope.$$nextSibling;
689+
}
690+
} while (nextScope != targetScope);
523691
}
524692
};
525693

694+
526695
function compileToFn(exp, name) {
527696
var fn = isString(exp)
528697
? expressionCompile(exp)

0 commit comments

Comments
 (0)