event.js

Summary

DOM Event related methods.

Project Homepage: http://mozile.mozdev.org

Version: 0.8 $Id: overview-summary-event.js.html,v 1.9 2006/08/23 23:30:17 jameso Exp $

Author: James A. Overton


/* ***** BEGIN LICENSE BLOCK *****
 * Licensed under Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * Full Terms at http://mozile.mozdev.org/0.8/LICENSE
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is James A. Overton's code (james@overton.ca).
 *
 * The Initial Developer of the Original Code is James A. Overton.
 * Portions created by the Initial Developer are Copyright (C) 2005-2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *	James A. Overton <james@overton.ca>
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * @fileoverview DOM Event related methods.
 * <p>Project Homepage: http://mozile.mozdev.org 
 * @author James A. Overton <james@overton.ca>
 * @version 0.8
 * $Id: overview-summary-event.js.html,v 1.9 2006/08/23 23:30:17 jameso Exp $
 */


mozile.require("mozile.dom");
mozile.require("mozile.edit");
mozile.provide("mozile.event.*");

/**
 * Tools for dealing with browser events.
 * @type Object
 */
mozile.event = new Object();
// JSDoc hack
mozile.event.prototype = new mozile.Module;


/**
 * Indicates that a mouse button is being held down. 
 * @type Boolean
 */
mozile.event.mousedown = false;


/**
 * Changes some of IE's event properties to use standard names.
 * @param {Event} e The event to normalize.
 * @type Object
 */
mozile.event.normalize = function(event) {
	if(!event) return event;
	if(typeof(event.target) == "undefined") event.target = event.srcElement;
	if(typeof(event.charCode) == "undefined") event.charCode = event.keyCode;
	return event;
}

/**
 * Adds an event listener to a node.
 * @param {Document} doc The document to attach the listener to.
 * @param {String} type The event type to listen for. Do NOT include the "on" prefix!
 * @param {Function} listener Optional. The function to call when the event occurs. Defailts to mozile.event.handle.
 * @param {Boolean} useCapture Optional. See the documentation for Document.addEventListener().
 * @type Void
 */
mozile.event.addListener = function(doc, type, listener, useCapture) {
	if(!listener) listener = mozile.event.handle;
	if(doc.addEventListener) doc.addEventListener(type, listener, useCapture);
	else if(doc.attachEvent) doc.attachEvent("on" + type, listener);
	else mozile.debug.inform("mozile.event.addListener", "No known event method available");
}

/**
 * Set up a series of event listeners on this document.
 * @type Void
 */
mozile.event.listen = function() {
	var events = ["mousedown", "mousemove", "mouseup", "click", "dblclick", "keydown", "keyup", "keypress"];
	for(var i=0; i < events.length; i++) {
		mozile.event.addListener(document, events[i]);
	}
}

/**
 * INCOMPLETE. Dispatch (fire) an event of a given type from the given element.
 * @param {Element} element The element fom which the event will be dispatched.
 * @param {String} type The type of event to dispatch.
 * @type Void
 */
mozile.event.dispatch = function(element, type, keyCode, charCode, ctrlKey, altKey, shiftKey, metaKey) {
	// IE event
	if(element.fireEvent) element.fireEvent("on"+type);
	// TODO: Other browsers
	else if(element.dispatchEvent) {
		var event;
		if(type.indexOf("click") > -1 || type.indexOf("mouse") > -1) {
			event = document.createEvent("MouseEvent");
			event.initMouseEvent(type, true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 1, null);
		}
		else if(type.indexOf("key") > -1) {
			if(!ctrlKey) ctrlKey = false;
			if(!altKey) altKey = false;
			if(!shiftKey) shiftKey = false;
			if(!metaKey) metaKey = false;
			event = document.createEvent("KeyEvent");
			event.initKeyEvent(type, true, true, document.defaultView, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode);
		}
		else {
			event = document.createEvent("Events");
			event.initEvent(type, true, true);
		}
		element.dispatchEvent(event);
	}
}

/**
 * Cancels an event.
 * @param {Event} event The event to cancel.
 * @type Void
 */
mozile.event.cancel = function(event) {
	if(!event) return;
	if(event.stopPropagation) event.stopPropagation();
	event.cancelBubble = true;
	if(event.preventDefault) event.preventDefault();
}

