GpsGate Server JavaScript API

DOM  1.0.0

GpsGate Server JavaScript API > DOM > event.js (source view)
Search:
 
Filters
/**
 * Copyright Franson Technology AB, Sweden, 2009
 * http://gpsgate.com, http://franson.com
 *
 * author Fredrik Blomqvist
 *
 * <p>
 * Event/Signal handling helpers. <br />
 * Built ontop of <a href="http://mochikit.com/doc/html/MochiKit/Signal.html">MochiKit.Signal</a>, thus the usage is similar and all handlers compatible.
 * </p>
 *
 * @module Event
 *
 * @requires MochiKit.Base, MochiKit.Signal, Franson.DOM
 *
 * todo: hmm, should this have been named .Signal instead?
 *
 */

var Franson = Franson || {};
/**
 * namespace
 * @class Franson.Event
 * @static
 */
Franson.Event = Franson.Event || {};


/**
 * Useful when creating wrappers/adapters.<br />
 * todo: add support for some kind of parameter control? at least a pass-no-params flag?
 * @method forwardEvent
 * @param {object} from Source object
 * @param {string} srcEventName
 * @param {object} to Destination object
 * @param {string} [dstEventName=srcEventName]
 * @return {EventHandler}
 */
Franson.Event.forwardEvent = function(from, srcEventName, to, dstEventName) // or just 'forward'?
{
	dstEventName = dstEventName || srcEventName;

	return MochiKit.Signal.connect(from, srcEventName, function(/*arguments*/)
	{
		var params = MochiKit.Base.extend([to, dstEventName], arguments);
		MochiKit.Signal.signal.apply(MochiKit.Signal, params);
	});
};


/**
 * connect single handler to multiple events.<br />
 * Useful?
 * @private (for now)
 * @method listenToMultiple
 * @param {object|DOM|string} src
 * @param {string[]} eventNames
 * @param {object|function} objOrFunc
 * @param {function|string} [funcOrStr]
 * @return {EventHandler[]}
 */
Franson.Event.listenToMultiple = function(src, eventNames, objOrFunc, funcOrStr)
{
	return MochiKit.Base.map(
		function(eventName)
		{
			return MochiKit.Signal.connect(src, eventName, objOrFunc, funcOrStr);
		},
		eventNames
	);
};

/**
 * disconnects automatically when predicate returns true.<br />
 * todo: support context obj
 * @method listenUntil
 * @param {object|DOM|string} source
 * @param {string} eventName
 * @param {function} predicateHandler callback handler, when returing true disconnects
 * @return {Eventhandler} only needed in case termination before the event is necessary, typically ignored (the purpose of this function..).
 */
Franson.Event.listenUntil = function(source, eventName, predicateHandler)
{
	var eh = MochiKit.Signal.connect(source, eventName, function(/*arguments*/)
	{
		var ret = predicateHandler.apply(this, arguments);  // todo: should we guard this in try-finally to make sure event is disconnected even if exception is thrown?
		if (ret)
			MochiKit.Signal.disconnect(eh);
	});
	return eh; // just to give opportunity to disconnect before event is fired. typically ignored
};


/**
 * execute the handler (incl cleanup) just once.<br />
 * todo: should support context obj
 * @method listenOnce
 * @param {object|DOM|string} source
 * @param {string} eventName
 * @param {function} handler
 * @return {Eventhandler} only needed in case termination before the event is necessary, typically ignored.
 */
Franson.Event.listenOnce = function(source, eventName, handler)
{
	return Franson.Event.listenUntil(source, eventName, function(/*arguments*/)
	{
		handler.apply(this, arguments);
		return true;
	});
};

/**
 *
 * todo: could create several versions/overloads.<br />
 * <code>findEventHandler(source)</code> => all handlers connected.<br />
 * <code>findEventHandler(source, eventName)</code> => all handlers listening to eventName.<br />
 * <code>findEventHandler(source, handler)</code> => all events the handler is attached via.<br />
 * <code>findEventHandler(eventName)</code> => all events hook to eventName regardless of obj.
 *
 * @method findEventHandler
 * @param {object|DOM|string} source
 * @param {string} eventName
 * @param {object|function} objOrFunc
 * @param {function|string} [funcOrStr]
 * @return {EventHandler}
 */
Franson.Event.findEventHandler = function(source, eventName, objOrFunc, funcOrStr)
{
	var src = MochiKit.DOM.getElement(source);

	var observers = MochiKit.Signal._observers;
	for (var i = 0; i < observers.length; ++i)
	{
		var o = observers[i];

		if (o.source == src && o.signal == eventName && o.objOrFunc === objOrFunc && o.funcOrStr === funcOrStr)
			return o;
	}
	return null;
};


/**
 * extracts all listeners attached to source.
 * ok name?
 * @method findAllEventHandlers
 * @param {object|DOM|string} source
 * @return {EventHandler[]}
 */
