/* 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 //*************************************************************************