Skip to content

Instantly share code, notes, and snippets.

@bengillies
Created March 16, 2012 00:52
Show Gist options
  • Save bengillies/2047984 to your computer and use it in GitHub Desktop.
Save bengillies/2047984 to your computer and use it in GitHub Desktop.
event-base - A simple Class Helper and a simple Event Helper.
<!DOCTYPE html>
<html>
<head>
<title>EventBase Demo</title>
</head>
<body>
<div class="foo">
<a href="">I'm a link</a>
<button>I'm a button</button>
</div>
<script type="text/javascript" src="events.js"></script>
<script type="text/javascript" src="exemplar.js"></script>
<script style="display: block; white-space: pre; font-family: courier;">
var Ev = new Emitter();
var Foo = Exemplar({
constructor: function(options) {
this.el = options && options.el;
Emitter.call(this, this.events);
},
foo: function() {
console.log('foo called with', arguments);
},
events: {
foo: 'foo',
'el.click button': 'buttonClick'
},
buttonClick: function() {
console.log('button clicked with arguments', arguments);
}
});
var Bar = Foo.extend({
constructor: function(options) {
Foo.call(this, options);
},
events: {
bar: 'bar'
},
bar: function() {
console.log('bar called with', arguments);
}
});
var foo = new Foo({ el: document.querySelector('.foo') });
foo.on('el.click a', function(ev) {
console.log('link clicked with arguments', arguments);
ev.preventDefault();
ev.stopPropagation();
});
var bar = new Bar();
Ev.on('global-event', function() {
console.log('global event called with', arguments);
});
Ev.on('call-foo', foo.foo);
foo.trigger('foo', 'arg1', 'arg2');
bar.trigger('bar', 'A man walked into a bar');
Ev.trigger('global-event', 'foo', 'bar', 'baz');
Ev.trigger('call-foo', 'foo', 'bar', 'baz');
Ev.body = document.body;
Ev.on('body.click', function() {
console.log('body clicked with arguments', arguments);
});
</script>
</body>
</html>
(function(global) {
"use strict";
var hasOwn = Object.prototype.hasOwnProperty,
define = Object.defineProperty,
slice = Array.prototype.slice,
hideFn = function(fn) { return { enumerable: false, value: fn }; };
/*
* Run the given function asynchronously. This ensures that all events are
* asynchronous, rather than a mixture of both, which can get confusing.
*/
function makeAsync(fn) {
window.setTimeout(fn, 1);
}
function Emitter(eventsObject) {
var self = (this === global) ? {} : this,
events = {};
/*
* create a function that can be used to bind to DOM elements and
* delegate to sub DOM elements. Set thisArg to self;
*/
function createEventListener(evObj, callback) {
return function(e) {
var el = e.target,
nodes = evObj.subDOM ? slice.call(evObj.el
.querySelectorAll(evObj.subDOM)) : [el];
for(;el && !~nodes.indexOf(el); el = el.parentNode || null);
if (el) callback.call(self, e);
};
}
/*
* take an event string and split it into it's component parts
*/
function parseEventString(str) {
var split = /([^\.]+)\.?(([^\s]+)?\s?(.+)?)?/.exec(str),
el = split.length > 2 ? self[split[1]] : undefined;
return {
el: el, // the sub-emitter if it exists
ev: el ? split[2] : split[1], // the whole event name
isDOM: el && el instanceof HTMLElement? true : false,
subDOM: split[4], // e.g. #foo.bar in 'el.click #foo.bar'
DOMEv: split[3] // e.g. click in 'el.click #foo.bar'
};
}
/*
* Call all functions bound to the given event with the given arguments
* delegate to a sub emitter if necessary
*/
define(self, 'trigger', hideFn(function(ev/*, ...args*/) {
var args = slice.call(arguments, 1),
evObj = parseEventString(ev);
if (evObj.el && !evObj.isDOM) {
args.unshift(evObj.ev);
evObj.el.trigger.apply(evObj.el, args);
} else if (evObj.el && evObj.isDOM) {
ev = document.createEvent('Event');
ev.initEvent(evObj.DOMEv, true, true);
evObj.el.dispatchEvent(ev);
} else if (hasOwn.call(events, ev)) {
events[ev].forEach(function(fn) {
makeAsync(function() { fn.apply(self, args); });
});
}
}));
/*
* bind a function to the event
* delegate to a sub-emitter if called
*/
define(self, 'on', hideFn(function(ev, callback) {
var evObj = parseEventString(ev), fn;
if (evObj.el && evObj.isDOM) {
fn = createEventListener(evObj, callback);
evObj.el.addEventListener(evObj.DOMEv, fn);
return fn;
} else if (evObj.el) {
evObj.el.on(ev, callback);
} else if (!hasOwn.call(events, ev)) {
events[ev] = events[ev].push ? events[ev].push(callback) :
[callback];
}
return callback;
}));
/*
* unbind + unbind from a sub-emitter if called for
*/
define(self, 'off', hideFn(function(ev, fn) {
var evObj = parseEventString(ev);
if (evObj.el && evObj.isDOM) {
evObj.el.removeEventListener(evObj.DOMEv, fn);
} else if (evObj.el) {
evObj.el.off(ev, fn);
} else if (!fn) {
delete events[evObj.ev];
} else if (hasOwn.call(events, evObj.ev)) {
events[evObj.ev] = events[evObj.ev]
.filter(function(handler) {
return fn === handler ? false : true;
});
}
}));
/*
* parse and bind everything in the given object at once
*/
function loadEventsObject(ev) {
for (var key in ev) if (hasOwn.call(ev, key)) {
self.on(key, (typeof ev[key] === 'function') ?
ev[key] : self[ev[key]]);
}
}
loadEventsObject(eventsObject);
return self;
}
global.Emitter = Emitter;
}(window));
(function(exports) {
"use strict";
/*
* Shallow copy properties in source to target
*/
function extend(target, source) {
Object.getOwnPropertyNames(source).forEach(function(key) {
Object.defineProperty(target, key,
Object.GetOwnPropertyDescriptor(source, key));
});
return target;
}
/*
* Turn proto into a function exemplar, and add a function to allow easy
* subclassing of it.
*/
function Exemplar(proto) {
// define a function for subclassing existing classes
function _extend(_proto) {
return function(obj) {
var _myClass = obj.hasOwnProperty('constructor') ?
obj.constructor : function() {
_proto.constructor.apply(this, arguments);
};
_myClass.prototype = Object.create(_proto);
extend(_myClass.prototype, obj);
// allow the subclass to be subclassed
Object.defineProperty(_myClass, 'extend', {
value: _extend(_myClass.prototype),
enumerable: false
});
return _myClass;
};
}
// use it to subclass Object
return _extend(Object.prototype)(proto);
}
exports.Exemplar = Exemplar;
exports.extend = extend;
}(window));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment