/**
* @requires javelin-install
* @provides javelin-event
* @javelin
*/
/**
* A generic event, routed by @{class:JX.Stratcom}. All events within Javelin
* are represented by a {@class:JX.Event}, regardless of whether they originate
* from a native DOM event (like a mouse click) or are custom application
* events.
*
* See @{article:Concepts: Event Delegation} for an introduction to Javelin's
* event delegation model.
*
* Events have a propagation model similar to native Javascript events, in that
* they can be stopped with stop() (which stops them from continuing to
* propagate to other handlers) or prevented with prevent() (which prevents them
* from taking their default action, like following a link). You can do both at
* once with kill().
*
* @task stop Stopping Event Behaviors
* @task info Getting Event Information
*/
JX.install('Event', {
members : {
/**
* Stop an event from continuing to propagate. No other handler will
* receive this event, but its default behavior will still occur. See
* ""Using Events"" for more information on the distinction between
* 'stopping' and 'preventing' an event. See also prevent() (which prevents
* an event but does not stop it) and kill() (which stops and prevents an
* event).
*
* @return this
* @task stop
*/
stop : function() {
var r = this.getRawEvent();
if (r) {
r.cancelBubble = true;
r.stopPropagation && r.stopPropagation();
}
this.setStopped(true);
return this;
},
/**
* Prevent an event's default action. This depends on the event type, but
* the common default actions are following links, submitting forms,
* and typing text. Event prevention is generally used when you have a link
* or form which work properly without Javascript but have a specialized
* Javascript behavior. When you intercept the event and make the behavior
* occur, you prevent it to keep the browser from following the link.
*
* Preventing an event does not stop it from propagating, so other handlers
* will still receive it. See ""Using Events"" for more information on the
* distinction between 'stopping' and 'preventing' an event. See also
* stop() (which stops an event but does not prevent it) and kill()
* (which stops and prevents an event).
*
* @return this
* @task stop
*/
prevent : function() {
var r = this.getRawEvent();
if (r) {
r.returnValue = false;
r.preventDefault && r.preventDefault();
}
this.setPrevented(true);
return this;
},
/**
* Stop and prevent an event, which stops it from propagating and prevents
* its defualt behavior. This is a convenience function, see stop() and
* prevent() for information on what it means to stop or prevent an event.
*
* @return this
* @task stop
*/
kill : function() {
this.prevent();
this.stop();
return this;
},
/**
* Get the special key (like tab or return), if any, associated with this
* event. Browsers report special keys differently; this method allows you
* to identify a keypress in a browser-agnostic way. Note that this detects
* only some special keys: delete, tab, return escape, left, up, right,
* down.
*
* For example, if you want to react to the escape key being pressed, you
* could install a listener like this:
*
* JX.Stratcom.listen('keydown', 'example', function(e) {
* if (e.getSpecialKey() == 'esc') {
* JX.log("You pressed 'Escape'! Well done! Bravo!");
* }
* });
*
* @return string|null ##null## if there is no associated special key,
* or one of the strings 'delete', 'tab', 'return',
* 'esc', 'left', 'up', 'right', or 'down'.
* @task info
*/
getSpecialKey : function() {
var r = this.getRawEvent();
if (!r) {
return null;
}
return JX.Event._keymap[r.keyCode] || null;
},
/**
* Get whether the mouse button associated with the mouse event is the
* right-side button in a browser-agnostic way.
*
* @return bool
* @task info
*/
isRightButton : function() {
var r = this.getRawEvent();
return r.which == 3 || r.button == 2;
},
/**
* Get whether the mouse button associated with the mouse event is the
* left-side button in a browser-agnostic way.
*
* @return bool
* @task info
*/
isLeftButton: function() {
var r = this.getRawEvent();
return (r.which == 1 || r.button == 0);
},
/**
* Determine if a mouse event is a normal event (left mouse button, no
* modifier keys).
*
* @return bool
* @task info
*/
isNormalMouseEvent : function() {
var supportedEvents = {'click': 1, 'mouseup': 1, 'mousedown': 1};
if (!(this.getType() in supportedEvents)) {
return false;
}
var r = this.getRawEvent();
if (r.metaKey || r.altKey || r.ctrlKey || r.shiftKey) {
return false;
}
if (('which' in r) && (r.which != 1)) {
return false;
}
if (('button' in r) && r.button) {
if ('which' in r) {
return false;
// IE won't have which and has left click == 1 here
} else if (r.button != 1) {
return false;
}
}
return true;
},
/**
* Determine if a click event is a normal click (left mouse button, no
* modifier keys).
*
* @return bool
* @task info
*/
isNormalClick : function() {
if (this.getType() != 'click') {
return false;
}
return this.isNormalMouseEvent();
},
/**
* Get the node corresponding to the specified key in this event's node map.
* This is a simple helper method that makes the API for accessing nodes
* less ugly.
*
* JX.Stratcom.listen('click', 'tag:a', function(e) {
* var a = e.getNode('tag:a');
* // do something with the link that was clicked
* });
*
* @param string sigil or stratcom node key
* @return node|null Node mapped to the specified key, or null if it the
* key does not exist. The available keys include:
* - 'tag:'+tag - first node of each type
* - 'id:'+id - all nodes with an id
* - sigil - first node of each sigil
* @task info
*/
getNode : function(key) {
return this.getNodes()[key] || null;
},
/**
* Get the metadata associated with the node that corresponds to the key
* in this event's node map. This is a simple helper method that makes
* the API for accessing metadata associated with specific nodes less ugly.
*
* JX.Stratcom.listen('click', 'tag:a', function(event) {
* var anchorData = event.getNodeData('tag:a');
* // do something with the metadata of the link that was clicked
* });
*
* @param string sigil or stratcom node key
* @return dict dictionary of the node's metadata
* @task info
*/
getNodeData : function(key) {
// Evade static analysis - JX.Stratcom
return JX['Stratcom'].getData(this.getNode(key));
}
},
statics : {
_keymap : {
8 : 'delete',
9 : 'tab',
// On Windows and Linux, Chrome sends '10' for return. On Mac OS X, it
// sends 13. Other browsers evidence varying degrees of diversity in their
// behavior. Treat '10' and '13' identically.
10 : 'return',
13 : 'return',
27 : 'esc',
37 : 'left',
38 : 'up',
39 : 'right',
40 : 'down',
63232 : 'up',
63233 : 'down',
62234 : 'left',
62235 : 'right'
}
},
properties : {
/**
* Native Javascript event which generated this @{class:JX.Event}. Not every
* event is generated by a native event, so there may be ##null## in
* this field.
*
* @type Event|null
* @task info
*/
rawEvent : null,
/**
* String describing the event type, like 'click' or 'mousedown'. This
* may also be an application or object event.
*
* @type string
* @task info
*/
type : null,
/**
* If available, the DOM node where this event occurred. For example, if
* this event is a click on a button, the target will be the button which
* was clicked. Application events will not have a target, so this property
* will return the value ##null##.
*
* @type DOMNode|null
* @task info
*/
target : null,
/**
* Metadata attached to nodes associated with this event.
*
* For native events, the DOM is walked from the event target to the root
* element. Each sigil which is encountered while walking up the tree is
* added to the map as a key. If the node has associated metainformation,
* it is set as the value; otherwise, the value is null.
*
* @type dict<string, *>
* @task info
*/
data : null,
/**
* Sigil path this event was activated from. TODO: explain this
*
* @type list<string>
* @task info
*/
path : [],
/**
* True if propagation of the event has been stopped. See stop().
*
* @type bool
* @task stop
*/
stopped : false,
/**
* True if default behavior of the event has been prevented. See prevent().
*
* @type bool
* @task stop
*/
prevented : false,
/**
* @task info
*/
nodes : {},
/**
* @task info
*/
nodeDistances : {},
/**
* True if this is a cursor event that was caused by a touch interaction
* rather than a mouse device interaction.
*
* @type bool
* @taks info
*/
isTouchEvent: false
},
/**
* @{class:JX.Event} installs a toString() method in ##__DEV__## which allows
* you to log or print events and get a reasonable representation of them:
*
* Event<'click', ['path', 'stuff'], [object HTMLDivElement]>
*/
initialize : function() {
if (__DEV__) {
JX.Event.prototype.toString = function() {
var path = '['+this.getPath().join(', ')+']';
var type = this.getType();
if (this.getIsTouchEvent()) {
type = type + '/touch';
}
return 'Event<'+type+', '+path+', '+this.getTarget()+'>';
};
}
}
});