/**
 * General event handler, which calls specific event handlers for the document and for the target nodes of the event.
 * @param {Event} event The event to handle.
 * @type Boolean
 * @return False if the event was cancelled, true if it was not cancelled.
 */
mozile.event.handle = function(event) {
try {
	switch(event.type) {
		case "mousemove":
			return mozile.event.handleMouseMove(event);
		case "mouseup":
		case "click":
		case "dblclick":
			mozile.event.mousedown = false;
	}

	event = mozile.event.normalize(event);
	mozile.event.findTarget(event);
	if(!event.node) return true;
	event.container = mozile.edit.getContainer(event.node);
	event.editable = Boolean(event.container);

	// Check global commands
	var state = mozile.edit.handleEvent(event);
	if(state) return mozile.event.handled(event, state);
	
	// Set editable state
	if(mozile.gui) mozile.gui.update(event);
	if(!mozile.edit.editable) return true;
	var priorStatus = mozile.edit.status;
	mozile.edit.setStatus(event.editable);
	if(!event.editable) return true;
	
	mozile.event.storeSelection(event);
	if(!priorStatus) mozile.event.fixFocus(event);

	// Check RNG Element commands for this node and editable ancestors.
	var node = event.node;
	while(event.node) {
		if(!event.rng) event.rng = mozile.edit.lookupRNG(event.node);
		if(event.rng) {
			state = event.rng.handleEvent(event);
			if(state) return mozile.event.handled(event, state);
		}
		if(event.node == event.container) break;
		else event.node = event.node.parentNode;
	}
	
	// Check default editing commands.
	event.node = node;
	state = mozile.edit.handleDefault(event);
	if(state) return mozile.event.handled(event, state);
	
	if(!mozile.event.cancelKeyEvent(event)) return false;
	if(!mozile.event.cancelHyperlink(event)) return false;

} catch(e) { mozile.debug.inform("mozile.event.handle", mozile.dumpError(e)); }

	return true;
}

/**
 * Finish handling an event, eieht by cancelling it or not.
 * @param {Event} event The event to handle.
 * @type Boolean
 */
mozile.event.handled = function(event, state) {
	mozile.edit.done(state);
	if(state.changesMade) {
		//mozile.debug.debug("mozile.event.handled", state.command.name +" made change "+ state.changesMade);
		if(mozile.gui) mozile.gui.update(event, state.changesMade);
	}
	if(state.cancel) {
		mozile.event.cancel(event);
		return false;
	}
	else return true;
}

/**
 * Handles mouse move events. Part of a hack for handling selections inside links in Mozilla. See cancelHyperlink() for the rest.
 * @param {Event} event The event to handle.
 * @type Boolean
 */
mozile.event.handleMouseMove = function(event) {
	if(!mozile.browser.isMozilla) return true;
	if(!mozile.event.mousedown) return true;
	var selection = mozile.dom.selection.get();
	if(!selection) return true;

	if(selection.focusNode != event.rangeParent ||
		selection.focusOffset != event.rangeOffset) {
		selection.extend(event.rangeParent, event.rangeOffset);
	}

	return true;
}

/**
 * Finds the target of an event. For a mouse event this will be the event.target. For a keyboard event we look at the selection's commonAncestorContainer.
 * @param {Event} event The event to handle.
 * @type Void
 */
mozile.event.findTarget = function(event) {
	event.selection = mozile.dom.selection.get();
	//if(event.type.indexOf("key") == 0) {
		if(!event.selection || event.selection.rangeCount < 1) return;
		event.range = event.selection.getRangeAt(0);
		if(!event.range) return;
		event.node = event.range.commonAncestorContainer;
		//mozile.debug.debug("mozile.event.findTarget", [event.type, event.charCode, mozile.xpath.getXPath(event.node)].join(", "));
	//}
	//else event.node = event.target;
}


/**
 * Store the current selection so it can be restored. Used to maintain a selection in the text while the Mozile GUI is being used.
 * @param {Event} event The event to handle.
 * @type Void
 */
