Powerful application events and event handling, made easy.
Download the full, minified, or server versions.
Bower: bower install eventi
NPM: npm install eventi
Component: component install esha/Eventi
Creates and dispatches the specified event(s) on the specified target(s), attaching any specified data.
Arguments | Required | Type(s) | Default | Description |
---|---|---|---|---|
target | No | Object, Array | global/window | A single target or array of them. |
eventi | Yes | String, Event, Array | One or more space-delimited Eventi definitions, an Event instance, or an array of either (in which case you must specify a target argument). | |
data | No | Any number of arguments that will be put in an Array and attached to all created events as the data property. |
*When passing events in an array, you must specify a target argument.
Any attached data will be fed to the event listeners as additional arguments after the event itself and any data registered along with the listener itself.
Eventi.fire(user, 'account:close', accountId);
Registers the specified function as an event listener for the specified event(s) on the specified target(s).
Arguments | Required | Type(s) | Default | Description |
---|---|---|---|---|
target | No | Object, Array | global/window | A single target or array of them. |
eventi | Yes | String, Array | One or more space-delimited Eventi definitions or an array of them (in which case you must specify a target argument). | |
fn | Yes | Function | The function to be called when a matching event hits the target(s). | |
data | No | * | Any number of arguments that will be passed directly to the listener function as arguments (after the event argument). |
The function argument will be called with the target as its context, the received event as the first argument, and data arguments registered with the listener as the next arguments, and any data arguments attached to the event itself after those.
Eventi.on(user, 'account:open', function(e) {
console.log('Event: type='+e.type+' category='+e.category, this/* will be user */);
});
Overloading of on()
for convenient registration of multiple listeners at once. Iterates over the object argument, registering key/value pairs as eventi/fn pairs.
Eventi.on(user, {
'account:open', function(e) {
console.log('Event: type='+e.type+' category='+e.category, this/* will be user */);
},
'account:close', function(e, accountId) {
console.log('User: '+this.id+' is closing account '+accountId);
}
});
Registers event listeners on the element that search between an Event's target and the listening element for an element with a handler attribute (e.g. data-click="..."
) telling the listener how to handle the event.
Attributes | Required | Content | Description
--------- | -------- | ------- | ------- | -----------
eventi, data-eventi | For all events except click
| Space-delimited Eventi definitions | Defines listeners for an element.
{alias}, data-{alias} | No | Element function, global function, Eventi definition | Either a reference to the function to be called, an Eventi definition to be fired on the element, or nothing, in which case the alias is used as the content.
<div id="admin" data-eventi="/beforeunload=>save keyup[delete]<.account>=>delete">
<ul class="accounts">
<li class="account" tabindex="0" delete="account:close">Savings</li>
...
</ul>
<button click="account:open">New Account</button>
</div>
Unregisters event listeners that match the specified event(s) and/or function on the specified target(s), or the global/window object, if none is specified.
Arguments | Required | Type(s) | Default | Description |
---|---|---|---|---|
target | No | Object, Array | global/window | A single target or array of them. |
eventi | No | String, Array | One or more space-delimited Eventi definitions (or portions thereof) or an array of them (in which case you must specify a target argument) | |
fn | No | Function | The specific listener function to be removed. |
Eventi.off(user, 'account:');
Defines aliases for the events specified. It creates sub-functions for on()
, off()
, and fire()
under the alias (or simple type if no alias is defined).
Arguments | Required | Type | Default | Description |
---|---|---|---|---|
context | No | Object | Eventi | An object that has been run through Eventi.fy() whose methods you want the aliases set for. |
eventi | Yes | String | One or more event space-delimited Eventi definitions. |
Eventi.alias('account:request#freeze=>hold');
Eventi.on.hold(user, function(e, admin) {
console.log(admin.id+' is requesting freeze for '+user.id+' accounts');
});
//...
Eventi.fire.hold(user, admin);
Convenience tool that simply defines on()
, off()
, and fire()
, making the specified target object the context and target for those functions.
Arguments | Required | Type | Default | Description |
---|---|---|---|---|
context | Yes | Object | A target object you would like to call fire/on/off upon directly. |
Eventi.fy(user);
user.on('account:open', function(e) {
console.log('Open new account for '+this/* will be user */);
});
Eventi.fy(Element.prototype);// lets you call Eventi functions on all DOM elements
Example definition: open
This is the central, "action" portion in an Eventi definition. It is not optional in most definitions, with the exception of calls to off()
. It is what you have left after you strip off all the other event and handler properties described below here. It is an Event instance's type
property. This should typically be a simple verb, in present or past tense.
var e = new Eventi('open');
console.log(e.type);// outputs 'open'
Example definition: account:open
This is the "subject" option in an Eventi definition. The category always precedes the type and is delimited from it by a :
. It is an Eventi instance's category
property. This should typically be a simple noun, and represent the subject or owner of a set of events. It is good practice to provide a category for your events unless you specifically mean them to be broadly applied.
var e = Eventi.fire('account:open');
console.log(e.category);// outputs 'account'
Example definition: move#up#left
These are the "adverb" or "adjective" portions of Eventi definitions. They always follow the type and are delimited from it and each other by #
. When creating an Event, they are set together in an array on the instance as the tags
property and each individual tag is set on the instance as a property with a value of true
. Use these to distinguish related sub-types of events or provide simple annotations for the subject or object of an event.
Eventi.on('open#new', function(e) {
if (e.unverified) {
console.log(e.tags.join(' & '));// outputs 'new & unverified'
}
}).fire('open#new#unverified');
Example definition: account:label(["verified","gold"])
This is the "object" portion of an Eventi definition. It follows the type and is delimited by (
and )
. The contents may be either JSON (which will get parsed), a reference to a global value (which will be resolved), or an arbitrary string. The result is an instance's detail
property. Use this to declaratively attach contextual information for an event at the moment it is created.
window.user = { name: "jdoe123" };
var e = Eventi.fire('like(user.name)');
console.log(e.detail);// outputs 'jdoe123'
Example definition: cancel<.transaction>
This specifies a selector for event delegation and is delimited by <
and >
. It instructs a listener to only react to events happening within the bounds of elements matching the specified selector and to use the matching element as the context for the handling function.
Eventi.on('change<[type=checkbox]>', function(e) {
console.log(this.type);// always outputs 'checkbox'
});
Example definition: keyup[delete]
This specifies a key or combination thereof, by either keyCode or description, and is delimited by [
and ]
. It instructs a listener to only react when the event has the appropriate keyCode/ctrlKey/metaKey/shiftKey/altKey properties. Particular keyCodes may be specified explicitly (e.g. keydown[33]
), but many have been given English or symbolic equivalents for the sake of readable code (e.g. keypress[z]
). These are stored in the Eventi._.codes
object and are keyup-based values, so keydown
and keypress
definitions should be wary of those outside ASCII. The other key-related event properties are set simply by giving them -
as a suffix (e.g. [shift-13]
-> e.shiftKey = true
, [meta-]
-> e.metaKey = true
, etc).
The [Key] syntax is also special in that it will provide keyup
as the type for event listeners (for on()
only) that have a [Key] specified, but no type. Typically, event listeners with no type are never executed.
Eventi.on(field, '[shift-enter]', function(e) {
console.log(e.shiftKey, e.keyCode);// outputs 'true 13'
});
Example definition: keypress[escape]@/edit
This specifies a url pattern that is matched against the current window.location
to filter event listener execution.
There are three types of @
location values:
@/vanilla#string
- Will be directly matched against the URL string retrieved from window.location
.
@?mini={template}
- Turns the sections in braces into wildcard matches (breaking on \
,?
, and #
) whose values are gathered into a key/value object, passed as the 2nd argument to the event handler.
@`reg[ular]+Exp?`
- For more complicated matching, use a regular expression. Use ` to delimit it. Handlers will receive the full match as the 2nd argument (after the event itself) and matching groups as subsequent arguments. Any handler or event data args will follow the location matching arguments.
Like [Key], @Location enables shorthand definitions by setting 'location' as the type for event listeners (for on()
only) that have a location property, but no type property.
Eventi.on('save@#file={filename}', function(e, vals, folder) {
console.log('Saved '+vals.filename+' in '+folder);// outputs 'Saved image.png in /pics'
});
window.location.hash = 'file=image.png';
Eventi.fire('save', '/pics');
Eventi also provides a unified location
event that is dispatched on the window
whenever the current location changes, whether via hashchange
and popstate
events or calls to history.pushState
(which gets its own pushstate
event as well). When registering a listener for a location
event, the location is promptly tested (as if a location
event was fired), allowing immediate execution of handlers for currently matching locations. Also, when a location
type event is dispatched (e.g. fire('location@/path')
), the @Location will be used to update window.location
via history.pushState
.
// assume URL is http://esha.github.io/ to start
var s = '';
Eventi.on('location', function(e) {
s += ' '+e.location;
});
window.location.hash = 'hash';
history.pushState(null,'push it', '?push');
history.go(-2);// popstate
console.log(s);// outputs ' / /#hash /?push /#hash /'
Eventi.on('@/login', function(e, match) {// will listen for 'location' type
console.log(e.type, match);// outputs 'location login'
});
Eventi.fire('location@/login');
@Location's mini-templates can work in reverse when updating the location via event. Just pass a key/value object back as the first data argument (either at listener registration or event firing).
Eventi.fire('location@?page={page}', {page:2});
window.location.search === '?page=2'; // true
Just to spell it out, the sum of these flexible and easy location features is a powerful event-based application router.
Example definition: _hide
This simply tells the dispatching code not to let an event propogate beyond the immediate target. Include a _
in the control characters at the start of an event definition to set the bubbles
property to false.
Eventi.on('test', function(e) {
console.log('Element events will never get here if they do not bubble.');
});
Eventi.fire(document.getElementById('hideme'), '_test');
Example definition: /login
This registers the listener on the global/window object but executes handler functions in the context for which they are registered. Include a /
in the control characters at the start of an event definition to assign the listener globally.
var buttons = document.querySelector('button,[type=submit]');
Eventi.on(buttons, '/ajaxStart', function(e) {
this.disabled = true;
});
Example definition: ^ready
Including ^
in an event definition's control characters identifies it as a "singleton". The simplest way to explain these is that they are the event-equivalent of jQuery's ready() function. Once a singleton event is dispatched or received, it is remembered so that registered listeners execute no more than once and listeners registered after a singleton event is dispatched will be immediately executed with the remembered event. It's a once-for-all kind of deal. Events that go through fire()
marked as singletons are automatically remembered for every target hit. When registering a listener for a singleton event that has not yet been fired, any matching event (marked singleton or not) will be remembered on the listener's target alone, for subsequent registrations. And yes, Eventi automatically watches for DOMContentLoaded
and fires a ^ready
event on the document element.
Eventi.on({
'^ready': function() {
loadAsync('globalResource').then(function(resource) {
Eventi.fire('^resource:loaded', resource);
});
},
'^resource:loaded': function(e, resource) {
// use resource here
}
});
Example definition: login$1
Not all event listeners are meant to last forever. You may declare a listener's end when registering it by appending a $
and a condition to the event definition. The condition may be either a number (indicating the number of executions allowed), a reference to a value (either a property of the context or global/window), or a reference to a function that will return such a value. In the case of a value, you may also prefix it with !
to reverse the condition. The "end declaration" follows all features of a definition except an alias. The most common is, of course, $1
for single-use listeners.
Eventi.on(player, 'death$!player.livesLeft', player.respawn);
Eventi.on('load$1', Plugins.init);
Example definition: !location
A !
control character in the front of an event definition is only relevant when you try to off()
it. Listeners defined as "important" may only be unregistered by a fully-matching definition given to off()
, including the !
. This exists mostly to protect "internal" listeners (both Eventi's own and extensions) from being errantly unregistered.
Eventi.on('!demo', function() {
console.log('still here!');
});
Eventi.off('demo');
Eventi.fire('demo');// will output 'still here!'
Example definition: account:notify#SMS(balance)=>textme
Aliases are used by either alias()
or data-eventi
definitions to provide a simple alias for safely and conveniently referencing event definitions (particularly complicated ones). These are strongly recommended for any complex definitions that are repeated in your JavaScript. And, of course, for data-eventi
declarations, they are unavoidable.
Eventi.alias('/user:logout=>shutdown');
Eventi.on.shutdown(function() {
// look ma, no typos!
});
The following syntax options are uber-syntax for working with multiple, separate definitions. No syntax features can be shared across definitions conjoined by spaces, commas, or plus symbols.
Example definition: keyup[enter] blur
Allows you to dispatch, register, unregister, or alias multiple event definitions in the same call.
Eventi.on(editor, 'keyup[ctrl-s] blur submit', function() {
Eventi.fire('service:send local:save');
});
Example definition: validate,save
Allows you to dispatch or listen for a connected, ordered sequence of events. This is another feature adapted from trigger (read this!). The concepts are the same, only the delimiter (now ,
) and the API given to sequenced events have changed:
event.index
- The index of this Event in the sequence.
event.previousEvent
- The preceding Event instance (if any).
event.sequence
- The array of Eventi definitions in the sequence.
event.pauseSequence([promise])
- Pauses the firing of the sequence. If a Promise (or other "then-able" object) is given as argument, it will automatically resume upon successful resolution of the promise. This makes async event sequences very straightforward!
event.isSequencePaused()
- Returns a Boolean indicating if the sequence has been paused.
event.resumeSequence([index])
- Resumes a paused sequence at either the specified index or the next index in the sequence.
Eventi.on('validate', function(e) {
var promise = asyncValidate($(this).closest('form'));
e.pauseSequence(promise);
});
<form>
...
<button click="validate,save,location@/home">Save and Quit</button>
</form>
Unlike trigger, Eventi also allows you to register listeners for event sequences as well. Such sequences can be fired as sequences (as above) or separately. You may also specify a time in milliseconds as the first data argument, in order to restrict the timeframe for sequence completion.
Eventi.on(editor, 'keyup[a],keyup[s],keyup[d],keyup[f]', function() {
console.log('Fake typing!');
}, 500);
Example definition: scroll+click
Allows you to listen for an unordered group of related events before executing the handler function. This is exactly like registering a listener for a sequence of events, except that the order in which the events are received is ignored (or irrelevant).
Eventi.on(editor, 'click+click+click', function() {
Eventi.fire(this, 'tripleclick');
}, 200);
And yes, you can mix combos and sequences. When doing so, sequences events will be sub-events of combos, not vice versa.
- 2014-02-11 v0.5.0 (alpha)
- 2014-04-03 v1.0.0 (beta)
- 2014-04-04 v1.0.1 (beta - IE fixes)
- 2014-04-09 v1.0.2 (beta - toString and location fix)
- 2014-04-17 v1.1.0 (beta - restructure artifacts, small improvements)
- 2014-04-21 v1.2.0 (beta - docs, space-delimited alias arguments, artifact changes, optional
data-
prefixes, combo fix) - 2014-04-22 v1.2.1 (beta - docs, shorthand type for [key] and @location)
- 2014-04-24 v1.3.0 (beta - server fixes, nodeunit tests, dual Eventi ctor)
- 2014-04-29 v1.3.1 (beta - jquery integration aid)
- 2014-08-15 v1.3.2 (beta - swap alias/delegate parser order)
- 2014-08-18 v1.3.3 (beta - fix click/enter handling in declare)
- 2014-08-19 v1.3.4 (beta - properly handle form elements as targets)
- 2014-10-15 v1.3.5 (beta - location event fixes)
- 2014-10-20 v1.3.6 (beta - location event fix, use sourcemap friendly main file for bower package)
- 2014-10-20 v1.3.7 (beta - add main entry to package.json)