Franson.Event.findAllEventHandlers = function(source)
{
	var src = MochiKit.DOM.getElement(source);

	return MochiKit.Base.filter(
		function(observer)
		{
			return observer.source == src;
		},
		MochiKit.Signal._observers
	);
};


/**
 * Note: observe that this will <i>not</i> work if called with a closure as parameter (closures are by definition unique on each instantiation)
 * @method isConnected
 * @param {object|DOM|string} source
 * @param {string} eventName
 * @param {object|function} objOrFunc
 * @param {function|string} [funcOrStr]
 * @return {boolean}
 */
Franson.Event.isConnected = function(source, eventName, objOrFunc, funcOrStr)
{
	return Franson.Event.findEventHandler(source, eventName, objOrFunc, funcOrStr) !== null;
};


/**
 * see <a href="#method_isConnected">isConnected()</a>. same arguments and same condition applies.
 * @method connectOnce
 * @param {object|DOM|string} source
 * @param {string} eventName
 * @param {object|function} objOrFunc
 * @param {function|string} [funcOrStr]
 * @return {EventHandler} null if already connected
 */
Franson.Event.connectOnce = function(source, eventName, objOrFunc, funcOrStr)
{
	if (!Franson.Event.isConnected(source, eventName, objOrFunc, funcOrStr))
		return MochiKit.Signal.connect(source, eventName, objOrFunc, funcOrStr);
	return null;
};


/**
 * to be used to hook on events with a unique function (connectOnce compatible) for example.<br />
 * todo: could add some more Event methods (and also generalize this)<br />
 * function version of <a href="http://mochikit.com/doc/html/MochiKit/Signal.html#fn-preventdefault">Event.preventDefault()</a>
 * @method preventDefault
 * @param {MochiKit.Signal.Event} e (Not a native event)
 */
Franson.Event.preventDefault = function(e)	// = MochiKit.Base.methodcaller('preventDefault');
{
	e.preventDefault();
};

/**
 * function version of <a href="http://mochikit.com/doc/html/MochiKit/Signal.html#fn-stoppropagation">Event.stopPropagation()</a>
 * @method stopPropagation
 * @param {MochiKit.Signal.Event} e (Not a native event)
 */
Franson.Event.stopPropagation = function(e)
{
	e.stopPropagation();
};

/**
 * function version of <a href="http://mochikit.com/doc/html/MochiKit/Signal.html#fn-stop">Event.stop()</a>
 * @method stop
 * @param {MochiKit.Signal.Event} e (Not a native event)
 */
Franson.Event.stop = function(e)
{
	e.stop();
};


/**
 * removes all signals connected to the elem tree.<br />
 * todo: optionally control type of signal to remove? (supply purge fn and/or event-name(s))
 * @method purgeDomNode
 * @param {DOMNode|string} elem
 */
Franson.Event.purgeDomNode = function(elem)
{
	MochiKit.Iter.forEach(
		Franson.Iter.treePreOrderIter( // could use any traversal order (todo: use the new Franson.DOM.preOrderIter etc wrappers)
			MochiKit.DOM.getElement(elem),
			MochiKit.Base.itemgetter('childNodes')
		),
		MochiKit.Signal.disconnectAll
	);
};


/**
 * @private (for now)
 * @method listenOnMany
 * @param {Array[object|DOM|string]} sources
 * @param {string} eventName
 * @param {object|function} objOrFunc
 * @param {function|string} [funcOrStr]
 * @return {EventHandler[]}
 */
Franson.Event.listenOnMany = function(sources, eventName, objOrFunc, funcOrStr)
{
	return MochiKit.Base.map(
		function(src)
		{
			return MochiKit.Signal.connect(src, eventName, objOrFunc, funcOrStr);
		},
		sources
	);
};

/**
 * test
 * todo: same for checkbox buttons
 * todo: currently hardwired to use onclick. expose 'onfocus' etc?
 * todo: hmm, would be convenient if MochiKit.Signal.disconnect would recognize array-like params and then disconnect each?
 * @private (for now)
 * @method listenToRadioGroup
 * @param {string} groupName
 * @param {function(Event)} handler
 * @return {EventHandler[]}
 */
Franson.Event.listenToRadioGroup = function(groupName, objOrFunc, funcOrStr)
{
	return Franson.Event.listenOnMany(
		MochiKit.Base.filter(
			function(button) { return button.type == 'radio'; },
			document.getElementsByName(groupName)
		),
		'onclick',
		objOrFunc,
		funcOrStr
	);
};


/**
 * Meta-event that triggers when a user clicks <i>outside</i> an object (used in the popup-controls for example)<br />
 * note: doesn't take overlapping elems into account, only geometry (and visibility)<br />
 * todo: hmm, could be implemented as enableOnOutSideClickEvent(elem) also that fires a MochiKit.Signal.signal(source, 'onoutsideclick', e).
 * and make listeners use connect(source, 'onoutsideclick', handler) hookup?
 * @method onOutsideClick
 * @param {DOM|string} source  - todo: support multiple elements?
 * @param {function} handler
 * @return {EventHandler}
 */
