/*
License: GNU Lesser General Public License (http://www.gnu.org/licenses/lgpl.html)
Copyright (C) 2005 tagnetic.org. Please do not remove this copyright/license comment.
*/
//******************************************************************
//******************************************************************
//******************************************************************
function Ctrl() {};
//*************************************************************************
//Start Update View section useUpdateView
//*************************************************************************
/**
* Utilities to facilitate updating the View (HTML/XHTML/DOM)
*/
//******************************************************************
Ctrl.updateView = function(viewId, htmlData, targetWindow)
{
if (!targetWindow)
targetWindow = window;
targetWindow.document.getlistenTypeById(viewId).innerHTML = htmlData;
}
//*************************************************************************
//End Update View section
//*************************************************************************
//*************************************************************************
//Start URL Ctrl methods useUrlCtrl
//*************************************************************************
/**
* Methods to help parse and build URLs with dynamic name/value pairs.
* Two approaches are supported:
* 1) Fragment Identifier (URL Hash): uses #name:value,name:value syntax.
* Won't cause page reloads as the URLs are navigated, but makes it
* harder to support back-forward.
*
* 2) HTTP GET (Querystring): uses the ?name=value&name=value syntax.
* This may cause page reloads and more hits to the server. It may make
* it easier to support back-forward.
*
*/
//******************************************************************
/**
* Defines the two types supported, and sets the default to Fragment.
* This can be overriden in your application by assigning a different
* value to Ctrl.paramType after you include this script in your page.
*
*/
Ctrl.kParamTypeFragment = 'fragment';
Ctrl.kParamTypeQuery = 'query';
Ctrl.paramType = Ctrl.kParamTypeFragment;
//******************************************************************
Ctrl.urlProps = new Object();
Ctrl.urlProps[Ctrl.kParamTypeFragment] = {
separator: '#',
paramSeparator: ',',
pairSeparator: ':'
};
Ctrl.urlProps[Ctrl.kParamTypeQuery] = {
separator: '?',
paramSeparator: '&',
pairSeparator: '='
};
//******************************************************************
/**
* This is method is probably not very efficient since it calls setUrlParam()
* for each parameter, but trying to reuse as much code as possible.
* Evaluate perfomance, then decide if it is better to optimize the function
* to do bulk adding of params on to the URL. One nice thing (or not?) about
* the approach below is that it will make sure that each param name is only
* on the URL once (the last param name/value wins).
*
*/
Ctrl.createUrlWithParams = function(url, params, paramType)
{
for (var key in params)
url = Ctrl.setUrlParam(url, key, params[key], paramType);
return url;
}
//******************************************************************
Ctrl.setUrlParam = function(url, name, value, paramType)
{
var urlProps = Ctrl._getUrlProps(paramType);
var separator = urlProps.separator;
name = encodeURIComponent(name);
value = encodeURIComponent(value);
var urlSuffix = '';
//Save off fragment identifier if dealing with query string params.
if (separator == '?')
{
var otherSeparatorIndex = url.indexOf('#');
if (otherSeparatorIndex != -1)
{
urlSuffix = url.substring(otherSeparatorIndex, url.length);
url = url.substring(0, otherSeparatorIndex - 1);
}
}
var index = url.indexOf(separator);
switch(index)
{
case -1: //no separator yet.
url += separator;
break;
case url.length - 1: // separator, but no name/values yet.
break;
default: //separator and at least one name/value pair.
var matches = url.match(name + urlProps.pairSeparator + '.*($|' + urlProps.paramSeparator + ')');
if (matches)
{
//The name is already in the list, remove it.
urlSuffix = url.substring(matches.index + matches[0].length, url.length);
url = url.substring(0, matches.index - 1);
}
else
url += urlProps.paramSeparator;
}
url += name + urlProps.pairSeparator + value;
url += urlSuffix;
return url;
}
//******************************************************************
Ctrl.getAllUrlParams = function(url, paramType)
{
var result = null;
var urlProps = Ctrl._getUrlProps(paramType);
var separator = urlProps.separator;
var i = -1;
//Save off fragment identifier if dealing with query string params.
if (separator == '?')
{
i = url.indexOf('#');
if (i != -1)
url = url.substring(0, i - 1);
}
i = url.indexOf(separator);
if (i != -1 && i < url.length - 1)
{
url = url.substring(i + 1, url.length);
var nvPairs = url.split(urlProps.paramSeparator);
if (nvPairs)
{
result = new Object();
var pair;
for (i = 0; i < nvPairs.length; i++)
{
pair = nvPairs[i].split(urlProps.pairSeparator);
result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
}
}
}
return result;
}
//******************************************************************
Ctrl.getUrlParam = function(url, name, paramType)
{
var result = null;
var urlProps = Ctrl._getUrlProps(paramType);
var separator = urlProps.separator;
var i = -1;
//Save off fragment identifier if dealing with query string params.
if (separator == '?')
{
i = url.indexOf('#');
if (i != -1)
url = url.substring(0, i - 1);
}
i = url.indexOf(separator);
if (i != -1 && i < url.length - 1)
{
url = url.substring(i + 1, url.length);
var nvExp = name + urlProps.pairSeparator + '(.*)($|' + urlProps.paramSeparator + ')';
var matches = url.match(nvExp);
if (matches && matches.length > 1)
result = decodeURIComponent(matches[1]);
}
return result;
}
//******************************************************************
Ctrl._getUrlProps = function(paramType)
{
if (!paramType)
paramType = Ctrl.paramType;
var urlProps = Ctrl.urlProps[paramType];
if (!urlProps)
throw 'Invalid parameter type: ' + paramType + '. Valid values are '
+ Ctrl.kParamTypeFragment + ' or ' + Ctrl.kParamTypeQuery;
return urlProps;
}
//*************************************************************************
//End URL Ctrl methods
//*************************************************************************
//*************************************************************************
//Start event listening methods useEventListen
//*************************************************************************
/**
*
* Motivation:
* There are some good resources on this issue. The following were consulted
* for the Ctrl library:
*
* - http://www.quirksmode.org/js/introevents.html
* - http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
* (including comments)
* - http://dean.edwards.name/weblog/2005/10/add-event2/
* - http://www.dustindiaz.com/rock-solid-addevent/
*
* The focus of the Ctrl implementation was to:
* - modify the events that are used in MSIE to look more like the standard events.
* - have "this" point to the element that has the event listener.
* - allow the ability for other JavaScript objects to have listeners.
* - allow for cleaning up to avoid memory leaks for individual DOM elements
* (and optionally their children). Ctrl does register a global cleanup function
* on document unload, but it also surfaces a removeListeners() method for
* cases when there are multiple nodes attached/detached within the web page's
* lifetime, and memory leaks want to be contained during that time.
* (Even does cleanup for Mozilla because of bug:
* https://bugzilla.mozilla.org/show_bug.cgi?id=241518
*/
//******************************************************************
/**
* Use this method for listening to events that happen on objects. The target
* can be a DOM element object, or any other object. If a string is passed as the
* value for target, then document.getElementById() will be used to get an object to
* work with.
*
* Assumptions:
* - listenType is all in lowercase, and does not include "on" suffix. For instance,
* "click" should be sent. Not "onClick" or "onclick".
* - Event capture model is not supported.
*
*/
Ctrl.listen = function(target, listenType, listener)
{
var type = typeof(target);
if (type == 'string')
Ctrl.listen(document.getElementById(target), listenType, listener);
else if (type == 'object')
{
//Attach the event to the DOM element.
var listenId = Ctrl._listenPrefix(listenType) + Ctrl._listenId++;
if (!target._$listeners)
target._$listeners = new Object();
target._$listeners[listenId] = listener;
//Do the "this" fix if it is required.
if (Ctrl._needThisFix)
listener = Ctrl._generateCallback(target, listenId);
//Track the listener in the global list (used to do cleanup
//on document unload to avoid memory leaks).
Ctrl._allListeners[listenId] = {
target: target,
listener: listener
};
//Officially register it. Issue if not a spec-defined event? Doesn't seem to be.
Ctrl._addListen(target, listenType, listener);
}
}
//******************************************************************
Ctrl.removeListen = function(target, listenType, listener)
{
var type = typeof(target);
if (type == 'string')
Ctrl.removeListen(document.getElementById(target), listenType, listener);
else if (type == 'object' && target._$listeners)
{
var idPrefix = Ctrl._listenPrefix(listenType);
for (var param in target._$listeners)
{
if (param.indexOf(idPrefix) == 0)
{
if (target._$listeners[param] == listener)
{
Ctrl._cleanListener(param, target, listenType, listener);
break;
}
}
}
}
}
//******************************************************************
Ctrl.removeListeners = function(target, removeChildListeners)
{
var type = typeof(target);
if (type == 'string')
Ctrl.removeListen(document.getElementById(target), listenType, listener);
else if (type == 'object' && target._$listeners)
{
//Find all of the registered listeners.
var listenType;
for (var param in target._$listeners)
{
if (param.indexOf('_$l_') == 0)
Ctrl._cleanListener(param, target, Ctrl._getListenType(param), listener);
}
//Removal of listeners for children, if that is wanted.
if (removeChildListeners && target.hasChildNodes)
{
for (var index = 0; index < target.childNodes.length; index++)
Ctrl.removeListeners(target.childNodes[index], true);
}
}
}
//******************************************************************
Ctrl.notify = function(object, listenType, event)
{
var idPrefix = Ctrl._listenPrefix(listenType);
if (object._$listeners)
{
for (var param in object._$listeners)
{
if (param.indexOf(idPrefix) == 0)
object._$listeners[param].apply(object, [event]);
}
}
}
//******************************************************************
//Removes all listeners. Called as part of document unload.
Ctrl.unloadListeners = function()
{
for (var param in Ctrl._allListeners)
{
if (Ctrl._allListeners[param])
Ctrl._cleanListener(param,
Ctrl._allListeners[param].target,
Ctrl._getListenType(param),
Ctrl._allListeners[param].listener,
true);
}
Ctrl._allListeners = null;
}
//******************************************************************
//******************************************************************
//******************************************************************
//Private properties and methods.
Ctrl._listenId = 0;
Ctrl._allListeners = new Object();
//******************************************************************
Ctrl._cleanListener = function(listenId, object, listenType, listener, shouldKeepGlobalId)
{
//Officially unregister it. Issue if not a spec-defined event? Doesn't seem to be.
var registeredListener = Ctrl._allListeners[listenId].listener;
Ctrl._removeListen(object, listenType, registeredListener);
//Clean up the object and the global list.
if (object._$listeners)
delete object._$listeners[listenId];
Ctrl._allListeners[listenId].target = null;
Ctrl._allListeners[listenId].listener = null;
if (shouldKeepGlobalId) //Don't delete, to avoid possible weirdness in Ctrl.unloadListeners
Ctrl._allListeners[listenId] = null;
else
delete Ctrl._allListeners[listenId];
}
//******************************************************************
Ctrl._generateCallback = function(object, id)
{
return function()
{
if (arguments[0].scrElement)
arguments[0] = Ctrl._fixEvent(arguments[0]);
object._$listeners[id].apply(object, arguments);
};
}
//******************************************************************
Ctrl._getListenType = function(listenId)
{
var endIndex = listenId.indexOf('_', 4) //4 is the first index after '_$l_'
return listenId.substring(4, endIndex);
}
//******************************************************************
Ctrl._listenPrefix = function(listenType)
{
return '_$l_' + listenType + '_';
}
//******************************************************************
Ctrl._addOn = function(listenType)
{
return (listenType.indexOf('on') != 0 ? 'on' + listenType : listenType);
}
//******************************************************************
/**
* Tries to make it easier to convert the event to have the same properties
* for MSIE and Mozilla. Copies MSIE values to names that Mozilla uses.
*
* Important parts of the rest of these event methods are from dean.edwards.name:
* http://dean.edwards.name/my/events.js
* Credits in that file:
* written by Dean Edwards, 2005
* with input from Tino Zijdel
* http://dean.edwards.name/weblog/2005/10/add-event/
* Licensed under Creative Commons license:
* http://creativecommons.org/licenses/by/2.5/
*/
Ctrl._fixEvent = function(event)
{
if (event.srcElement)
{
// add W3C standard event methods
event.preventDefault = Ctrl._fixEvent.preventDefault;
event.stopPropagation = Ctrl._fixEvent.stopPropagation;
event.target = event.srcElement;
//Setting of pageX and pageY mentioned here:
//http://www.quirksmode.org/js/events_properties.html
//For a complete set of event compatibilities, see:
//http://www.quirksmode.org/dom/w3c_events.html
event.pageX = event.clientX + document.body.scrollLeft;
event.pageY = event.clientY + document.body.scrollTop;
}
return event;
}
//******************************************************************
Ctrl._fixEvent.preventDefault = function()
{
this.returnValue = false;
}
//******************************************************************
Ctrl._fixEvent.stopPropagation = function()
{
this.cancelBubble = true;
}
//Set up browser-specific add/remove functions.
if (document.addEventListener)
{
Ctrl._needThisFix = false;
Ctrl._addListen = function(object, listenType, listener)
{
if (object.addEventListener)
object.addEventListener(listenType, listener, false);
};
Ctrl._removeListen = function(object, listenType, listener)
{
if (object.removeEventListener)
object.removeEventListener(listenType, listener, false);
};
}
else if (document.attachEvent)
{
Ctrl._needThisFix = true;
Ctrl._addListen = function(object, listenType, listener)
{
if (object.attachEvent)
object.attachEvent(Ctrl._addOn(listenType), listener);
};
Ctrl._removeListen = function(object, listenType, listener)
{
if (object.detachEvent)
object.detachEvent(Ctrl._addOn(listenType), listener);
};
}
//Register the global cleanup function to avoid memory leaks.
Ctrl.listen(window, 'unload', Ctrl.unloadListeners);
//*************************************************************************
//End event listening methods
//*************************************************************************