JooseX.Observable - cross-plaform implementation of the Observable pattern
// Simple events
Class('My.Class', {
does : JooseX.Observable
})
var instance = new My.Class()
var handler = function (event, param1, param2) {
// event.source == instance
// param1 == 'foo'
// param2 == 'bar'
}
instance.on('event', handler, scope, { single : true, delay : 100 })
instance.fireEvent('event', 'foo', 'bar')
// Hierarchical events #1
var handler = function (event, param1, param2) {
// event.source == instance
// event.splat == 'event'
// param1 == 'foo'
// param2 == 'bar'
}
instance.on('/some/*', handler, scope, { single : true, buffer : 100 })
instance.emit('/some/event', 'foo', 'bar')
// Hierarchical events #2
var handler = function (event, param1, param2) {
// event.source == instance
// event.splat == [ 'long', 'event' ]
// param1 == 'foo'
// param2 == 'bar'
}
instance.on('/some/**', handler, scope, { single : true, buffer : 100 })
instance.fireEvent('/some/long/event', 'foo', 'bar')
// Bubbling
Class('Container', {
does : JooseX.Observable
})
Class('Component', {
does : JooseX.Observable,
has : {
parentContainer : { required : true }
},
methods : {
getBubbleTarget : function () {
return this.parentContainer
}
}
})
var container = new Container()
var component = new Component({
parentContainer : container
})
var handler = function (event, param1, param2) {
// event.source == component
// event.current == container
event.stopPropagation() // stop further bubbling
}
container.on('/some/**', handler, scope, { single : true, buffer : 100 })
component.fireEvent('/some/long/event', 'foo', 'bar')
From npm
:
> [sudo] npm install joosex-observable
Tarballs are available for downloading at: http://search.npmjs.org/#/joosex-observable
In NodeJS:
require('task-joose-nodejs')
require('joosex-observable')
In browsers (assuming you've completed the 3.1 item from this document):
<script type="text/javascript" src="/jsan/Task/Joose/Core.js"></script>
<script type="text/javascript" src="/jsan/Task/JooseX/Observable/Core.js"></script>
JooseX.Observable
is a Joose role, implementing the Observable pattern. To use it, just add it to your class declaration:
Class('My.Class', {
does : JooseX.Observable
})
In Observable pattern, class instances may notify the world about the changes in its state with "events". One can say instance "fire" or "emit" the events. Other instances may "observe" such notifications ("listen to events").
To fire (emit) the event use fireEvent
or emit
methods (both are identical):
var instance = new My.Class()
instance.fireEvent('event', 'foo', 'bar')
The first argument to this method is the event name, all other arguments becomes the arguments of the event and will be available in the listeners.
To listen the event, use the method on
:
var handler = function (event, param1, param2) {
// event.source == instance
// param1 == 'foo'
// param2 == 'bar'
}
var listener = instance.on('event', handler, scope, { single : true, delay : 100 })
This method accept the event name, handler function, optional scope and optional options (see below for details). It returns the "listener" instance.
Each time the event being fired, all listeners with matching name will be activated. Their handler functions will always receive the instance of event as the 1st argument
(see above) and then other arguments from fireEvent
. The object, which fired the event is available as the source
property of the event.
To remove the listener you can either pass it to the method un
:
instance.un(listener)
or call its remove
method:
listener.remove()
or still use the un
specifying the same handler and scope as during on
:
instance.un('event', handler, scope)
If the event name will contain slash(es) /
, then it will be considered hierarchical. Hierarchical events behaves pretty much the same as usual events, with couple of additional features.
The very last segment of the event name will be treated as the "event name". Other segments will be treated as the "event channel". For example:
// fires the event "foo" in the channel "/"
instance.fireEvent('/foo')
// fires the event "bar" in the channel "/foo"
instance.fireEvent('/foo/bar')
// fires the event "" (empty string) in the channel "/foo/bar"
instance.fireEvent('/foo/bar/')
The same rules applies for the listeners, plus you can use the *
and **
wildcards:
// will be activated by "/foo" only
instance.on('/foo')
// will be activated by "/foo/bar" only
instance.on('/foo/bar')
// will be activated by "/foo/bar", "/foo/baz", "/foo/" (empty string)
// but not by "/foo/bar/baz" or "/bar"
instance.on('/foo/*')
// will be activated by "/foo/bar", "/foo/baz", "/foo/" (empty string), "/foo/bar/baz", "/foo/bar/baz/quix" etc
// but not by "/bar"
instance.on('/foo/**')
Hierarchical events are useful, when instance may emit a lot of different events, which can be categorized in groups, and listeners need the ability to listen on the whole group of events.
See also JooseX.Observable.Event documentation to know how to introspect the event
instance.
Additionally, all events bubbles (a-la DOM). This is useful, when the instance participate in the nested "part/whole" relationships and the "whole" needs to listen the events from the inner "parts".
To use this feature, provide the implementation of the getBubbleTarget
method in the consuming class:
Class('Component', {
does : JooseX.Observable,
methods : {
getBubbleTarget : function () {
}
}
})
In the handler, you may call the stopPropagation
method of the event, to stop bubbling:
var handler = function (event, param1, param2) {
// event.source == component
// event.current == container
event.stopPropagation() // stop further bubbling
}
See also JooseX.Observable.Event documentation.
Number suspendCounter
Initially set to 0. When this attribute is bigger than 0 the instance won't emit any events.
on(eventName, func, scope?, options?)
This method creates a new listener for the event
eventName
. Listener will activate the functionfunc
using either providedscope
or the event source as the scope. Optionaloptions
parameter should contain object with properties, corresponding to attributes of JooseX.Observable.Listener
Returns an instance of JooseX.Observable.Listener
un(eventName, func, scope)
un(listener)
This method will remove the listener from this instance. Either accept an instance of JooseX.Observable.Listener or the same arguments as were used for
on
emit(eventName, param1, param2, ...)
fireEvent(eventName, param1, param2, ...)
These methods are identical and both "fires" the event (activates all listeners with names matching to
eventName
). The handlers of the listeners will receive the instance of JooseX.Observable.Event as the 1st argument and the remaining parameters as other arguments
hasListenerFor(eventName)
Returns boolean value, indicating whether this instance has listeners for the
eventName
.eventName
may contain*
and**
wildcards.
purgeListeners()
Removes all listeners
suspendEvents()
This method increase the [suspendCounter] attribute and prevents any events from being emitted
resumeEvents()
This method decrease the [suspendCounter] attribute. As soon as it becomes 0 again, instance can emit events.
This extension is supported via github issues tracker: http://github.com/SamuraiJack/JooseX-Observable/issues
For general Joose questions you can also visit #joose on irc.freenode.org or mailing list at: http://groups.google.com/group/joose-js
Web page of this module: http://github.com/SamuraiJack/JooseX-Observable/
General documentation for Joose: http://joose.github.com/Joose/
All complex software has bugs lurking in it, and this module is no exception.
Please report any bugs through the web interface at http://github.com/SamuraiJack/JooseX-Observable/issues
Nickolay Platonov nplatonov@cpan.org
BSD 2-Clause License
Copyright (c) 2010-2016, Nickolay Platonov nplatonov@cpan.org. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.