Franson.Event.onOutsideClick = function(source, handler)
{
	// todo: perhaps this should be put in a proper bounds-class
	function isInside(bounds, pos)
	{
		return (bounds.x <= pos.x && pos.x <= (bounds.x + bounds.w)) &&
			(bounds.y <= pos.y && pos.y <= (bounds.y + bounds.h));
	}

	return MochiKit.Signal.connect(document, 'onclick', MochiKit.DOM.getElement(source), function(e)
	{
		if (this.style.display == 'none' || this.style.visibility == 'hidden') // ok? (or use .offsetWidth <= 0?)
		{
			return;
		}

		// todo: hmm, or better to just traverse the e.target's parentNode chain and look for source?
		var pos = e.mouse().page;
		var bounds = Franson.DOM.getElementBounds(this);

		if (!isInside(bounds, pos))
		{
			handler.call(this, e);
		}
	});
};


/**
 * for debugging purposes, to be able to detect handler leaks.
 * note that it won't return 0 even at the very first call since
 * MochiKit needs a couple of events for internal use (page-unload etc)
 * @method getTotalNumberOfListeners
 * @return {integer}
 */
Franson.Event.getTotalNumberOfListeners = function()
{
	return MochiKit.Signal._observers.length; // undocumented
};


/**
 * note: this doesn't (can't) set the .src() property of e.
 * @method normalize
 * @param {event} e browser-native event (if passed a Mochi.Event it is returned as-is)
 * @return {MochiKit.Signal.Event}
 */
Franson.Event.normalize = function(e)
{
	return (e instanceof MochiKit.Signal.Event) ? e : new MochiKit.Signal.Event(null, e);
};


/**
 * makes sure this connection to source is the only one listening to eventName
 * useful? (name?)
 * see connectOnce
 * @method singleConnect
 * @private (for now)
 * @return {EventHandler}
 */
Franson.Event.singleConnect = function(source, eventName, objOrFunc, funcOrStr)
{
	MochiKit.Signal.disconnectAll(source, eventName);
	return MochiKit.Signal.connect(source, eventName, objOrFunc, funcOrStr);
};

/**
 * convenience function that accepts array as input also (and multiple arguments).<br />
 * see <a href="http://mochikit.com/doc/html/MochiKit/Signal.html#fn-disconnect">MochiKit.Signal.disconnect()</a>
 * todo: accept (silently) null args also?
 * @private (for now)
 * @method disconnect
 * @param {EventHandler|EventHandler[]|..} handlers
 */
Franson.Event.disconnect = function(handlers)
{
	MochiKit.Iter.forEach(arguments, function(handler)
	{
		if (MochiKit.Base.isArrayLike(handler))
			MochiKit.Iter.forEach(handler, MochiKit.Signal.disconnect);
		else
			MochiKit.Signal.disconnect(handler);
	});
};


/**
 * hidden dummy object to create a broadcast system
 * see publish, subscribe
 * @private
 */
Franson.Event._topics = {};

/**
 * broadcasts a signal to all listeners to the topic attached via <a href="#method_subscribe">subscribe()</a> <br />
 * ref <a href="http://docs.dojocampus.org/dojo/publish">dojo.publish()</a> (difference is that this allows multiple arguments)
 * @method publish
 * @param {string} topic
 * @param {[object, ...]} [args] optional multiple arguments passed to the listeners
 */
Franson.Event.publish = function(topic, args)
{
	MochiKit.Signal.signal.apply(MochiKit.Signal/*or null?*/, MochiKit.Base.extend([Franson.Event._topics, topic], arguments, 1));
};

/**
 * same syntax as <a href="http://mochikit.com/doc/html/MochiKit/Signal.html#fn-connect">MochiKit.Signal.connect()</a> but with source (first param) already bound.<br />
 * see also <a href="#method_publish">publish()</a><br />
 * ref <a href="http://docs.dojocampus.org/dojo/subscribe">dojo.subscribe()</a>
 * @method subscribe
 * @param {string} topic
 * @param {object|function} objOrFunc
 * @param {function|string} [funcOrStr]
 * @return {EventHandler}
 */
Franson.Event.subscribe = function(topic, objOrFunc, funcOrStr)
{
	return MochiKit.Signal.connect(Franson.Event._topics, topic, objOrFunc, funcOrStr);
};

/**
 * disconnects a signal attached with <a href="#method_subscribe">subscribe()</a> <br />
 * same as disconnect (alias), mostly for symmetry (same as dojo) (or skip?)
 * ref <a href="http://docs.dojocampus.org/dojo/unsubscribe">dojo.unsubscribe()</a>
 * @method unsubscribe
 * @param {EventHandler} handle
 */
Franson.Event.unsubscribe = function(handle)
{
	MochiKit.Signal.disconnect(handle);
};

Copyright © 2009 Franson Technology AB, Sweden. All rights reserved.