d3
Version:
A small, free JavaScript library for manipulating documents based on data.
721 lines (626 loc) • 20.4 kB
JavaScript
/*
* Envjs event.1.3.pre03
* Pure JavaScript Browser Environment
* By John Resig <http://ejohn.org/> and the Envjs Team
* Copyright 2008-2010 John Resig, under the MIT License
*
* This file simply provides the global definitions we need to
* be able to correctly implement to core browser DOM Event interfaces.
- leaked globally -
var Event,
MouseEvent,
UIEvent,
KeyboardEvent,
MutationEvent,
DocumentEvent,
EventTarget,
EventException;
*/
var Envjs = Envjs || require('./platform/core').Envjs,
After = After || require('./platform/core').After,
Document = Document || require('./dom').Document;
/*
* Envjs event.1.3.pre03
* Pure JavaScript Browser Environment
* By John Resig <http://ejohn.org/> and the Envjs Team
* Copyright 2008-2010 John Resig, under the MIT License
*/
//CLOSURE_START
(function(){
/**
* @author john resig
*/
// Helper method for extending one object with another.
function __extend__(a,b) {
for ( var i in b ) {
if(b.hasOwnProperty(i)){
var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
if ( g || s ) {
if ( g ) { a.__defineGetter__(i, g); }
if ( s ) { a.__defineSetter__(i, s); }
} else {
a[i] = b[i];
}
}
}
return a;
}
/**
* @author john resig
*/
//from jQuery
function __setArray__( target, array ) {
// Resetting the length to 0, then using the native Array push
// is a super-fast way to populate an object with array-like properties
target.length = 0;
Array.prototype.push.apply( target, array );
}
var __addEventListener__,
__removeEventListener__,
__dispatchEvent__,
__captureEvent__,
__bubbleEvent__;
(function(){
var log = Envjs.logger();
Envjs.once('tick', function(){
log = Envjs.logger('Envjs.DOM.EventTarget').debug('available');
});
/**
* @name EventTarget
* @w3c:domlevel 2
* @uri -//TODO: paste dom event level 2 w3c spc uri here
*/
exports.EventTarget = EventTarget = function(){};
EventTarget.prototype.addEventListener = function(type, fn, phase){
__addEventListener__(this, type, fn, phase);
};
EventTarget.prototype.removeEventListener = function(type, fn, phase){
__removeEventListener__(this, type, fn, phase);
};
EventTarget.prototype.dispatchEvent = function(event, bubbles){
__dispatchEvent__(this, event, bubbles);
};
__extend__(Node.prototype, EventTarget.prototype);
var $events = [{}];
__addEventListener__ = function(target, type, fn, phase){
phase = !!phase?"CAPTURING":"BUBBLING";
if ( !target.uuid ) {
target.uuid = $events.length+'';
log.debug('add event uuid for %s %s', target, target.uuid);
}
if ( !$events[target.uuid] ) {
log.debug('creating listener for target: %s %s', target, target.uuid);
$events[target.uuid] = {};
}
if ( !$events[target.uuid][type] ){
log.debug('creating listener for type: %s %s %s', target, target.uuid, type);
$events[target.uuid][type] = {
CAPTURING:[],
BUBBLING:[]
};
}
if ( $events[target.uuid][type][phase].indexOf( fn ) < 0 ){
log.debug( 'adding event listener %s %s %s %s', target, target.uuid, type, phase);
$events[target.uuid][type][phase].push( fn );
}
log.debug('registered event listeners %s', $events.length);
};
__removeEventListener__ = function(target, type, fn, phase){
phase = !!phase?"CAPTURING":"BUBBLING";
if ( !target.uuid ) {
log.debug('target has never had registered events %s', target);
return;
}
if ( !$events[target.uuid] ) {
log.debug('target has no registered events to remove %s %s', target, target.uuid);
return;
}
if(type == '*'){
//used to clean all event listeners for a given node
log.debug('cleaning all event listeners for node %s %s',target, target.uuid);
delete $events[target.uuid];
return;
}else if ( !$events[target.uuid][type] ){
log.debug('target has no registered events of type %s to remove %s %s', type, target, target.uuid);
return;
}
$events[target.uuid][type][phase] =
$events[target.uuid][type][phase].filter(function(f){
log.debug('removing event listener %s %s %s %s', target, type, phase );
return f != fn;
});
};
var __eventuuid__ = 0;
__dispatchEvent__ = function(target, event, bubbles){
if (!event.uuid) {
event.uuid = __eventuuid__++;
}
//the window scope defines the $event object, for IE(^^^) compatibility;
//$event = event;
if (bubbles === undefined || bubbles === null) {
bubbles = true;
}
if (!event.target) {
event.target = target;
}
log.debug('dispatching %s %s %s %s', event.uuid, target, event.type, bubbles);
if ( event.type && (target.nodeType || target === window )) {
__captureEvent__(target, event);
event.eventPhase = Event.AT_TARGET;
if ( target.uuid && $events[target.uuid] && $events[target.uuid][event.type] ) {
event.currentTarget = target;
log.debug('begin dispatching capturing phase %s %s', target, event.type);
$events[target.uuid][event.type].CAPTURING.forEach(function(fn){
log.debug('capturing event %s', target);
var returnValue = fn.apply(target, [event]);
if(returnValue === false){
event.stopPropagation();
}
});
log.debug('begin dispatching bubbling phase %s %s', target, event.type);
$events[target.uuid][event.type].BUBBLING.forEach(function(fn){
log.debug('bubbling event %s', target);
var returnValue = fn.apply(target, [event] );
if(returnValue === false){
event.stopPropagation();
}
});
}
if (target["on" + event.type]) {
target["on" + event.type](event);
}
if (bubbles && !event.cancelled){
__bubbleEvent__(target, event);
}
if(!event._preventDefault){
//At this point I'm guessing that just HTMLEvents are concerned
//with default behavior being executed in a browser but I could be
//wrong as usual. The goal is much more to filter at this point
//what events have no need to be handled
//console.log('triggering default behavior for %s', event.type);
if(event.type in Envjs.defaultEventBehaviors){
Envjs.defaultEventBehaviors[event.type](event);
}
}
log.debug('deleting event %s', event.uuid);
event.target = null;
event = null;
}else{
throw new EventException(EventException.UNSPECIFIED_EVENT_TYPE_ERR);
}
};
__captureEvent__ = function(target, event){
var ancestorStack = [],
parent = target.parentNode,
stopevent = function(fn){
var returnValue = fn( event );
if(returnValue === false){
event.stopPropagation();
}
};
event.eventPhase = Event.CAPTURING_PHASE;
while(parent){
if(parent.uuid && $events[parent.uuid] && $events[parent.uuid][event.type]){
ancestorStack.push(parent);
}
parent = parent.parentNode;
}
while(ancestorStack.length && !event.cancelled){
event.currentTarget = ancestorStack.pop();
if($events[event.currentTarget.uuid] && $events[event.currentTarget.uuid][event.type]){
$events[event.currentTarget.uuid][event.type].CAPTURING.forEach(stopevent);
}
}
};
__bubbleEvent__ = function(target, event){
var parent = target.parentNode,
stopevent = function(fn){
var returnValue = fn( event );
if(returnValue === false){
event.stopPropagation();
}
};
event.eventPhase = Event.BUBBLING_PHASE;
while(parent){
if(parent.uuid && $events[parent.uuid] && $events[parent.uuid][event.type] ){
event.currentTarget = parent;
$events[event.currentTarget.uuid][event.type].BUBBLING.forEach(stopevent);
}
parent = parent.parentNode;
}
};
}(/*Envjs.DOM2.EventTarget*/));
(function(){
var log = Envjs.logger();
Envjs.once('tick', function(){
log = Envjs.logger('Envjs.DOM.Event').debug('available');
});
/**
* @class Event
*/
exports.Event = Event = function(options){
// event state is kept read-only by forcing
// a new object for each event. This may not
// be appropriate in the long run and we'll
// have to decide if we simply dont adhere to
// the read-only restriction of the specification
this._bubbles = true;
this._cancelable = true;
this._cancelled = false;
this._currentTarget = null;
this._target = null;
this._eventPhase = Event.AT_TARGET;
this._timeStamp = new Date().getTime();
this._preventDefault = false;
this._stopPropogation = false;
};
__extend__(Event.prototype,{
get bubbles(){return this._bubbles;},
get cancelable(){return this._cancelable;},
get currentTarget(){return this._currentTarget;},
set currentTarget(currentTarget){ this._currentTarget = currentTarget; },
get eventPhase(){return this._eventPhase;},
set eventPhase(eventPhase){this._eventPhase = eventPhase;},
get target(){return this._target;},
set target(target){ this._target = target;},
get timeStamp(){return this._timeStamp;},
get type(){return this._type;},
initEvent: function(type, bubbles, cancelable){
this._type=type?type:'';
this._bubbles=!!bubbles;
this._cancelable=!!cancelable;
},
preventDefault: function(){
this._preventDefault = true;
},
stopPropagation: function(){
if(this._cancelable){
this._cancelled = true;
this._bubbles = false;
}
},
get cancelled(){
return this._cancelled;
},
toString: function(){
return '[object Event]';
}
});
__extend__(Event,{
CAPTURING_PHASE : 1,
AT_TARGET : 2,
BUBBLING_PHASE : 3
});
}(/*Envjs.DOM.Event*/));
(function(){
var log = Envjs.logger();
Envjs.once('tick', function(){
log = Envjs.logger('Envjs.DOM.UIEvent').debug('available');
});
/**
* @name UIEvent
* @param {Object} options
*/
exports.UIEvent = UIEvent = function(options) {
this._view = null;
this._detail = 0;
};
UIEvent.prototype = new Event();
__extend__(UIEvent.prototype,{
get view(){
return this._view;
},
get detail(){
return this._detail;
},
initUIEvent: function(type, bubbles, cancelable, windowObject, detail){
this.initEvent(type, bubbles, cancelable);
this._detail = 0;
this._view = windowObject;
}
});
}(/*Envjs.DOM.UIEvent*/));
(function(){
var log = Envjs.logger();
Envjs.once('tick', function(){
log = Envjs.logger('Envjs.DOM.MouseEvent').debug('available');
});
/**
* @name MouseEvent
* @w3c:domlevel 2
* @uri http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html
*/
exports.MouseEvent = MouseEvent = function(options) {
this._screenX= 0;
this._screenY= 0;
this._clientX= 0;
this._clientY= 0;
this._ctrlKey= false;
this._metaKey= false;
this._altKey= false;
this._button= null;
this._relatedTarget= null;
};
MouseEvent.prototype = new UIEvent();
__extend__(MouseEvent.prototype,{
get screenX(){
return this._screenX;
},
get screenY(){
return this._screenY;
},
get clientX(){
return this._clientX;
},
get clientY(){
return this._clientY;
},
get ctrlKey(){
return this._ctrlKey;
},
get altKey(){
return this._altKey;
},
get shiftKey(){
return this._shiftKey;
},
get metaKey(){
return this._metaKey;
},
get button(){
return this._button;
},
get relatedTarget(){
return this._relatedTarget;
},
initMouseEvent: function(type, bubbles, cancelable, windowObject, detail,
screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey,
metaKey, button, relatedTarget){
this.initUIEvent(type, bubbles, cancelable, windowObject, detail);
this._screenX = screenX;
this._screenY = screenY;
this._clientX = clientX;
this._clientY = clientY;
this._ctrlKey = ctrlKey;
this._altKey = altKey;
this._shiftKey = shiftKey;
this._metaKey = metaKey;
this._button = button;
this._relatedTarget = relatedTarget;
}
});
}(/*Envjs.DOM2.MouseEvent*/));
(function(){
var log = Envjs.logger();
Envjs.once('tick', function(){
log = Envjs.logger('Envjs.DOM.KeyboardEvent').
debug('KeyboardEvent available');
});
/**
* Interface KeyboardEvent (introduced in DOM Level 3)
*/
exports.KeyboardEvent = KeyboardEvent = function(options) {
this._keyIdentifier = 0;
this._keyLocation = 0;
this._ctrlKey = false;
this._metaKey = false;
this._altKey = false;
this._metaKey = false;
};
KeyboardEvent.prototype = new UIEvent();
__extend__(KeyboardEvent.prototype,{
get ctrlKey(){
return this._ctrlKey;
},
get altKey(){
return this._altKey;
},
get shiftKey(){
return this._shiftKey;
},
get metaKey(){
return this._metaKey;
},
get button(){
return this._button;
},
get relatedTarget(){
return this._relatedTarget;
},
getModifiersState: function(keyIdentifier){
},
initMouseEvent: function(type, bubbles, cancelable, windowObject,
keyIdentifier, keyLocation, modifiersList, repeat){
this.initUIEvent(type, bubbles, cancelable, windowObject, 0);
this._keyIdentifier = keyIdentifier;
this._keyLocation = keyLocation;
this._modifiersList = modifiersList;
this._repeat = repeat;
}
});
KeyboardEvent.DOM_KEY_LOCATION_STANDARD = 0;
KeyboardEvent.DOM_KEY_LOCATION_LEFT = 1;
KeyboardEvent.DOM_KEY_LOCATION_RIGHT = 2;
KeyboardEvent.DOM_KEY_LOCATION_NUMPAD = 3;
KeyboardEvent.DOM_KEY_LOCATION_MOBILE = 4;
KeyboardEvent.DOM_KEY_LOCATION_JOYSTICK = 5;
}(/*Envjs.DOM3.KeyboardEvent*/));
//We dont fire mutation events until someone has registered for them
var __supportedMutations__ = /DOMSubtreeModified|DOMNodeInserted|DOMNodeRemoved|DOMAttrModified|DOMCharacterDataModified/;
var __fireMutationEvents__ = Aspect.before({
target: EventTarget,
method: 'addEventListener'
}, function(target, type){
if(type && type.match(__supportedMutations__)){
//unweaving removes the __addEventListener__ aspect
__fireMutationEvents__.unweave();
// These two methods are enough to cover all dom 2 manipulations
Aspect.around({
target: Node,
method:"removeChild"
}, function(invocation){
var event,
node = invocation['arguments'][0];
event = node.ownerDocument.createEvent('MutationEvents');
event.initEvent('DOMNodeRemoved', true, false, node.parentNode, null, null, null, null);
node.dispatchEvent(event, false);
return invocation.proceed();
});
Aspect.around({
target: Node,
method:"appendChild"
}, function(invocation) {
var event,
node = invocation.proceed();
event = node.ownerDocument.createEvent('MutationEvents');
event.initEvent('DOMNodeInserted', true, false, node.parentNode, null, null, null, null);
node.dispatchEvent(event, false);
return node;
});
}
});
(function(){
var log = Envjs.logger();
Envjs.once('tick', function(){
log = Envjs.logger('Envjs.DOM.MutationEvent').debug('available');
});
/**
* @name MutationEvent
* @param {Object} options
*/
exports.MutationEvent = MutationEvent = function(options) {
this._cancelable = false;
this._timeStamp = 0;
};
MutationEvent.prototype = new Event();
__extend__(MutationEvent.prototype,{
get relatedNode(){
return this._relatedNode;
},
get prevValue(){
return this._prevValue;
},
get newValue(){
return this._newValue;
},
get attrName(){
return this._attrName;
},
get attrChange(){
return this._attrChange;
},
initMutationEvent: function( type, bubbles, cancelable,
relatedNode, prevValue, newValue, attrName, attrChange ){
this._relatedNode = relatedNode;
this._prevValue = prevValue;
this._newValue = newValue;
this._attrName = attrName;
this._attrChange = attrChange;
switch(type){
case "DOMSubtreeModified":
this.initEvent(type, true, false);
break;
case "DOMNodeInserted":
this.initEvent(type, true, false);
break;
case "DOMNodeRemoved":
this.initEvent(type, true, false);
break;
case "DOMNodeRemovedFromDocument":
this.initEvent(type, false, false);
break;
case "DOMNodeInsertedIntoDocument":
this.initEvent(type, false, false);
break;
case "DOMAttrModified":
this.initEvent(type, true, false);
break;
case "DOMCharacterDataModified":
this.initEvent(type, true, false);
break;
default:
this.initEvent(type, bubbles, cancelable);
}
}
});
// constants
MutationEvent.ADDITION = 0;
MutationEvent.MODIFICATION = 1;
MutationEvent.REMOVAL = 2;
}(/*Envjs.DOM.MutationEvent*/));
(function(){
var log = Envjs.logger();
Envjs.once('tick', function(){
log = Envjs.logger('Envjs.DOM.EventException').debug('available');
});
/**
* @name EventException
*/
exports.EventException = EventException = function(code) {
this.code = code;
};
EventException.UNSPECIFIED_EVENT_TYPE_ERR = 0;
}(/*Envjs.DOM2.EventException*/));
/**
*
* DOM Level 2: http://www.w3.org/TR/DOM-Level-2-Events/events.html
* DOM Level 3: http://www.w3.org/TR/DOM-Level-3-Events/
*
* interface DocumentEvent {
* Event createEvent (in DOMString eventType)
* raises (DOMException);
* };
*
* Firefox (3.6) exposes DocumentEvent
* Safari (4) does NOT.
*/
(function(){
var log = Envjs.logger();
Envjs.once('tick', function(){
log = Envjs.logger('Envjs.DOM.DocumentEvent').debug('available');
});
/**
* TODO: Not sure we need a full prototype. We not just an regular object?
*/
exports.DocumentEvent = DocumentEvent = function(){};
DocumentEvent.prototype.__EventMap__ = {
// Safari4: singular and plural forms accepted
// Firefox3.6: singular and plural forms accepted
'Event' : Event,
'Events' : Event,
'UIEvent' : UIEvent,
'UIEvents' : UIEvent,
'MouseEvent' : MouseEvent,
'MouseEvents' : MouseEvent,
'MutationEvent' : MutationEvent,
'MutationEvents' : MutationEvent,
// Safari4: accepts HTMLEvents, but not HTMLEvent
// Firefox3.6: accepts HTMLEvents, but not HTMLEvent
'HTMLEvent' : Event,
'HTMLEvents' : Event,
// Safari4: both not accepted
// Firefox3.6, only KeyEvents is accepted
'KeyEvent' : KeyboardEvent,
'KeyEvents' : KeyboardEvent,
// Safari4: both accepted
// Firefox3.6: none accepted
'KeyboardEvent' : KeyboardEvent,
'KeyboardEvents' : KeyboardEvent
};
DocumentEvent.prototype.createEvent = function(eventType) {
var Clazz = this.__EventMap__[eventType];
if (Clazz) {
return new Clazz();
}
throw(new DOMException(DOMException.NOT_SUPPORTED_ERR));
};
__extend__(Document.prototype, DocumentEvent.prototype);
}(/*Envjs.DOM.DocumentEvent*/));
/**
* @author john resig & the envjs team
* @uri http://www.envjs.com/
* @copyright 2008-2010
* @license MIT
*/
//CLOSURE_END
}());