mozile.event.storeSelection = function(event) {
	mozile.dom.selection.last = {
		anchorNode: event.selection.anchorNode,
		anchorOffset: event.selection.anchorOffset, 
		focusNode: event.selection.focusNode, 
		focusOffset: event.selection.focusOffset,
		isCollapsed: event.selection.isCollapsed
	};
}

/**
 * Makes sure the editable area is focussed. This is a hack to fix a bug when activiating Mozila's designMode. The method is to duplicate the mousedown event and send it again, esentailly clicking twice.
 * @param {Event} event The event to handle.
 * @type Void
 */
mozile.event.fixFocus = function(event) {
	if(!mozile.browser.isMozilla) return;
	if(!mozile.useDesignMode) return;
	if(event.type != "mousedown") return;
	
	var newEvent = document.createEvent("MouseEvent");
	newEvent.initMouseEvent("mousedown", true, true, event.view, 1, event.screenX, event.screenY, event.clientX, event.clientY, false, false, false, false, 1, event.relatedTarget);
	event.target.dispatchEvent(newEvent);
}

/**
 * Cancels certain keyboard events and cancels them.
 * @param {Event} event The event to handle.
 * @type Boolean
 * @return false means the event has been cancelled.
 */
mozile.event.cancelKeyEvent = function(event) {
	if(!event || !event.keyCode) return true;
	switch(event.keyCode) {
		case 8: // Backspace
		case 9: // Tab
		case 46: // Delete
			mozile.event.cancel(event);
			return false;
	}
	return true;
}

/**
 * Cancels events which would lead Mozilla to follow a hyperlink while editing.
 * @param {Event} event The event to handle.
 * @type Boolean
 * @return false means the event has been cancelled.
 */
mozile.event.cancelHyperlink = function(event) {
	if(!mozile.browser.isMozilla) return true;
	if(mozile.useDesignMode) return true;

	// Only handle certain mouse events.
	switch(event.type) {
		case "mousedown":
		case "click":
		case "dblclick":
		case "mouseup":
			break;
		default: return true;
	}

	var node = event.explicitOriginalTarget;
	var container = mozile.edit.getContainer(node);
	if(container) {
		while(node) {
			if(node.localName && node.localName.toLowerCase() == "a") {
				//mozile.require("mozile.util");
				//mozile.debug.debug("", mozile.util.dumpValues(event));
				if(event.selection && event.rangeParent && 
					event.rangeOffset != undefined) {
					if(event.type == "mousedown") {
						event.selection.collapse(event.rangeParent, event.rangeOffset);
						mozile.event.mousedown = true;
					}
					else mozile.event.mousedown = false;
					if(event.type == "dblclick") {
						mozile.event.selectWord(event.rangeParent, event.rangeOffset);
					}
				}
				mozile.event.cancel(event);
				return false;
			}
			if(node == container) break;
			node = node.parentNode;
		}
	}

	return true;
}

/**
 * Selects the word inside the given node which contains the given offset. Tries to immitate Mozilla's double-click behaviour.
 * @param {Node} node
 * @param {Integer} offset
 * @type Void
 */
mozile.event.selectWord = function(node, offset) {
	if(!node || offset == undefined) return;

	var selection = mozile.dom.selection.get();
	if(node.nodeType != mozile.dom.TEXT_NODE) {
		selection.collapse(node, offset);
	}

	else {
		//mozile.debug.debug("mozile.event.selectWord", mozile.xpath.getXPath(node) +" "+ offset);
		var match = /\s/;
		var data = node.data;

		// Stupid bug: the event.rangeParent is always the first text node
		// Ignore that case
		if(offset == data.length) return;
		
		var startOffset = offset - 2;
		while(startOffset >= 0) {
			if(data.charAt(startOffset).match(match)) {
				startOffset++;
				break;
			}
			else startOffset--;
		}
		if(startOffset < 0) startOffset = 0;

		var endOffset = offset + 1;
		while(endOffset <= data.length) {
			if(data.charAt(endOffset).match(match)) break;
			else endOffset++;
		}
		if(endOffset > data.length) endOffset = data.length;

		selection.collapse(node, startOffset);
		selection.extend(node, endOffset);
	}	
	
}


/**** Final Configuration ****/

// Activate the listeners.
mozile.event.listen();






Documentation generated by JSDoc on Wed Aug 23 18:45:51 2006