Initial progress towards converting auto-completion framework to Spry. Not currently in a very working state.
authorDan
Sun, 22 Jun 2008 18:13:59 -0400
changeset 581 5e8fd89c02ea
parent 580 41c45314ac27
child 582 a38876c0793c
Initial progress towards converting auto-completion framework to Spry. Not currently in a very working state.
includes/clientside/css/enano-shared.css
includes/clientside/jsres.php
includes/clientside/static/SpryAutoSuggest.js
includes/clientside/static/SpryData.js
includes/clientside/static/SpryJSONDataSet.js
includes/clientside/static/ajax.js
includes/clientside/static/autocomplete.js
includes/clientside/static/autofill.js
includes/clientside/static/enano-lib-basic.js
includes/clientside/static/l10n.js
includes/clientside/static/loader.js
includes/clientside/static/misc.js
includes/functions.php
includes/template.php
plugins/SpecialAdmin.php
plugins/SpecialPageFuncs.php
--- a/includes/clientside/css/enano-shared.css	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/css/enano-shared.css	Sun Jun 22 18:13:59 2008 -0400
@@ -157,7 +157,7 @@
 
 /* The beautiful tables inside what may not obviously be mdg-comment divs */
 div.tblholder td.row1             { padding: 4px; background-color: #E0E0E0; }
-div.tblholder td.row2             { padding: 4px; background-color: #F0F0F0; }
+div.tblholder td.row2             { padding: 4px; background-color: #F0F0F0 !important; } /* using !important allows emphasized cells like in autosuggest to be highlighted */
 div.tblholder td.row3             { padding: 4px; background-color: #E8E8E8; }
 div.tblholder th                  { padding: 4px; background-color: #7080A0; font-weight: bold; text-align: center; color: #FFFFFF; }
 div.tblholder th.subhead          { padding: 4px; background-color: #90A0B0; font-weight: bold; text-align: center; color: #FFFFFF; }
@@ -240,20 +240,6 @@
   background-image: url(../../../images/search-btn-hilite.png);
 }
 
-input[type ^="text"].username, input[type ^="password"].password {
-  padding: 2px 2px 2px 27px;
-  width: 96px;
-  height: 15px;
-  border: 0px none #000;
-  font-size: 11px;
-}
-input[type ^="text"].username {
-  background-image: url(../../../images/login-username.png);
-}
-input[type ^="password"].password {
-  background-image: url(../../../images/login-password.png);
-}
-
 /*
  * jBox menu system
  */
@@ -827,3 +813,21 @@
 div.rankadmin-right {
   float: left;
 }
+
+/* Spry auto-suggestion */
+.hideSuggestClass
+{
+	max-height: 200px;
+	overflow:auto;
+	display:none;
+	width: 175px;
+	margin: 0px;
+	cursor: pointer;
+	z-index: 1011;
+}
+
+.showSuggestClass .hideSuggestClass
+{
+	display: block !important;
+}
+
--- a/includes/clientside/jsres.php	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/jsres.php	Sun Jun 22 18:13:59 2008 -0400
@@ -54,9 +54,12 @@
 // Files safe to run full (aggressive) compression on
 $full_compress_safe = array(
   // Sorted by file size, descending (du -b *.js | sort -n)
-  'libbigint.js',
+  // 'SpryData.js',
+  // 'SpryEffects.js',
   'ajax.js',
+  'libbigint.js',
   'editor.js',
+  // 'SpryAutoSuggest.js',
   'login.js',
   'acl.js',
   'misc.js',
@@ -65,9 +68,9 @@
   'autofill.js',
   'dropdown.js',
   'paginate.js',
-  'autocomplete.js',
   'md5.js',
   'enano-lib-basic.js',
+  'SpryJSONDataSet.js',
   'pwstrength.js',
   'sha256.js',
   'flyin.js',
@@ -79,7 +82,7 @@
 );
 
 // Files that should NOT be compressed due to already being compressed, licensing, or invalid produced code
-$compress_unsafe = array('SpryEffects.js', 'json.js', 'fat.js', 'admin-menu.js');
+$compress_unsafe = array('SpryEffects.js', 'json.js', 'fat.js', 'admin-menu.js', 'autofill.js');
 
 require('includes/functions.php');
 require('includes/json2.php');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/SpryAutoSuggest.js	Sun Jun 22 18:13:59 2008 -0400
@@ -0,0 +1,883 @@
+// SpryAutoSuggest.js - version 0.91 - Spry Pre-Release 1.6.1
+//
+// Copyright (c) 2006. Adobe Systems Incorporated.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//   * Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//   * Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//   * Neither the name of Adobe Systems Incorporated nor the names of its
+//     contributors may be used to endorse or promote products derived from this
+//     software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+var Spry;
+if (!Spry) Spry = {};
+if (!Spry.Widget) Spry.Widget = {};
+
+Spry.Widget.BrowserSniff = function()
+{
+	var b = navigator.appName.toString();
+	var up = navigator.platform.toString();
+	var ua = navigator.userAgent.toString();
+
+	this.mozilla = this.ie = this.opera = this.safari = false;
+	var re_opera = /Opera.([0-9\.]*)/i;
+	var re_msie = /MSIE.([0-9\.]*)/i;
+	var re_gecko = /gecko/i;
+	var re_safari = /(applewebkit|safari)\/([\d\.]*)/i;
+	var r = false;
+
+	if ( (r = ua.match(re_opera))) {
+		this.opera = true;
+		this.version = parseFloat(r[1]);
+	} else if ( (r = ua.match(re_msie))) {
+		this.ie = true;
+		this.version = parseFloat(r[1]);
+	} else if ( (r = ua.match(re_safari))) {
+		this.safari = true;
+		this.version = parseFloat(r[2]);
+	} else if (ua.match(re_gecko)) {
+		var re_gecko_version = /rv:\s*([0-9\.]+)/i;
+		r = ua.match(re_gecko_version);
+		this.mozilla = true;
+		this.version = parseFloat(r[1]);
+	}
+	this.windows = this.mac = this.linux = false;
+
+	this.Platform = ua.match(/windows/i) ? "windows" :
+					(ua.match(/linux/i) ? "linux" :
+					(ua.match(/mac/i) ? "mac" :
+					ua.match(/unix/i)? "unix" : "unknown"));
+	this[this.Platform] = true;
+	this.v = this.version;
+
+	if (this.safari && this.mac && this.mozilla) {
+		this.mozilla = false;
+	}
+};
+
+Spry.is = new Spry.Widget.BrowserSniff();
+
+Spry.Widget.AutoSuggest = function(region, sRegion, dataset, field, options)
+{
+	if (!this.isBrowserSupported())
+		return;
+
+	options = options || {};
+
+	this.init(region, sRegion, dataset, field);
+	Spry.Widget.Utils.setOptions(this, options);
+
+	if (Spry.Widget.AutoSuggest.onloadDidFire)
+		this.attachBehaviors();
+	else 
+		Spry.Widget.AutoSuggest.loadQueue.push(this);
+
+	// when data is changing we will decide if we will have to show the suggestions
+	this.dataset.addObserver(this);
+	
+	// Set up an observer so we can attach our click behaviors whenever
+	// the region is regenerated.
+	var regionID = Spry.Widget.Utils.getElementID(sRegion);
+
+	var self = this;
+	this._notifyDataset = { onPostUpdate: function() {
+			self.attachClickBehaviors();
+	}, onPreUpdate: function(){
+			self.removeClickBehaviours();
+	}};
+
+	Spry.Data.Region.addObserver(regionID,this._notifyDataset);
+
+	// clean up the widget when on page unload
+	Spry.Widget.Utils.addEventListener(window, 'unload', function(){self.destroy()}, false);
+
+	// make the first computation in case the textfield is not empty
+	this.attachClickBehaviors();
+	this.handleKeyUp(null);
+	this.showSuggestions(false);
+};
+
+Spry.Widget.AutoSuggest.prototype.init = function(region, sRegion, dataset, field)
+{
+	this.region = Spry.Widget.Utils.getElement(region);
+	
+	if (!this.region)
+		return;
+	
+	this.minCharsType = false;
+	this.containsString = false;
+	this.loadFromServer = false;
+	this.urlParam = '';
+	this.suggestionIsVisible = false;
+	this.stopFocus = false;
+	this.hasFocus = false;
+	this.showSuggestClass = 'showSuggestClass';
+	this.hideSuggestClass = 'hideSuggestClass';
+	this.hoverSuggestClass = 'hoverSuggestClass';
+	this.movePrevKeyCode = Spry.Widget.AutoSuggest.KEY_UP;
+	this.moveNextKeyCode = Spry.Widget.AutoSuggest.KEY_DOWN;
+
+	this.textElement = Spry.Widget.Utils.getFirstChildWithNodeNameAtAnyLevel(this.region, "INPUT");
+	this.textElement.setAttribute('AutoComplete', 'off');
+	
+	this.suggestRegion = Spry.Widget.Utils.getElement(sRegion);
+	// prepare the suggest region
+	Spry.Widget.Utils.makePositioned(this.suggestRegion);
+	Spry.Widget.Utils.addClassName(this.suggestRegion, this.hideSuggestClass);
+
+	this.timerID = null;
+	if (typeof dataset == "string"){
+		this.dataset = window[dataset];
+	}else{
+		this.dataset = dataset;
+	}
+	this.field = field;
+	if (typeof field == 'string' && field.indexOf(',') != -1)
+	{
+		field = field.replace(/\s*,\s*/ig, ',');
+		this.field = field.split(',');
+	}
+};
+
+Spry.Widget.AutoSuggest.prototype.isBrowserSupported = function()
+{
+	return Spry.is.ie && Spry.is.v >= 5 && Spry.is.windows
+		||
+	Spry.is.mozilla && Spry.is.v >= 1.4
+		||
+	Spry.is.safari
+		||
+	Spry.is.opera && Spry.is.v >= 9;
+};
+
+Spry.Widget.AutoSuggest.prototype.getValue = function()
+{
+	if (!this.textElement)
+		return '';
+	return this.textElement.value;
+};
+
+Spry.Widget.AutoSuggest.prototype.setValue = function(str)
+{
+	if (!this.textElement)
+		return;
+	this.textElement.value = str;
+	this.showSuggestions(false);
+};
+
+Spry.Widget.AutoSuggest.prototype.focus = function()
+{
+	if (!this.textElement)
+		return;
+	this.textElement.focus();
+};
+
+Spry.Widget.AutoSuggest.prototype.showSuggestions = function(doShow)
+{
+	if (this.region && this.isVisibleSuggestion() != doShow)
+	{
+		if (doShow && this.hasFocus)
+		{
+				Spry.Widget.Utils.addClassName(this.region, this.showSuggestClass);
+				if (Spry.is.ie && Spry.is.version < 7)
+					this.createIframeLayer(this.suggestRegion);
+		}
+		else
+		{
+				if (Spry.is.ie && Spry.is.version < 7)
+					this.removeIframeLayer();
+				Spry.Widget.Utils.removeClassName(this.region, this.showSuggestClass);
+		}
+	}
+	this.suggestionIsVisible = Spry.Widget.Utils.hasClassName(this.region, this.showSuggestClass);
+};
+
+Spry.Widget.AutoSuggest.prototype.isVisibleSuggestion = function()
+{
+	return this.suggestionIsVisible;
+};
+
+Spry.Widget.AutoSuggest.prototype.onDataChanged = function(el)
+{
+		var data = el.getData(true);
+		var val = this.getValue();
+		this.showSuggestions(data && (!this.minCharsType || val.length >= this.minCharsType) && (data.length > 1 || (data.length == 1 && this.childs[0] && this.childs[0].attributes.getNamedItem("spry:suggest").value != this.getValue())));
+};
+Spry.Widget.AutoSuggest.prototype.nodeMouseOver = function(e, node)
+{
+	var l = this.childs.length;
+	for (var i=0; i<l; i++)
+		if (this.childs[i] != node && Spry.Widget.Utils.hasClassName(this.childs[i], this.hoverSuggestClass))
+		{
+			Spry.Widget.Utils.removeClassName(this.childs[i], this.hoverSuggestClass);
+			break;
+		}
+};
+Spry.Widget.AutoSuggest.prototype.nodeClick = function(e, value) 
+{
+	if (value)
+		this.setValue(value);
+};
+Spry.Widget.AutoSuggest.prototype.handleKeyUp = function(e)
+{
+	if (this.timerID)
+	{
+		clearTimeout(this.timerID);
+		this.timerID = null;
+	}
+
+	// If the user hit the escape key, hide the auto suggest menu!
+	if (e && this.isSpecialKey(e))
+	{
+		this.handleSpecialKeys(e);
+		return;
+	}
+
+	var self = this;
+	var func = function() { self.timerID = null; self.loadDataSet();};
+	if (!this.loadFromServer)
+		func = function() { self.timerID = null; self.filterDataSet();};
+
+	this.timerID = setTimeout(func, 200);
+	
+};
+
+Spry.Widget.AutoSuggest.prototype.scrollVisible = function(el)
+{
+	if (typeof this.scrolParent == 'undefined')
+	{
+		var currEl = el;
+		this.scrolParent = false;
+		while	(!this.scrolParent)
+		{
+			var overflow = Spry.Widget.Utils.getStyleProp(currEl, 'overflow');
+			if (!overflow || overflow.toLowerCase() == 'scroll')
+			{
+					this.scrolParent = currEl;
+					break;
+			}
+			if (currEl == this.region) 
+				break;
+			
+			currEl = currEl.parentNode;
+		}
+	}
+
+	if (this.scrolParent != false)
+	{
+		var h = parseInt(Spry.Widget.Utils.getStyleProp(this.scrolParent, 'height'), 10);
+		if (el.offsetTop < this.scrolParent.scrollTop)
+			this.scrolParent.scrollTop = el.offsetTop;
+		else if (el.offsetTop + el.offsetHeight > this.scrolParent.scrollTop + h)
+		{
+			// the 5 pixels make the latest option more visible.
+			this.scrolParent.scrollTop = el.offsetTop + el.offsetHeight - h + 5;
+			if (this.scrolParent.scrollTop < 0)
+				this.scrolParent.scrollTop = 0;	
+		}
+
+	}
+};
+
+Spry.Widget.AutoSuggest.KEY_UP = 38;
+Spry.Widget.AutoSuggest.KEY_DOWN = 40;
+
+Spry.Widget.AutoSuggest.prototype.handleSpecialKeys = function(e){
+
+ 	switch (e.keyCode)
+	{
+		case this.moveNextKeyCode: // Down key  
+ 		case this.movePrevKeyCode: // Up Key
+			if (!(this.childs.length > 0) || !this.getValue())
+				return;	
+
+			var prev = this.childs.length-1;
+			var next = false;
+			var found = false;
+			var data = this.dataset.getData();
+			if (this.childs.length > 1 || (data && data.length == 1 && this.childs[0] && this.childs[0].attributes.getNamedItem('spry:suggest').value != this.getValue()))
+			{
+				this.showSuggestions(true);
+			}
+			else
+				return;	
+			
+			var utils = Spry.Widget.Utils;
+			for (var k=0; k < this.childs.length; k++)
+			{
+				if (next)
+				{
+					utils.addClassName(this.childs[k], this.hoverSuggestClass);
+					this.scrollVisible(this.childs[k]);
+					break;
+				}
+				if (utils.hasClassName(this.childs[k], this.hoverSuggestClass))
+				{
+					utils.removeClassName(this.childs[k], this.hoverSuggestClass);
+					found = true;
+					if (e.keyCode == this.moveNextKeyCode)
+					{
+						next = true;
+						continue;
+					}
+					else
+					{
+						utils.addClassName(this.childs[prev], this.hoverSuggestClass);
+						this.scrollVisible(this.childs[prev]);
+						break;
+					}
+				}
+				prev = k;
+			}
+			if (!found || (next && k == this.childs.length))
+			{
+				utils.addClassName(this.childs[0], this.hoverSuggestClass);
+				this.scrollVisible(this.childs[0]);
+			}
+			utils.stopEvent(e);
+			break;
+		case 27: // ESC key
+			this.showSuggestions(false);
+			break;
+		case 13: //Enter Key
+			if (!this.isVisibleSuggestion()) 
+				return;
+			for (var k=0; k < this.childs.length; k++)
+				if (Spry.Widget.Utils.hasClassName(this.childs[k], this.hoverSuggestClass))
+				{
+					var attr = this.childs[k].attributes.getNamedItem('spry:suggest');
+					if (attr){
+						this.setValue(attr.value);
+						this.handleKeyUp(null);
+					}
+					// stop form submission
+					Spry.Widget.Utils.stopEvent(e);
+					return false;
+				}
+			break;
+		case 9: //Tab Key
+			this.showSuggestions(false);
+	}
+	return;
+};
+
+Spry.Widget.AutoSuggest.prototype.filterDataSet = function()
+{
+	var contains = this.containsString;
+	var columnName = this.field;
+	var val = this.getValue();
+
+	if (this.previousString && this.previousString == val)
+		return;
+
+	this.previousString = val;
+
+	if (!val || (this.minCharsType && this.minCharsType > val.length))
+	{
+		this.dataset.filter(function(ds, row, rowNumber) {return null;});
+		this.showSuggestions(false);
+		return;
+	}
+
+	var regExpStr = Spry.Widget.Utils.escapeRegExp(val);
+
+	if (!contains)
+	 	regExpStr = "^" + regExpStr;
+
+	var regExp = new RegExp(regExpStr, "ig");
+	
+	if (this.maxListItems > 0)
+		this.dataset.maxItems = this.maxListItems;
+
+	var filterFunc = function(ds, row, rowNumber)
+	{
+		if (ds.maxItems >0  && ds.maxItems <= ds.data.length)
+			return null;
+
+		if (typeof columnName == 'object')
+		{
+			var l = columnName.length;
+			for (var i=0; i < l; i++)
+			{
+				var str = row[columnName[i]];
+				if (str && str.search(regExp) != -1)
+					return row;
+			}
+		}
+		else
+		{
+			var str = row[columnName];
+			if (str && str.search(regExp) != -1)
+				return row;
+		}
+		return null; 
+	};
+
+	this.dataset.filter(filterFunc);
+	var data = this.dataset.getData();
+	this.showSuggestions(data && (!this.minCharsType || val.length >= this.minCharsType) && (data.length > 1 || (data.length == 1 && this.childs[0] && this.childs[0].attributes.getNamedItem('spry:suggest').value != val )));
+};
+
+Spry.Widget.AutoSuggest.prototype.loadDataSet = function()
+{
+	var val = this.getValue();
+	var ds = this.dataset;
+	ds.cancelLoadData();
+	ds.useCache = false;
+	
+	if (!val || (this.minCharsType && this.minCharsType > val.length))
+	{
+		this.showSuggestions(false);
+		return;
+	}
+	
+	if (this.previousString && this.previousString == val)
+	{
+		var data = ds.getData();
+		this.showSuggestions(data && (data.length > 1 || (data.length == 1 && this.childs[0].attributes.getNamedItem("spry:suggest").value != val)));
+		return;
+	}
+
+	this.previousString = val;
+
+	var url = Spry.Widget.Utils.addReplaceParam(ds.url, this.urlParam, val);
+	ds.setURL(url);
+	ds.loadData();
+};
+
+Spry.Widget.AutoSuggest.prototype.addMouseListener =  function(node, value)
+{
+	var self = this;
+	var addListener = Spry.Widget.Utils.addEventListener;
+	addListener(node, "click", function(e){ return self.nodeClick(e, value); self.handleKeyUp(null);}, false); 
+	addListener(node, "mouseover", function(e){ Spry.Widget.Utils.addClassName(node, self.hoverSuggestClass); self.nodeMouseOver(e, node)}, false); 
+	addListener(node, "mouseout", function(e){ Spry.Widget.Utils.removeClassName(node, self.hoverSuggestClass); self.nodeMouseOver(e, node)}, false); 
+};
+Spry.Widget.AutoSuggest.prototype.removeMouseListener =  function(node, value)
+{
+	var self = this;
+	var removeListener = Spry.Widget.Utils.removeEventListener;
+	removeListener(node, "click", function (e){ self.nodeClick(e, value); self.handleKeyUp(null);}, false); 
+	removeListener(node, "mouseover", function(e){ Spry.Widget.Utils.addClassName(node, self.hoverSuggestClass); self.nodeMouseOver(e, node)}, false); 
+	removeListener(node, "mouseout", function(e){ Spry.Widget.Utils.removeClassName(node, self.hoverSuggestClass); self.nodeMouseOver(e, node)}, false); 
+};
+Spry.Widget.AutoSuggest.prototype.attachClickBehaviors =  function()
+{
+	var self = this;
+	var valNodes = Spry.Utils.getNodesByFunc(this.region, function(node)
+	{
+		if (node.nodeType == 1) /* Node.ELEMENT_NODE */
+		{
+			var attr = node.attributes.getNamedItem("spry:suggest");
+			if (attr){
+				self.addMouseListener(node, attr.value);
+				return true;
+			}
+		}
+		return false;
+	});
+	this.childs = valNodes;
+};
+Spry.Widget.AutoSuggest.prototype.removeClickBehaviours = function()
+{
+	var self = this;
+	var valNodes = Spry.Utils.getNodesByFunc(this.region, function(node)
+	{
+		if (node.nodeType == 1) /* Node.ELEMENT_NODE */
+		{
+			var attr = node.attributes.getNamedItem("spry:suggest");
+			if (attr){
+				self.removeMouseListener(node, attr.value);
+				return true;
+			}
+		}
+		return false;
+	});
+};
+Spry.Widget.AutoSuggest.prototype.destroy = function(){
+
+	this.removeClickBehaviours();
+	Spry.Data.Region.removeObserver(Spry.Widget.Utils.getElementID(this.suggestRegion),this._notifyDataset);
+	
+	if (this.event_handlers)
+		for (var i=0; i<this.event_handlers.length; i++) {
+			Spry.Widget.Utils.removeEventListener(this.event_handlers[i][0], this.event_handlers[i][1], this.event_handlers[i][2], false);
+		}
+
+	for (var k in this)
+	{
+		if (typeof this[k] != 'function'){
+			try { delete this[k]; } catch(err) {}
+		}
+	}
+};
+
+Spry.Widget.AutoSuggest.onloadDidFire = false;
+Spry.Widget.AutoSuggest.loadQueue = [];
+
+Spry.Widget.AutoSuggest.processLoadQueue = function(handler)
+{
+	Spry.Widget.AutoSuggest.onloadDidFire = true;
+	var q = Spry.Widget.AutoSuggest.loadQueue;
+	var qlen = q.length;
+	for (var i = 0; i < qlen; i++)
+		q[i].attachBehaviors();
+};
+
+Spry.Widget.AutoSuggest.addLoadListener = function(handler)
+{
+	if (typeof window.addEventListener != 'undefined')
+		window.addEventListener('load', handler, false);
+	else if (typeof document.addEventListener != 'undefined')
+		document.addEventListener('load', handler, false);
+	else if (typeof window.attachEvent != 'undefined')
+		window.attachEvent('onload', handler);
+};
+
+Spry.Widget.AutoSuggest.addLoadListener(Spry.Widget.AutoSuggest.processLoadQueue);
+
+Spry.Widget.AutoSuggest.prototype.attachBehaviors = function()
+{
+	this.event_handlers = [];
+	var self = this;
+	// adding listeners to the text input to catch the text changes
+	var _notifyKeyUp = function(e){ self.handleKeyUp(e)};
+	this.event_handlers.push([this.textElement, "keydown", _notifyKeyUp]); 
+	this.event_handlers.push([this.textElement, "focus", function(e){ if (self.stopFocus){ self.handleKeyUp(e);} self.hasFocus = true; self.stopFocus = false;}]);
+	this.event_handlers.push([this.textElement, "drop", _notifyKeyUp]);
+	this.event_handlers.push([this.textElement, "dragdrop", _notifyKeyUp]);
+	
+	var _notifyBlur = false;
+	// on opera the blur is triggered before onclick
+	if (Spry.is.opera){
+		_notifyBlur = function(e) { setTimeout(function(){if (!self.clickInList){ self.showSuggestions(false); }else{ self.stopFocus = true; self.textElement.focus();} self.clickInList = false; self.hasFocus = false;}, 100); };
+	}else{
+		_notifyBlur = function(e) { if (!self.clickInList){ self.showSuggestions(false); }else{ self.stopFocus = true; self.textElement.focus();} self.clickInList = false; self.hasFocus = false;};
+	}
+	this.event_handlers.push([this.textElement, "blur", _notifyBlur]);
+
+	// we listen on the suggest region too
+	this.event_handlers.push([this.suggestRegion, "mousedown", function(e){ self.clickInList = true;}]);
+
+	for (var i=0; i<this.event_handlers.length; i++) 
+		Spry.Widget.Utils.addEventListener(this.event_handlers[i][0], this.event_handlers[i][1], this.event_handlers[i][2], false);
+};
+
+// createIframeLayer for Tooltip
+// creates an IFRAME underneath a tooltip element so that it will show above form controls and ActiveX
+Spry.Widget.AutoSuggest.prototype.createIframeLayer = function(element)
+{
+	if (typeof this.iframeLayer == 'undefined')
+	{
+		var layer = document.createElement('iframe');
+		layer.tabIndex = '-1';
+		layer.src = 'javascript:"";';
+		layer.scrolling = 'no';
+		layer.frameBorder = '0';
+		layer.className = 'iframeSuggest';
+		element.parentNode.appendChild(layer);
+		this.iframeLayer = layer;
+	}
+	this.iframeLayer.style.left = element.offsetLeft + 'px';
+	this.iframeLayer.style.top = element.offsetTop + 'px';
+	this.iframeLayer.style.width = element.offsetWidth + 'px';
+	this.iframeLayer.style.height = element.offsetHeight + 'px';
+	this.iframeLayer.style.display = 'block';	
+};
+
+// removeIframeLayer for Tooltip Element
+// removes an IFRAME underneath a tooltip to reveal any form controls and ActiveX
+Spry.Widget.AutoSuggest.prototype.removeIframeLayer =  function()
+{
+	if (this.iframeLayer)
+		this.iframeLayer.style.display = 'none';
+};
+
+//////////////////////////////////////////////////////////////////////
+//
+// Spry.Widget.Utils
+//
+//////////////////////////////////////////////////////////////////////
+if (!Spry.Widget.Utils)	Spry.Widget.Utils = {};
+
+Spry.Widget.Utils.specialSafariNavKeys = ",63232,63233,63234,63235,63272,63273,63275,63276,63277,63289,"; //left,up,rigtht,down arrows,delete,home,end,page up,page down,num lock
+Spry.Widget.Utils.specialCharacters = ",9,13,27,38,40,";              //suggest keys: tab,enter,escape,up arrow,down arrow
+Spry.Widget.Utils.specialCharacters += ",33,34,35,36,37,39,45,46,";   //edit keys: insert,delete,home,end,left arrow,right arrow,page up,page down
+Spry.Widget.Utils.specialCharacters += ",16,17,18,19,20,144,145,";    //control keys: shift,control,alt,pause,caps lock,num lock,scroll lock
+Spry.Widget.Utils.specialCharacters += ",112,113,114,115,116,117,118,119,120,121,122,123,"; //F1-F12
+Spry.Widget.Utils.specialCharacters += Spry.Widget.Utils.specialSafariNavKeys;
+
+Spry.Widget.AutoSuggest.prototype.isSpecialKey = function (ev)
+{
+	return Spry.Widget.Utils.specialCharacters.indexOf("," + ev.keyCode + ",") != -1 || this.moveNextKeyCode == ev.keyCode || this.movePrevKeyCode == ev.keyCode;
+};
+Spry.Widget.Utils.getElementID = function(el)
+{
+	if (typeof el == 'string' && el)
+		return el;
+	return el.getAttribute('id');
+};
+Spry.Widget.Utils.getElement = function(ele)
+{
+	if (ele && typeof ele == "string")
+		return document.getElementById(ele);
+	return ele;
+};
+Spry.Widget.Utils.addReplaceParam = function(url, param, paramValue)
+{
+	var uri ='';
+	var qstring = '';
+	var i = url.indexOf('?');
+	if ( i != -1)
+	{
+		uri = url.slice(0, i);
+		qstring = url.slice(i+1);
+	}
+	else 
+		uri = url;
+
+	// the list of parameters
+	qstring = qstring.replace('?', '');
+	var arg = qstring.split("&");
+
+	// prevent malicious use
+	if (param.lastIndexOf('/') != -1)
+		param = param.slice(param.lastIndexOf('/')+1);
+
+	// remove param from the list
+	for (i=0; i < arg.length ;i++)
+	{
+		var k = arg[i].split('=');
+		if ( (k[0] && k[0] == decodeURI(param)) || arg[i] == decodeURI(param))
+			arg[i] = null;
+	}
+
+	arg[arg.length] = encodeURIComponent(param) + '=' + encodeURIComponent(paramValue);
+	qstring = '';
+	// reconstruct the qstring
+	for (i=0; i < arg.length; i++)
+		if (arg[i])
+			qstring += '&'+arg[i];
+
+	// remove the first &
+	qstring = qstring.slice(1);
+
+	url = uri + '?' + qstring;
+	return url;
+};
+
+Spry.Widget.Utils.addClassName = function(ele, clssName)
+{
+	if (!ele) return;
+
+	if (!ele.className) ele.className = '';
+
+	if (!ele || ele.className.search(new RegExp("\\b" + clssName + "\\b")) != -1)
+	  return;
+
+	ele.className += ' ' + clssName;
+};
+
+Spry.Widget.Utils.removeClassName = function(ele, className)
+{
+	if (!ele) return;	
+
+	if (!ele.className)
+	{
+		ele.className = '';
+		return;
+	}
+	ele.className = ele.className.replace(new RegExp("\\s*\\b" + className + "\\b", "g"), '');
+};
+
+Spry.Widget.Utils.hasClassName = function (ele, className)
+{
+	if (!ele || !className)
+		return false;
+
+	if (!ele.className)
+		ele.className = '';
+
+	return ele.className.search(new RegExp("\\s*\\b" + className + "\\b")) != -1;
+};
+
+Spry.Widget.Utils.addEventListener = function(el, eventType, handler, capture)
+{
+	try
+	{
+		if (el.addEventListener)
+			el.addEventListener(eventType, handler, capture);
+		else if (el.attachEvent)
+			el.attachEvent("on" + eventType, handler, capture);
+	}catch (e) {}
+};
+
+Spry.Widget.Utils.removeEventListener = function(el, eventType, handler, capture)
+{
+	try
+	{
+		if (el.removeEventListener)
+			el.removeEventListener(eventType, handler, capture);
+		else if (el.detachEvent)
+			el.detachEvent("on" + eventType, handler, capture);
+	}catch (e) {}
+};
+
+Spry.Widget.Utils.stopEvent = function(ev)
+{
+	ev.cancelBubble = true;
+	ev.returnValue = false;
+
+	try
+	{
+		this.stopPropagation(ev);
+	}catch (e){}
+	try{
+		this.preventDefault(ev);
+	}catch(e){}
+};
+
+/**
+ * Stops event propagation
+ * @param {Event} ev the event
+ */
+Spry.Widget.Utils.stopPropagation = function(ev)
+{
+	if (ev.stopPropagation)
+		ev.stopPropagation();
+	else
+		ev.cancelBubble = true;
+};
+
+/**
+ * Prevents the default behavior of the event
+ * @param {Event} ev the event
+ */
+Spry.Widget.Utils.preventDefault = function(ev)
+{
+	if (ev.preventDefault)
+		ev.preventDefault();
+	else
+		ev.returnValue = false;
+};
+
+Spry.Widget.Utils.setOptions = function(obj, optionsObj, ignoreUndefinedProps)
+{
+	if (!optionsObj)
+		return;
+	for (var optionName in optionsObj)
+	{
+		if (typeof ignoreUndefinedProps != 'undefined' && ignoreUndefinedProps && typeof optionsObj[optionName] == 'undefined')
+			continue;
+		obj[optionName] = optionsObj[optionName];
+	}
+};
+
+Spry.Widget.Utils.firstValid = function()
+{
+	var ret = null;
+	for (var i=0; i < Spry.Widget.Utils.firstValid.arguments.length; i++)
+		if (typeof Spry.Widget.Utils.firstValid.arguments[i] != 'undefined')
+		{
+			ret = Spry.Widget.Utils.firstValid.arguments[i];
+			break;
+		}
+
+	return ret;
+};
+
+Spry.Widget.Utils.camelize = function(stringToCamelize)
+{
+  var oStringList = stringToCamelize.split('-');
+	var isFirstEntry = true;
+	var camelizedString = '';
+
+	for(var i=0; i < oStringList.length; i++)
+	{
+		if(oStringList[i].length>0)
+		{
+			if(isFirstEntry)
+			{
+				camelizedString = oStringList[i];
+				isFirstEntry = false;
+			}
+			else
+			{
+				var s = oStringList[i];
+				camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+			}
+		}
+	}
+
+	return camelizedString;
+};
+
+Spry.Widget.Utils.getStyleProp = function(element, prop)
+{
+	var value;
+	var camel = Spry.Widget.Utils.camelize(prop);
+	try
+	{
+		value = element.style[camel];
+		if (!value)
+		{
+			if (document.defaultView && document.defaultView.getComputedStyle)
+			{
+				var css = document.defaultView.getComputedStyle(element, null);
+				value = css ? css.getPropertyValue(prop) : null;
+			}
+			else
+				if (element.currentStyle)
+					value = element.currentStyle[camel];
+		}
+	}
+	catch (e) {}
+
+	return value == 'auto' ? null : value;
+};
+Spry.Widget.Utils.makePositioned = function(element)
+{
+	var pos = Spry.Widget.Utils.getStyleProp(element, 'position');
+	if (!pos || pos == 'static')
+	{
+		element.style.position = 'relative';
+
+		// Opera returns the offset relative to the positioning context, when an
+		// element is position relative but top and left have not been defined
+		if (window.opera)
+		{
+			element.style.top = 0;
+			element.style.left = 0;
+		}
+	}
+};
+Spry.Widget.Utils.escapeRegExp = function(rexp)
+{
+	return rexp.replace(/([\.\/\]\[\{\}\(\)\\\$\^\?\*\|\!\=\+\-])/g, '\\$1');
+};
+Spry.Widget.Utils.getFirstChildWithNodeNameAtAnyLevel = function(node, nodeName)
+{
+	var elements  = node.getElementsByTagName(nodeName);
+	if (elements)
+		return elements[0];
+	
+	return null;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/SpryData.js	Sun Jun 22 18:13:59 2008 -0400
@@ -0,0 +1,1045 @@
+// SpryData.js - version 0.45 - Spry Pre-Release 1.6.1
+//
+// Copyright (c) 2007. Adobe Systems Incorporated.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//   * Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//   * Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//   * Neither the name of Adobe Systems Incorporated nor the names of its
+//     contributors may be used to endorse or promote products derived from this
+//     software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+var Spry;if(!Spry)Spry={};if(!Spry.Utils)Spry.Utils={};Spry.Utils.msProgIDs=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0"];Spry.Utils.createXMLHttpRequest=function()
+{var req=null;try
+{if(window.ActiveXObject)
+{while(!req&&Spry.Utils.msProgIDs.length)
+{try{req=new ActiveXObject(Spry.Utils.msProgIDs[0]);}catch(e){req=null;}
+if(!req)
+Spry.Utils.msProgIDs.splice(0,1);}}
+if(!req&&window.XMLHttpRequest)
+req=new XMLHttpRequest();}
+catch(e){req=null;}
+if(!req)
+Spry.Debug.reportError("Failed to create an XMLHttpRequest object!");return req;};Spry.Utils.loadURL=function(method,url,async,callback,opts)
+{var req=new Spry.Utils.loadURL.Request();req.method=method;req.url=url;req.async=async;req.successCallback=callback;Spry.Utils.setOptions(req,opts);try
+{req.xhRequest=Spry.Utils.createXMLHttpRequest();if(!req.xhRequest)
+return null;if(req.async)
+req.xhRequest.onreadystatechange=function(){Spry.Utils.loadURL.callback(req);};req.xhRequest.open(req.method,req.url,req.async,req.username,req.password);if(req.headers)
+{for(var name in req.headers)
+req.xhRequest.setRequestHeader(name,req.headers[name]);}
+req.xhRequest.send(req.postData);if(!req.async)
+Spry.Utils.loadURL.callback(req);}
+catch(e)
+{if(req.errorCallback)
+req.errorCallback(req);else
+Spry.Debug.reportError("Exception caught while loading "+url+": "+e);req=null;}
+return req;};Spry.Utils.loadURL.callback=function(req)
+{if(!req||req.xhRequest.readyState!=4)
+return;if(req.successCallback&&(req.xhRequest.status==200||req.xhRequest.status==0))
+req.successCallback(req);else if(req.errorCallback)
+req.errorCallback(req);};Spry.Utils.loadURL.Request=function()
+{var props=Spry.Utils.loadURL.Request.props;var numProps=props.length;for(var i=0;i<numProps;i++)
+this[props[i]]=null;this.method="GET";this.async=true;this.headers={};};Spry.Utils.loadURL.Request.props=["method","url","async","username","password","postData","successCallback","errorCallback","headers","userData","xhRequest"];Spry.Utils.loadURL.Request.prototype.extractRequestOptions=function(opts,undefineRequestProps)
+{if(!opts)
+return;var props=Spry.Utils.loadURL.Request.props;var numProps=props.length;for(var i=0;i<numProps;i++)
+{var prop=props[i];if(opts[prop]!=undefined)
+{this[prop]=opts[prop];if(undefineRequestProps)
+opts[prop]=undefined;}}};Spry.Utils.loadURL.Request.prototype.clone=function()
+{var props=Spry.Utils.loadURL.Request.props;var numProps=props.length;var req=new Spry.Utils.loadURL.Request;for(var i=0;i<numProps;i++)
+req[props[i]]=this[props[i]];if(this.headers)
+{req.headers={};Spry.Utils.setOptions(req.headers,this.headers);}
+return req;};Spry.Utils.setInnerHTML=function(ele,str,preventScripts)
+{if(!ele)
+return;ele=Spry.$(ele);var scriptExpr="<script[^>]*>(.|\s|\n|\r)*?</script>";ele.innerHTML=str.replace(new RegExp(scriptExpr,"img"),"");if(preventScripts)
+return;var matches=str.match(new RegExp(scriptExpr,"img"));if(matches)
+{var numMatches=matches.length;for(var i=0;i<numMatches;i++)
+{var s=matches[i].replace(/<script[^>]*>[\s\r\n]*(<\!--)?|(-->)?[\s\r\n]*<\/script>/img,"");Spry.Utils.eval(s);}}};Spry.Utils.updateContent=function(ele,url,finishFunc,opts)
+{Spry.Utils.loadURL("GET",url,true,function(req)
+{Spry.Utils.setInnerHTML(ele,req.xhRequest.responseText);if(finishFunc)
+finishFunc(ele,url);},opts);};if(!Spry.$$)
+{Spry.Utils.addEventListener=function(element,eventType,handler,capture)
+{try
+{element=Spry.$(element);if(element.addEventListener)
+element.addEventListener(eventType,handler,capture);else if(element.attachEvent)
+element.attachEvent("on"+eventType,handler);}
+catch(e){}};Spry.Utils.removeEventListener=function(element,eventType,handler,capture)
+{try
+{element=Spry.$(element);if(element.removeEventListener)
+element.removeEventListener(eventType,handler,capture);else if(element.detachEvent)
+element.detachEvent("on"+eventType,handler);}
+catch(e){}};Spry.Utils.addLoadListener=function(handler)
+{if(typeof window.addEventListener!='undefined')
+window.addEventListener('load',handler,false);else if(typeof document.addEventListener!='undefined')
+document.addEventListener('load',handler,false);else if(typeof window.attachEvent!='undefined')
+window.attachEvent('onload',handler);};Spry.Utils.addClassName=function(ele,className)
+{ele=Spry.$(ele);if(!ele||!className||(ele.className&&ele.className.search(new RegExp("\\b"+className+"\\b"))!=-1))
+return;ele.className+=(ele.className?" ":"")+className;};Spry.Utils.removeClassName=function(ele,className)
+{ele=Spry.$(ele);if(!ele||!className||(ele.className&&ele.className.search(new RegExp("\\b"+className+"\\b"))==-1))
+return;ele.className=ele.className.replace(new RegExp("\\s*\\b"+className+"\\b","g"),"");};Spry.Utils.getObjectByName=function(name)
+{var result=null;if(name)
+{var lu=window;var objPath=name.split(".");for(var i=0;lu&&i<objPath.length;i++)
+{result=lu[objPath[i]];lu=result;}}
+return result;};Spry.$=function(element)
+{if(arguments.length>1)
+{for(var i=0,elements=[],length=arguments.length;i<length;i++)
+elements.push(Spry.$(arguments[i]));return elements;}
+if(typeof element=='string')
+element=document.getElementById(element);return element;};}
+Spry.Utils.eval=function(str)
+{return eval(str);};Spry.Utils.escapeQuotesAndLineBreaks=function(str)
+{if(str)
+{str=str.replace(/\\/g,"\\\\");str=str.replace(/["']/g,"\\$&");str=str.replace(/\n/g,"\\n");str=str.replace(/\r/g,"\\r");}
+return str;};Spry.Utils.encodeEntities=function(str)
+{if(str&&str.search(/[&<>"]/)!=-1)
+{str=str.replace(/&/g,"&amp;");str=str.replace(/</g,"&lt;");str=str.replace(/>/g,"&gt;");str=str.replace(/"/g,"&quot;");}
+return str};Spry.Utils.decodeEntities=function(str)
+{var d=Spry.Utils.decodeEntities.div;if(!d)
+{d=document.createElement('div');Spry.Utils.decodeEntities.div=d;if(!d)return str;}
+d.innerHTML=str;if(d.childNodes.length==1&&d.firstChild.nodeType==3&&d.firstChild.nextSibling==null)
+str=d.firstChild.data;else
+{str=str.replace(/&lt;/gi,"<");str=str.replace(/&gt;/gi,">");str=str.replace(/&quot;/gi,"\"");str=str.replace(/&amp;/gi,"&");}
+return str;};Spry.Utils.fixupIETagAttributes=function(inStr)
+{var outStr="";var tagStart=inStr.match(/^<[^\s>]+\s*/)[0];var tagEnd=inStr.match(/\s*\/?>$/)[0];var tagAttrs=inStr.replace(/^<[^\s>]+\s*|\s*\/?>/g,"");outStr+=tagStart;if(tagAttrs)
+{var startIndex=0;var endIndex=0;while(startIndex<tagAttrs.length)
+{while(tagAttrs.charAt(endIndex)!='='&&endIndex<tagAttrs.length)
+++endIndex;if(endIndex>=tagAttrs.length)
+{outStr+=tagAttrs.substring(startIndex,endIndex);break;}
+++endIndex;outStr+=tagAttrs.substring(startIndex,endIndex);startIndex=endIndex;if(tagAttrs.charAt(endIndex)=='"'||tagAttrs.charAt(endIndex)=="'")
+{var savedIndex=endIndex++;while(endIndex<tagAttrs.length)
+{if(tagAttrs.charAt(endIndex)==tagAttrs.charAt(savedIndex))
+{endIndex++;break;}
+else if(tagAttrs.charAt(endIndex)=="\\")
+endIndex++;endIndex++;}
+outStr+=tagAttrs.substring(startIndex,endIndex);startIndex=endIndex;}
+else
+{outStr+="\"";var sIndex=tagAttrs.slice(endIndex).search(/\s/);endIndex=(sIndex!=-1)?(endIndex+sIndex):tagAttrs.length;outStr+=tagAttrs.slice(startIndex,endIndex);outStr+="\"";startIndex=endIndex;}}}
+outStr+=tagEnd;return outStr;};Spry.Utils.fixUpIEInnerHTML=function(inStr)
+{var outStr="";var regexp=new RegExp("<\\!--|<\\!\\[CDATA\\[|<\\w+[^<>]*>|-->|\\]\\](>|\&gt;)","g");var searchStartIndex=0;var skipFixUp=0;while(inStr.length)
+{var results=regexp.exec(inStr);if(!results||!results[0])
+{outStr+=inStr.substr(searchStartIndex,inStr.length-searchStartIndex);break;}
+if(results.index!=searchStartIndex)
+{outStr+=inStr.substr(searchStartIndex,results.index-searchStartIndex);}
+if(results[0]=="<!--"||results[0]=="<![CDATA[")
+{++skipFixUp;outStr+=results[0];}
+else if(results[0]=="-->"||results[0]=="]]>"||(skipFixUp&&results[0]=="]]&gt;"))
+{--skipFixUp;outStr+=results[0];}
+else if(!skipFixUp&&results[0].charAt(0)=='<')
+outStr+=Spry.Utils.fixupIETagAttributes(results[0]);else
+outStr+=results[0];searchStartIndex=regexp.lastIndex;}
+return outStr;};Spry.Utils.stringToXMLDoc=function(str)
+{var xmlDoc=null;try
+{var xmlDOMObj=new ActiveXObject("Microsoft.XMLDOM");xmlDOMObj.async=false;xmlDOMObj.loadXML(str);xmlDoc=xmlDOMObj;}
+catch(e)
+{try
+{var domParser=new DOMParser;xmlDoc=domParser.parseFromString(str,'text/xml');}
+catch(e)
+{Spry.Debug.reportError("Caught exception in Spry.Utils.stringToXMLDoc(): "+e+"\n");xmlDoc=null;}}
+return xmlDoc;};Spry.Utils.serializeObject=function(obj)
+{var str="";var firstItem=true;if(obj==null||obj==undefined)
+return str+obj;var objType=typeof obj;if(objType=="number"||objType=="boolean")
+str+=obj;else if(objType=="string")
+str+="\""+Spry.Utils.escapeQuotesAndLineBreaks(obj)+"\"";else if(obj.constructor==Array)
+{str+="[";for(var i=0;i<obj.length;i++)
+{if(!firstItem)
+str+=", ";str+=Spry.Utils.serializeObject(obj[i]);firstItem=false;}
+str+="]";}
+else if(objType=="object")
+{str+="{";for(var p in obj)
+{if(!firstItem)
+str+=", ";str+="\""+p+"\": "+Spry.Utils.serializeObject(obj[p]);firstItem=false;}
+str+="}";}
+return str;};Spry.Utils.getNodesByFunc=function(root,func)
+{var nodeStack=new Array;var resultArr=new Array;var node=root;while(node)
+{if(func(node))
+resultArr.push(node);if(node.hasChildNodes())
+{nodeStack.push(node);node=node.firstChild;}
+else
+{if(node==root)
+node=null;else
+try{node=node.nextSibling;}catch(e){node=null;};}
+while(!node&&nodeStack.length>0)
+{node=nodeStack.pop();if(node==root)
+node=null;else
+try{node=node.nextSibling;}catch(e){node=null;}}}
+if(nodeStack&&nodeStack.length>0)
+Spry.Debug.trace("-- WARNING: Spry.Utils.getNodesByFunc() failed to traverse all nodes!\n");return resultArr;};Spry.Utils.getFirstChildWithNodeName=function(node,nodeName)
+{var child=node.firstChild;while(child)
+{if(child.nodeName==nodeName)
+return child;child=child.nextSibling;}
+return null;};Spry.Utils.setOptions=function(obj,optionsObj,ignoreUndefinedProps)
+{if(!optionsObj)
+return;for(var optionName in optionsObj)
+{if(ignoreUndefinedProps&&optionsObj[optionName]==undefined)
+continue;obj[optionName]=optionsObj[optionName];}};Spry.Utils.SelectionManager={};Spry.Utils.SelectionManager.selectionGroups=new Object;Spry.Utils.SelectionManager.SelectionGroup=function()
+{this.selectedElements=new Array;};Spry.Utils.SelectionManager.SelectionGroup.prototype.select=function(element,className,multiSelect)
+{var selObj=null;if(!multiSelect)
+{this.clearSelection();}
+else
+{for(var i=0;i<this.selectedElements.length;i++)
+{selObj=this.selectedElements[i].element;if(selObj.element==element)
+{if(selObj.className!=className)
+{Spry.Utils.removeClassName(element,selObj.className);Spry.Utils.addClassName(element,className);}
+return;}}}
+selObj=new Object;selObj.element=element;selObj.className=className;this.selectedElements.push(selObj);Spry.Utils.addClassName(element,className);};Spry.Utils.SelectionManager.SelectionGroup.prototype.unSelect=function(element)
+{for(var i=0;i<this.selectedElements.length;i++)
+{var selObj=this.selectedElements[i].element;if(selObj.element==element)
+{Spry.Utils.removeClassName(selObj.element,selObj.className);return;}}};Spry.Utils.SelectionManager.SelectionGroup.prototype.clearSelection=function()
+{var selObj=null;do
+{selObj=this.selectedElements.shift();if(selObj)
+Spry.Utils.removeClassName(selObj.element,selObj.className);}
+while(selObj);};Spry.Utils.SelectionManager.getSelectionGroup=function(selectionGroupName)
+{if(!selectionGroupName)
+return null;var groupObj=Spry.Utils.SelectionManager.selectionGroups[selectionGroupName];if(!groupObj)
+{groupObj=new Spry.Utils.SelectionManager.SelectionGroup();Spry.Utils.SelectionManager.selectionGroups[selectionGroupName]=groupObj;}
+return groupObj;};Spry.Utils.SelectionManager.select=function(selectionGroupName,element,className,multiSelect)
+{var groupObj=Spry.Utils.SelectionManager.getSelectionGroup(selectionGroupName);if(!groupObj)
+return;groupObj.select(element,className,multiSelect);};Spry.Utils.SelectionManager.unSelect=function(selectionGroupName,element)
+{var groupObj=Spry.Utils.SelectionManager.getSelectionGroup(selectionGroupName);if(!groupObj)
+return;groupObj.unSelect(element,className);};Spry.Utils.SelectionManager.clearSelection=function(selectionGroupName)
+{var groupObj=Spry.Utils.SelectionManager.getSelectionGroup(selectionGroupName);if(!groupObj)
+return;groupObj.clearSelection();};Spry.Utils.Notifier=function()
+{this.observers=[];this.suppressNotifications=0;};Spry.Utils.Notifier.prototype.addObserver=function(observer)
+{if(!observer)
+return;var len=this.observers.length;for(var i=0;i<len;i++)
+{if(this.observers[i]==observer)
+return;}
+this.observers[len]=observer;};Spry.Utils.Notifier.prototype.removeObserver=function(observer)
+{if(!observer)
+return;for(var i=0;i<this.observers.length;i++)
+{if(this.observers[i]==observer)
+{this.observers.splice(i,1);break;}}};Spry.Utils.Notifier.prototype.notifyObservers=function(methodName,data)
+{if(!methodName)
+return;if(!this.suppressNotifications)
+{var len=this.observers.length;for(var i=0;i<len;i++)
+{var obs=this.observers[i];if(obs)
+{if(typeof obs=="function")
+obs(methodName,this,data);else if(obs[methodName])
+obs[methodName](this,data);}}}};Spry.Utils.Notifier.prototype.enableNotifications=function()
+{if(--this.suppressNotifications<0)
+{this.suppressNotifications=0;Spry.Debug.reportError("Unbalanced enableNotifications() call!\n");}};Spry.Utils.Notifier.prototype.disableNotifications=function()
+{++this.suppressNotifications;};Spry.Debug={};Spry.Debug.enableTrace=true;Spry.Debug.debugWindow=null;Spry.Debug.onloadDidFire=false;Spry.Utils.addLoadListener(function(){Spry.Debug.onloadDidFire=true;Spry.Debug.flushQueuedMessages();});Spry.Debug.flushQueuedMessages=function()
+{if(Spry.Debug.flushQueuedMessages.msgs)
+{var msgs=Spry.Debug.flushQueuedMessages.msgs;for(var i=0;i<msgs.length;i++)
+Spry.Debug.debugOut(msgs[i].msg,msgs[i].color);Spry.Debug.flushQueuedMessages.msgs=null;}};Spry.Debug.createDebugWindow=function()
+{if(!Spry.Debug.enableTrace||Spry.Debug.debugWindow||!Spry.Debug.onloadDidFire)
+return;try
+{Spry.Debug.debugWindow=document.createElement("div");var div=Spry.Debug.debugWindow;div.style.fontSize="12px";div.style.fontFamily="console";div.style.position="absolute";div.style.width="400px";div.style.height="300px";div.style.overflow="auto";div.style.border="solid 1px black";div.style.backgroundColor="white";div.style.color="black";div.style.bottom="0px";div.style.right="0px";div.setAttribute("id","SpryDebugWindow");document.body.appendChild(Spry.Debug.debugWindow);}
+catch(e){}};Spry.Debug.debugOut=function(str,bgColor)
+{if(!Spry.Debug.debugWindow)
+{Spry.Debug.createDebugWindow();if(!Spry.Debug.debugWindow)
+{if(!Spry.Debug.flushQueuedMessages.msgs)
+Spry.Debug.flushQueuedMessages.msgs=new Array;Spry.Debug.flushQueuedMessages.msgs.push({msg:str,color:bgColor});return;}}
+var d=document.createElement("div");if(bgColor)
+d.style.backgroundColor=bgColor;d.innerHTML=str;Spry.Debug.debugWindow.appendChild(d);};Spry.Debug.trace=function(str)
+{Spry.Debug.debugOut(str);};Spry.Debug.reportError=function(str)
+{Spry.Debug.debugOut(str,"red");};Spry.Data={};Spry.Data.regionsArray={};Spry.Data.initRegionsOnLoad=true;Spry.Data.initRegions=function(rootNode)
+{rootNode=rootNode?Spry.$(rootNode):document.body;var lastRegionFound=null;var regions=Spry.Utils.getNodesByFunc(rootNode,function(node)
+{try
+{if(node.nodeType!=1)
+return false;var attrName="spry:region";var attr=node.attributes.getNamedItem(attrName);if(!attr)
+{attrName="spry:detailregion";attr=node.attributes.getNamedItem(attrName);}
+if(attr)
+{if(lastRegionFound)
+{var parent=node.parentNode;while(parent)
+{if(parent==lastRegionFound)
+{Spry.Debug.reportError("Found a nested "+attrName+" in the following markup. Nested regions are currently not supported.<br/><pre>"+Spry.Utils.encodeEntities(parent.innerHTML)+"</pre>");return false;}
+parent=parent.parentNode;}}
+if(attr.value)
+{attr=node.attributes.getNamedItem("id");if(!attr||!attr.value)
+{node.setAttribute("id","spryregion"+(++Spry.Data.initRegions.nextUniqueRegionID));}
+lastRegionFound=node;return true;}
+else
+Spry.Debug.reportError(attrName+" attributes require one or more data set names as values!");}}
+catch(e){}
+return false;});var name,dataSets,i;var newRegions=[];for(i=0;i<regions.length;i++)
+{var rgn=regions[i];var isDetailRegion=false;name=rgn.attributes.getNamedItem("id").value;attr=rgn.attributes.getNamedItem("spry:region");if(!attr)
+{attr=rgn.attributes.getNamedItem("spry:detailregion");isDetailRegion=true;}
+if(!attr.value)
+{Spry.Debug.reportError("spry:region and spry:detailregion attributes require one or more data set names as values!");continue;}
+rgn.attributes.removeNamedItem(attr.nodeName);Spry.Utils.removeClassName(rgn,Spry.Data.Region.hiddenRegionClassName);dataSets=Spry.Data.Region.strToDataSetsArray(attr.value);if(!dataSets.length)
+{Spry.Debug.reportError("spry:region or spry:detailregion attribute has no data set!");continue;}
+var hasBehaviorAttributes=false;var hasSpryContent=false;var dataStr="";var parent=null;var regionStates={};var regionStateMap={};attr=rgn.attributes.getNamedItem("spry:readystate");if(attr&&attr.value)
+regionStateMap["ready"]=attr.value;attr=rgn.attributes.getNamedItem("spry:errorstate");if(attr&&attr.value)
+regionStateMap["error"]=attr.value;attr=rgn.attributes.getNamedItem("spry:loadingstate");if(attr&&attr.value)
+regionStateMap["loading"]=attr.value;attr=rgn.attributes.getNamedItem("spry:expiredstate");if(attr&&attr.value)
+regionStateMap["expired"]=attr.value;var piRegions=Spry.Utils.getNodesByFunc(rgn,function(node)
+{try
+{if(node.nodeType==1)
+{var attributes=node.attributes;var numPI=Spry.Data.Region.PI.orderedInstructions.length;var lastStartComment=null;var lastEndComment=null;for(var i=0;i<numPI;i++)
+{var piName=Spry.Data.Region.PI.orderedInstructions[i];var attr=attributes.getNamedItem(piName);if(!attr)
+continue;var piDesc=Spry.Data.Region.PI.instructions[piName];var childrenOnly=(node==rgn)?true:piDesc.childrenOnly;var openTag=piDesc.getOpenTag(node,piName);var closeTag=piDesc.getCloseTag(node,piName);if(childrenOnly)
+{var oComment=document.createComment(openTag);var cComment=document.createComment(closeTag);if(!lastStartComment)
+node.insertBefore(oComment,node.firstChild);else
+node.insertBefore(oComment,lastStartComment.nextSibling);lastStartComment=oComment;if(!lastEndComment)
+node.appendChild(cComment);else
+node.insertBefore(cComment,lastEndComment);lastEndComment=cComment;}
+else
+{var parent=node.parentNode;parent.insertBefore(document.createComment(openTag),node);parent.insertBefore(document.createComment(closeTag),node.nextSibling);}
+if(piName=="spry:state")
+regionStates[attr.value]=true;node.removeAttribute(piName);}
+if(Spry.Data.Region.enableBehaviorAttributes)
+{var bAttrs=Spry.Data.Region.behaviorAttrs;for(var behaviorAttrName in bAttrs)
+{var bAttr=attributes.getNamedItem(behaviorAttrName);if(bAttr)
+{hasBehaviorAttributes=true;if(bAttrs[behaviorAttrName].setup)
+bAttrs[behaviorAttrName].setup(node,bAttr.value);}}}}}
+catch(e){}
+return false;});dataStr=rgn.innerHTML;if(window.ActiveXObject&&!Spry.Data.Region.disableIEInnerHTMLFixUp&&dataStr.search(/=\{/)!=-1)
+{if(Spry.Data.Region.debug)
+Spry.Debug.trace("<hr />Performing IE innerHTML fix up of Region: "+name+"<br /><br />"+Spry.Utils.encodeEntities(dataStr));dataStr=Spry.Utils.fixUpIEInnerHTML(dataStr);}
+if(Spry.Data.Region.debug)
+Spry.Debug.trace("<hr />Region template markup for '"+name+"':<br /><br />"+Spry.Utils.encodeEntities(dataStr));if(!hasSpryContent)
+{rgn.innerHTML="";}
+var region=new Spry.Data.Region(rgn,name,isDetailRegion,dataStr,dataSets,regionStates,regionStateMap,hasBehaviorAttributes);Spry.Data.regionsArray[region.name]=region;newRegions.push(region);}
+for(var i=0;i<newRegions.length;i++)
+newRegions[i].updateContent();};Spry.Data.initRegions.nextUniqueRegionID=0;Spry.Data.updateRegion=function(regionName)
+{if(!regionName||!Spry.Data.regionsArray||!Spry.Data.regionsArray[regionName])
+return;try{Spry.Data.regionsArray[regionName].updateContent();}
+catch(e){Spry.Debug.reportError("Spry.Data.updateRegion("+regionName+") caught an exception: "+e+"\n");}};Spry.Data.getRegion=function(regionName)
+{return Spry.Data.regionsArray[regionName];};Spry.Data.updateAllRegions=function()
+{if(!Spry.Data.regionsArray)
+return;for(var regionName in Spry.Data.regionsArray)
+Spry.Data.updateRegion(regionName);};Spry.Data.getDataSetByName=function(dataSetName)
+{var ds=window[dataSetName];if(typeof ds!="object"||!ds.getData||!ds.filter)
+return null;return ds;};Spry.Data.DataSet=function(options)
+{Spry.Utils.Notifier.call(this);this.name="";this.internalID=Spry.Data.DataSet.nextDataSetID++;this.curRowID=0;this.data=[];this.unfilteredData=null;this.dataHash={};this.columnTypes={};this.filterFunc=null;this.filterDataFunc=null;this.distinctOnLoad=false;this.distinctFieldsOnLoad=null;this.sortOnLoad=null;this.sortOrderOnLoad="ascending";this.keepSorted=false;this.dataWasLoaded=false;this.pendingRequest=null;this.lastSortColumns=[];this.lastSortOrder="";this.loadIntervalID=0;Spry.Utils.setOptions(this,options);};Spry.Data.DataSet.prototype=new Spry.Utils.Notifier();Spry.Data.DataSet.prototype.constructor=Spry.Data.DataSet;Spry.Data.DataSet.prototype.getData=function(unfiltered)
+{return(unfiltered&&this.unfilteredData)?this.unfilteredData:this.data;};Spry.Data.DataSet.prototype.getUnfilteredData=function()
+{return this.getData(true);};Spry.Data.DataSet.prototype.getLoadDataRequestIsPending=function()
+{return this.pendingRequest!=null;};Spry.Data.DataSet.prototype.getDataWasLoaded=function()
+{return this.dataWasLoaded;};Spry.Data.DataSet.prototype.getValue=function(valueName,rowContext)
+{var result=undefined;if(!rowContext)
+rowContext=this.getCurrentRow();switch(valueName)
+{case"ds_RowNumber":result=this.getRowNumber(rowContext);break;case"ds_RowNumberPlus1":result=this.getRowNumber(rowContext)+1;break;case"ds_RowCount":result=this.getRowCount();break;case"ds_UnfilteredRowCount":result=this.getRowCount(true);break;case"ds_CurrentRowNumber":result=this.getCurrentRowNumber();break;case"ds_CurrentRowID":result=this.getCurrentRowID();break;case"ds_EvenOddRow":result=(this.getRowNumber(rowContext)%2)?Spry.Data.Region.evenRowClassName:Spry.Data.Region.oddRowClassName;break;case"ds_SortOrder":result=this.getSortOrder();break;case"ds_SortColumn":result=this.getSortColumn();break;default:if(rowContext)
+result=rowContext[valueName];break;}
+return result;};Spry.Data.DataSet.prototype.setDataFromArray=function(arr,fireSyncLoad)
+{this.notifyObservers("onPreLoad");this.unfilteredData=null;this.filteredData=null;this.data=[];this.dataHash={};var arrLen=arr.length;for(var i=0;i<arrLen;i++)
+{var row=arr[i];if(row.ds_RowID==undefined)
+row.ds_RowID=i;this.dataHash[row.ds_RowID]=row;this.data.push(row);}
+this.loadData(fireSyncLoad);};Spry.Data.DataSet.prototype.loadData=function(syncLoad)
+{var self=this;this.pendingRequest=new Object;this.dataWasLoaded=false;var loadCallbackFunc=function()
+{self.pendingRequest=null;self.dataWasLoaded=true;self.applyColumnTypes();self.disableNotifications();self.filterAndSortData();self.enableNotifications();self.notifyObservers("onPostLoad");self.notifyObservers("onDataChanged");};if(syncLoad)
+loadCallbackFunc();else
+this.pendingRequest.timer=setTimeout(loadCallbackFunc,0);};Spry.Data.DataSet.prototype.filterAndSortData=function()
+{if(this.filterDataFunc)
+this.filterData(this.filterDataFunc,true);if(this.distinctOnLoad)
+this.distinct(this.distinctFieldsOnLoad);if(this.keepSorted&&this.getSortColumn())
+this.sort(this.lastSortColumns,this.lastSortOrder);else if(this.sortOnLoad)
+this.sort(this.sortOnLoad,this.sortOrderOnLoad);if(this.filterFunc)
+this.filter(this.filterFunc,true);if(this.data&&this.data.length>0)
+this.curRowID=this.data[0]['ds_RowID'];else
+this.curRowID=0;};Spry.Data.DataSet.prototype.cancelLoadData=function()
+{if(this.pendingRequest&&this.pendingRequest.timer)
+clearTimeout(this.pendingRequest.timer);this.pendingRequest=null;};Spry.Data.DataSet.prototype.getRowCount=function(unfiltered)
+{var rows=this.getData(unfiltered);return rows?rows.length:0;};Spry.Data.DataSet.prototype.getRowByID=function(rowID)
+{if(!this.data)
+return null;return this.dataHash[rowID];};Spry.Data.DataSet.prototype.getRowByRowNumber=function(rowNumber,unfiltered)
+{var rows=this.getData(unfiltered);if(rows&&rowNumber>=0&&rowNumber<rows.length)
+return rows[rowNumber];return null;};Spry.Data.DataSet.prototype.getCurrentRow=function()
+{return this.getRowByID(this.curRowID);};Spry.Data.DataSet.prototype.setCurrentRow=function(rowID)
+{if(this.curRowID==rowID)
+return;var nData={oldRowID:this.curRowID,newRowID:rowID};this.curRowID=rowID;this.notifyObservers("onCurrentRowChanged",nData);};Spry.Data.DataSet.prototype.getRowNumber=function(row,unfiltered)
+{if(row)
+{var rows=this.getData(unfiltered);if(rows&&rows.length)
+{var numRows=rows.length;for(var i=0;i<numRows;i++)
+{if(rows[i]==row)
+return i;}}}
+return-1;};Spry.Data.DataSet.prototype.getCurrentRowNumber=function()
+{return this.getRowNumber(this.getCurrentRow());};Spry.Data.DataSet.prototype.getCurrentRowID=function()
+{return this.curRowID;};Spry.Data.DataSet.prototype.setCurrentRowNumber=function(rowNumber)
+{if(!this.data||rowNumber>=this.data.length)
+{Spry.Debug.trace("Invalid row number: "+rowNumber+"\n");return;}
+var rowID=this.data[rowNumber]["ds_RowID"];if(rowID==undefined||this.curRowID==rowID)
+return;this.setCurrentRow(rowID);};Spry.Data.DataSet.prototype.findRowsWithColumnValues=function(valueObj,firstMatchOnly,unfiltered)
+{var results=[];var rows=this.getData(unfiltered);if(rows)
+{var numRows=rows.length;for(var i=0;i<numRows;i++)
+{var row=rows[i];var matched=true;for(var colName in valueObj)
+{if(valueObj[colName]!=row[colName])
+{matched=false;break;}}
+if(matched)
+{if(firstMatchOnly)
+return row;results.push(row);}}}
+return firstMatchOnly?null:results;};Spry.Data.DataSet.prototype.setColumnType=function(columnNames,columnType)
+{if(columnNames)
+{if(typeof columnNames=="string")
+columnNames=[columnNames];for(var i=0;i<columnNames.length;i++)
+this.columnTypes[columnNames[i]]=columnType;}};Spry.Data.DataSet.prototype.getColumnType=function(columnName)
+{if(this.columnTypes[columnName])
+return this.columnTypes[columnName];return"string";};Spry.Data.DataSet.prototype.applyColumnTypes=function()
+{var rows=this.getData(true);var numRows=rows.length;var colNames=[];if(numRows<1)
+return;for(var cname in this.columnTypes)
+{var ctype=this.columnTypes[cname];if(ctype!="string")
+{for(var i=0;i<numRows;i++)
+{var row=rows[i];var val=row[cname];if(val!=undefined)
+{if(ctype=="number")
+row[cname]=new Number(val);else if(ctype=="html")
+row[cname]=Spry.Utils.decodeEntities(val);}}}}};Spry.Data.DataSet.prototype.distinct=function(columnNames)
+{if(this.data)
+{var oldData=this.data;this.data=[];this.dataHash={};var dataChanged=false;var alreadySeenHash={};var i=0;var keys=[];if(typeof columnNames=="string")
+keys=[columnNames];else if(columnNames)
+keys=columnNames;else
+for(var recField in oldData[0])
+keys[i++]=recField;for(var i=0;i<oldData.length;i++)
+{var rec=oldData[i];var hashStr="";for(var j=0;j<keys.length;j++)
+{recField=keys[j];if(recField!="ds_RowID")
+{if(hashStr)
+hashStr+=",";hashStr+=recField+":"+"\""+rec[recField]+"\"";}}
+if(!alreadySeenHash[hashStr])
+{this.data.push(rec);this.dataHash[rec['ds_RowID']]=rec;alreadySeenHash[hashStr]=true;}
+else
+dataChanged=true;}
+if(dataChanged)
+this.notifyObservers('onDataChanged');}};Spry.Data.DataSet.prototype.getSortColumn=function(){return(this.lastSortColumns&&this.lastSortColumns.length>0)?this.lastSortColumns[0]:"";};Spry.Data.DataSet.prototype.getSortOrder=function(){return this.lastSortOrder?this.lastSortOrder:"";};Spry.Data.DataSet.prototype.sort=function(columnNames,sortOrder)
+{if(!columnNames)
+return;if(typeof columnNames=="string")
+columnNames=[columnNames,"ds_RowID"];else if(columnNames.length<2&&columnNames[0]!="ds_RowID")
+columnNames.push("ds_RowID");if(!sortOrder)
+sortOrder="toggle";if(sortOrder=="toggle")
+{if(this.lastSortColumns.length>0&&this.lastSortColumns[0]==columnNames[0]&&this.lastSortOrder=="ascending")
+sortOrder="descending";else
+sortOrder="ascending";}
+if(sortOrder!="ascending"&&sortOrder!="descending")
+{Spry.Debug.reportError("Invalid sort order type specified: "+sortOrder+"\n");return;}
+var nData={oldSortColumns:this.lastSortColumns,oldSortOrder:this.lastSortOrder,newSortColumns:columnNames,newSortOrder:sortOrder};this.notifyObservers("onPreSort",nData);var cname=columnNames[columnNames.length-1];var sortfunc=Spry.Data.DataSet.prototype.sort.getSortFunc(cname,this.getColumnType(cname),sortOrder);for(var i=columnNames.length-2;i>=0;i--)
+{cname=columnNames[i];sortfunc=Spry.Data.DataSet.prototype.sort.buildSecondarySortFunc(Spry.Data.DataSet.prototype.sort.getSortFunc(cname,this.getColumnType(cname),sortOrder),sortfunc);}
+if(this.unfilteredData)
+{this.unfilteredData.sort(sortfunc);if(this.filterFunc)
+this.filter(this.filterFunc,true);}
+else
+this.data.sort(sortfunc);this.lastSortColumns=columnNames.slice(0);this.lastSortOrder=sortOrder;this.notifyObservers("onPostSort",nData);};Spry.Data.DataSet.prototype.sort.getSortFunc=function(prop,type,order)
+{var sortfunc=null;if(type=="number")
+{if(order=="ascending")
+sortfunc=function(a,b)
+{a=a[prop];b=b[prop];if(a==undefined||b==undefined)
+return(a==b)?0:(a?1:-1);return a-b;};else
+sortfunc=function(a,b)
+{a=a[prop];b=b[prop];if(a==undefined||b==undefined)
+return(a==b)?0:(a?-1:1);return b-a;};}
+else if(type=="date")
+{if(order=="ascending")
+sortfunc=function(a,b)
+{var dA=a[prop];var dB=b[prop];dA=dA?(new Date(dA)):0;dB=dB?(new Date(dB)):0;return dA-dB;};else
+sortfunc=function(a,b)
+{var dA=a[prop];var dB=b[prop];dA=dA?(new Date(dA)):0;dB=dB?(new Date(dB)):0;return dB-dA;};}
+else
+{if(order=="ascending")
+sortfunc=function(a,b){a=a[prop];b=b[prop];if(a==undefined||b==undefined)
+return(a==b)?0:(a?1:-1);var tA=a.toString();var tB=b.toString();var tA_l=tA.toLowerCase();var tB_l=tB.toLowerCase();var min_len=tA.length>tB.length?tB.length:tA.length;for(var i=0;i<min_len;i++)
+{var a_l_c=tA_l.charAt(i);var b_l_c=tB_l.charAt(i);var a_c=tA.charAt(i);var b_c=tB.charAt(i);if(a_l_c>b_l_c)
+return 1;else if(a_l_c<b_l_c)
+return-1;else if(a_c>b_c)
+return 1;else if(a_c<b_c)
+return-1;}
+if(tA.length==tB.length)
+return 0;else if(tA.length>tB.length)
+return 1;return-1;};else
+sortfunc=function(a,b){a=a[prop];b=b[prop];if(a==undefined||b==undefined)
+return(a==b)?0:(a?-1:1);var tA=a.toString();var tB=b.toString();var tA_l=tA.toLowerCase();var tB_l=tB.toLowerCase();var min_len=tA.length>tB.length?tB.length:tA.length;for(var i=0;i<min_len;i++)
+{var a_l_c=tA_l.charAt(i);var b_l_c=tB_l.charAt(i);var a_c=tA.charAt(i);var b_c=tB.charAt(i);if(a_l_c>b_l_c)
+return-1;else if(a_l_c<b_l_c)
+return 1;else if(a_c>b_c)
+return-1;else if(a_c<b_c)
+return 1;}
+if(tA.length==tB.length)
+return 0;else if(tA.length>tB.length)
+return-1;return 1;};}
+return sortfunc;};Spry.Data.DataSet.prototype.sort.buildSecondarySortFunc=function(funcA,funcB)
+{return function(a,b)
+{var ret=funcA(a,b);if(ret==0)
+ret=funcB(a,b);return ret;};};Spry.Data.DataSet.prototype.filterData=function(filterFunc,filterOnly)
+{var dataChanged=false;if(!filterFunc)
+{this.filterDataFunc=null;dataChanged=true;}
+else
+{this.filterDataFunc=filterFunc;if(this.dataWasLoaded&&((this.unfilteredData&&this.unfilteredData.length)||(this.data&&this.data.length)))
+{if(this.unfilteredData)
+{this.data=this.unfilteredData;this.unfilteredData=null;}
+var oldData=this.data;this.data=[];this.dataHash={};for(var i=0;i<oldData.length;i++)
+{var newRow=filterFunc(this,oldData[i],i);if(newRow)
+{this.data.push(newRow);this.dataHash[newRow["ds_RowID"]]=newRow;}}
+dataChanged=true;}}
+if(dataChanged)
+{if(!filterOnly)
+{this.disableNotifications();if(this.filterFunc)
+this.filter(this.filterFunc,true);this.enableNotifications();}
+this.notifyObservers("onDataChanged");}};Spry.Data.DataSet.prototype.filter=function(filterFunc,filterOnly)
+{var dataChanged=false;if(!filterFunc)
+{if(this.filterFunc&&this.unfilteredData)
+{this.data=this.unfilteredData;this.unfilteredData=null;this.filterFunc=null;dataChanged=true;}}
+else
+{this.filterFunc=filterFunc;if(this.dataWasLoaded&&(this.unfilteredData||(this.data&&this.data.length)))
+{if(!this.unfilteredData)
+this.unfilteredData=this.data;var udata=this.unfilteredData;this.data=[];for(var i=0;i<udata.length;i++)
+{var newRow=filterFunc(this,udata[i],i);if(newRow)
+this.data.push(newRow);}
+dataChanged=true;}}
+if(dataChanged)
+this.notifyObservers("onDataChanged");};Spry.Data.DataSet.prototype.startLoadInterval=function(interval)
+{this.stopLoadInterval();if(interval>0)
+{var self=this;this.loadInterval=interval;this.loadIntervalID=setInterval(function(){self.loadData();},interval);}};Spry.Data.DataSet.prototype.stopLoadInterval=function()
+{if(this.loadIntervalID)
+clearInterval(this.loadIntervalID);this.loadInterval=0;this.loadIntervalID=null;};Spry.Data.DataSet.nextDataSetID=0;Spry.Data.HTTPSourceDataSet=function(dataSetURL,dataSetOptions)
+{Spry.Data.DataSet.call(this);this.url=dataSetURL;this.dataSetsForDataRefStrings=new Array;this.hasDataRefStrings=false;this.useCache=true;this.setRequestInfo(dataSetOptions,true);Spry.Utils.setOptions(this,dataSetOptions,true);this.recalculateDataSetDependencies();if(this.loadInterval>0)
+this.startLoadInterval(this.loadInterval);};Spry.Data.HTTPSourceDataSet.prototype=new Spry.Data.DataSet();Spry.Data.HTTPSourceDataSet.prototype.constructor=Spry.Data.HTTPSourceDataSet;Spry.Data.HTTPSourceDataSet.prototype.setRequestInfo=function(requestInfo,undefineRequestProps)
+{this.requestInfo=new Spry.Utils.loadURL.Request();this.requestInfo.extractRequestOptions(requestInfo,undefineRequestProps);if(this.requestInfo.method=="POST")
+{if(!this.requestInfo.headers)
+this.requestInfo.headers={};if(!this.requestInfo.headers['Content-Type'])
+this.requestInfo.headers['Content-Type']="application/x-www-form-urlencoded; charset=UTF-8";}};Spry.Data.HTTPSourceDataSet.prototype.recalculateDataSetDependencies=function()
+{this.hasDataRefStrings=false;var i=0;for(i=0;i<this.dataSetsForDataRefStrings.length;i++)
+{var ds=this.dataSetsForDataRefStrings[i];if(ds)
+ds.removeObserver(this);}
+this.dataSetsForDataRefStrings=new Array();var regionStrs=this.getDataRefStrings();var dsCount=0;for(var n=0;n<regionStrs.length;n++)
+{var tokens=Spry.Data.Region.getTokensFromStr(regionStrs[n]);for(i=0;tokens&&i<tokens.length;i++)
+{if(tokens[i].search(/{[^}:]+::[^}]+}/)!=-1)
+{var dsName=tokens[i].replace(/^\{|::.*\}/g,"");var ds=null;if(!this.dataSetsForDataRefStrings[dsName])
+{ds=Spry.Data.getDataSetByName(dsName);if(dsName&&ds)
+{this.dataSetsForDataRefStrings[dsName]=ds;this.dataSetsForDataRefStrings[dsCount++]=ds;this.hasDataRefStrings=true;}}}}}
+for(i=0;i<this.dataSetsForDataRefStrings.length;i++)
+{var ds=this.dataSetsForDataRefStrings[i];ds.addObserver(this);}};Spry.Data.HTTPSourceDataSet.prototype.getDataRefStrings=function()
+{var strArr=[];if(this.url)strArr.push(this.url);if(this.requestInfo&&this.requestInfo.postData)strArr.push(this.requestInfo.postData);return strArr;};Spry.Data.HTTPSourceDataSet.prototype.attemptLoadData=function()
+{for(var i=0;i<this.dataSetsForDataRefStrings.length;i++)
+{var ds=this.dataSetsForDataRefStrings[i];if(ds.getLoadDataRequestIsPending()||!ds.getDataWasLoaded())
+return;}
+this.loadData();};Spry.Data.HTTPSourceDataSet.prototype.onCurrentRowChanged=function(ds,data)
+{this.attemptLoadData();};Spry.Data.HTTPSourceDataSet.prototype.onPostSort=function(ds,data)
+{this.attemptLoadData();};Spry.Data.HTTPSourceDataSet.prototype.onDataChanged=function(ds,data)
+{this.attemptLoadData();};Spry.Data.HTTPSourceDataSet.prototype.loadData=function()
+{if(!this.url)
+return;this.cancelLoadData();var url=this.url;var postData=this.requestInfo.postData;if(this.hasDataRefStrings)
+{var allDataSetsReady=true;for(var i=0;i<this.dataSetsForDataRefStrings.length;i++)
+{var ds=this.dataSetsForDataRefStrings[i];if(ds.getLoadDataRequestIsPending())
+allDataSetsReady=false;else if(!ds.getDataWasLoaded())
+{ds.loadData();allDataSetsReady=false;}}
+if(!allDataSetsReady)
+return;url=Spry.Data.Region.processDataRefString(null,this.url,this.dataSetsForDataRefStrings);if(!url)
+return;if(postData&&(typeof postData)=="string")
+postData=Spry.Data.Region.processDataRefString(null,postData,this.dataSetsForDataRefStrings);}
+this.notifyObservers("onPreLoad");this.data=null;this.dataWasLoaded=false;this.unfilteredData=null;this.dataHash=null;this.curRowID=0;var req=this.requestInfo.clone();req.url=url;req.postData=postData;this.pendingRequest=new Object;this.pendingRequest.data=Spry.Data.HTTPSourceDataSet.LoadManager.loadData(req,this,this.useCache);};Spry.Data.HTTPSourceDataSet.prototype.cancelLoadData=function()
+{if(this.pendingRequest)
+{Spry.Data.HTTPSourceDataSet.LoadManager.cancelLoadData(this.pendingRequest.data,this);this.pendingRequest=null;}};Spry.Data.HTTPSourceDataSet.prototype.getURL=function(){return this.url;};Spry.Data.HTTPSourceDataSet.prototype.setURL=function(url,requestOptions)
+{if(this.url==url)
+{if(!requestOptions||(this.requestInfo.method==requestOptions.method&&(requestOptions.method!="POST"||this.requestInfo.postData==requestOptions.postData)))
+return;}
+this.url=url;this.setRequestInfo(requestOptions);this.cancelLoadData();this.recalculateDataSetDependencies();this.dataWasLoaded=false;};Spry.Data.HTTPSourceDataSet.prototype.setDataFromDoc=function(rawDataDoc)
+{this.pendingRequest=null;this.loadDataIntoDataSet(rawDataDoc);this.applyColumnTypes();this.disableNotifications();this.filterAndSortData();this.enableNotifications();this.notifyObservers("onPostLoad");this.notifyObservers("onDataChanged");};Spry.Data.HTTPSourceDataSet.prototype.loadDataIntoDataSet=function(rawDataDoc)
+{this.dataHash=new Object;this.data=new Array;this.dataWasLoaded=true;};Spry.Data.HTTPSourceDataSet.prototype.xhRequestProcessor=function(xhRequest)
+{var resp=xhRequest.responseText;if(xhRequest.status==200||xhRequest.status==0)
+return resp;return null;};Spry.Data.HTTPSourceDataSet.prototype.sessionExpiredChecker=function(req)
+{if(req.xhRequest.responseText=='session expired')
+return true;return false;};Spry.Data.HTTPSourceDataSet.prototype.setSessionExpiredChecker=function(checker)
+{this.sessionExpiredChecker=checker;};Spry.Data.HTTPSourceDataSet.prototype.onRequestResponse=function(cachedRequest,req)
+{this.setDataFromDoc(cachedRequest.rawData);};Spry.Data.HTTPSourceDataSet.prototype.onRequestError=function(cachedRequest,req)
+{this.notifyObservers("onLoadError",req);};Spry.Data.HTTPSourceDataSet.prototype.onRequestSessionExpired=function(cachedRequest,req)
+{this.notifyObservers("onSessionExpired",req);};Spry.Data.HTTPSourceDataSet.LoadManager={};Spry.Data.HTTPSourceDataSet.LoadManager.cache=[];Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest=function(reqInfo,xhRequestProcessor,sessionExpiredChecker)
+{Spry.Utils.Notifier.call(this);this.reqInfo=reqInfo;this.rawData=null;this.timer=null;this.state=Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.NOT_LOADED;this.xhRequestProcessor=xhRequestProcessor;this.sessionExpiredChecker=sessionExpiredChecker;};Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype=new Spry.Utils.Notifier();Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype.constructor=Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest;Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.NOT_LOADED=1;Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_REQUESTED=2;Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_FAILED=3;Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_SUCCESSFUL=4;Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype.loadDataCallback=function(req)
+{if(req.xhRequest.readyState!=4)
+return;var rawData=null;if(this.xhRequestProcessor)rawData=this.xhRequestProcessor(req.xhRequest);if(this.sessionExpiredChecker)
+{Spry.Utils.setOptions(req,{'rawData':rawData},false);if(this.sessionExpiredChecker(req))
+{this.state=Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_FAILED;this.notifyObservers("onRequestSessionExpired",req);this.observers.length=0;return;}}
+if(!rawData)
+{this.state=Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_FAILED;this.notifyObservers("onRequestError",req);this.observers.length=0;return;}
+this.rawData=rawData;this.state=Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_SUCCESSFUL;this.notifyObservers("onRequestResponse",req);this.observers.length=0;};Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype.loadData=function()
+{var self=this;this.cancelLoadData();this.rawData=null;this.state=Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_REQUESTED;var reqInfo=this.reqInfo.clone();reqInfo.successCallback=function(req){self.loadDataCallback(req);};reqInfo.errorCallback=reqInfo.successCallback;this.timer=setTimeout(function()
+{self.timer=null;Spry.Utils.loadURL(reqInfo.method,reqInfo.url,reqInfo.async,reqInfo.successCallback,reqInfo);},0);};Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype.cancelLoadData=function()
+{if(this.state==Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_REQUESTED)
+{if(this.timer)
+{this.timer.clearTimeout();this.timer=null;}
+this.rawData=null;this.state=Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.NOT_LOADED;}};Spry.Data.HTTPSourceDataSet.LoadManager.getCacheKey=function(reqInfo)
+{return reqInfo.method+"::"+reqInfo.url+"::"+reqInfo.postData+"::"+reqInfo.username;};Spry.Data.HTTPSourceDataSet.LoadManager.loadData=function(reqInfo,ds,useCache)
+{if(!reqInfo)
+return null;var cacheObj=null;var cacheKey=null;if(useCache)
+{cacheKey=Spry.Data.HTTPSourceDataSet.LoadManager.getCacheKey(reqInfo);cacheObj=Spry.Data.HTTPSourceDataSet.LoadManager.cache[cacheKey];}
+if(cacheObj)
+{if(cacheObj.state==Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_REQUESTED)
+{if(ds)
+cacheObj.addObserver(ds);return cacheObj;}
+else if(cacheObj.state==Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_SUCCESSFUL)
+{if(ds)
+setTimeout(function(){ds.setDataFromDoc(cacheObj.rawData);},0);return cacheObj;}}
+if(!cacheObj)
+{cacheObj=new Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest(reqInfo,(ds?ds.xhRequestProcessor:null),(ds?ds.sessionExpiredChecker:null));if(useCache)
+{Spry.Data.HTTPSourceDataSet.LoadManager.cache[cacheKey]=cacheObj;cacheObj.addObserver({onRequestError:function(){Spry.Data.HTTPSourceDataSet.LoadManager.cache[cacheKey]=undefined;}});}}
+if(ds)
+cacheObj.addObserver(ds);cacheObj.loadData();return cacheObj;};Spry.Data.HTTPSourceDataSet.LoadManager.cancelLoadData=function(cacheObj,ds)
+{if(cacheObj)
+{if(ds)
+cacheObj.removeObserver(ds);else
+cacheObj.cancelLoadData();}};Spry.Data.XMLDataSet=function(dataSetURL,dataSetPath,dataSetOptions)
+{this.xpath=dataSetPath;this.doc=null;this.subPaths=[];this.entityEncodeStrings=true;Spry.Data.HTTPSourceDataSet.call(this,dataSetURL,dataSetOptions);var jwType=typeof this.subPaths;if(jwType=="string"||(jwType=="object"&&this.subPaths.constructor!=Array))
+this.subPaths=[this.subPaths];};Spry.Data.XMLDataSet.prototype=new Spry.Data.HTTPSourceDataSet();Spry.Data.XMLDataSet.prototype.constructor=Spry.Data.XMLDataSet;Spry.Data.XMLDataSet.prototype.getDataRefStrings=function()
+{var strArr=[];if(this.url)strArr.push(this.url);if(this.xpath)strArr.push(this.xpath);if(this.requestInfo&&this.requestInfo.postData)strArr.push(this.requestInfo.postData);return strArr;};Spry.Data.XMLDataSet.prototype.getDocument=function(){return this.doc;};Spry.Data.XMLDataSet.prototype.getXPath=function(){return this.xpath;};Spry.Data.XMLDataSet.prototype.setXPath=function(path)
+{if(this.xpath!=path)
+{this.xpath=path;if(this.dataWasLoaded&&this.doc)
+{this.notifyObservers("onPreLoad");this.setDataFromDoc(this.doc);}}};Spry.Data.XMLDataSet.nodeContainsElementNode=function(node)
+{if(node)
+{node=node.firstChild;while(node)
+{if(node.nodeType==1)
+return true;node=node.nextSibling;}}
+return false;};Spry.Data.XMLDataSet.getNodeText=function(node,encodeText,encodeCData)
+{var txt="";if(!node)
+return;try
+{var child=node.firstChild;while(child)
+{try
+{if(child.nodeType==3)
+txt+=encodeText?Spry.Utils.encodeEntities(child.data):child.data;else if(child.nodeType==4)
+txt+=encodeCData?Spry.Utils.encodeEntities(child.data):child.data;}catch(e){Spry.Debug.reportError("Spry.Data.XMLDataSet.getNodeText() exception caught: "+e+"\n");}
+child=child.nextSibling;}}
+catch(e){Spry.Debug.reportError("Spry.Data.XMLDataSet.getNodeText() exception caught: "+e+"\n");}
+return txt;};Spry.Data.XMLDataSet.createObjectForNode=function(node,encodeText,encodeCData)
+{if(!node)
+return null;var obj=new Object();var i=0;var attr=null;try
+{for(i=0;i<node.attributes.length;i++)
+{attr=node.attributes[i];if(attr&&attr.nodeType==2)
+obj["@"+attr.name]=attr.value;}}
+catch(e)
+{Spry.Debug.reportError("Spry.Data.XMLDataSet.createObjectForNode() caught exception while accessing attributes: "+e+"\n");}
+var child=node.firstChild;if(child&&!child.nextSibling&&child.nodeType!=1)
+{obj[node.nodeName]=Spry.Data.XMLDataSet.getNodeText(node,encodeText,encodeCData);}
+while(child)
+{if(child.nodeType==1)
+{if(!Spry.Data.XMLDataSet.nodeContainsElementNode(child))
+{obj[child.nodeName]=Spry.Data.XMLDataSet.getNodeText(child,encodeText,encodeCData);try
+{var namePrefix=child.nodeName+"/@";for(i=0;i<child.attributes.length;i++)
+{attr=child.attributes[i];if(attr&&attr.nodeType==2)
+obj[namePrefix+attr.name]=attr.value;}}
+catch(e)
+{Spry.Debug.reportError("Spry.Data.XMLDataSet.createObjectForNode() caught exception while accessing attributes: "+e+"\n");}}}
+child=child.nextSibling;}
+return obj;};Spry.Data.XMLDataSet.getRecordSetFromXMLDoc=function(xmlDoc,path,suppressColumns,entityEncodeStrings)
+{if(!xmlDoc||!path)
+return null;var recordSet=new Object();recordSet.xmlDoc=xmlDoc;recordSet.xmlPath=path;recordSet.dataHash=new Object;recordSet.data=new Array;recordSet.getData=function(){return this.data;};var ctx=new ExprContext(xmlDoc);var pathExpr=xpathParse(path);var e=pathExpr.evaluate(ctx);var nodeArray=e.nodeSetValue();var isDOMNodeArray=true;if(nodeArray&&nodeArray.length>0)
+isDOMNodeArray=nodeArray[0].nodeType!=2;var nextID=0;var encodeText=true;var encodeCData=false;if(typeof entityEncodeStrings=="boolean")
+encodeText=encodeCData=entityEncodeStrings;for(var i=0;i<nodeArray.length;i++)
+{var rowObj=null;if(suppressColumns)
+rowObj=new Object;else
+{if(isDOMNodeArray)
+rowObj=Spry.Data.XMLDataSet.createObjectForNode(nodeArray[i],encodeText,encodeCData);else
+{rowObj=new Object;rowObj["@"+nodeArray[i].name]=nodeArray[i].value;}}
+if(rowObj)
+{rowObj['ds_RowID']=nextID++;rowObj['ds_XMLNode']=nodeArray[i];recordSet.dataHash[rowObj['ds_RowID']]=rowObj;recordSet.data.push(rowObj);}}
+return recordSet;};Spry.Data.XMLDataSet.PathNode=function(path)
+{this.path=path;this.subPaths=[];this.xpath="";};Spry.Data.XMLDataSet.PathNode.prototype.addSubPath=function(path)
+{var node=this.findSubPath(path);if(!node)
+{node=new Spry.Data.XMLDataSet.PathNode(path);this.subPaths.push(node);}
+return node;};Spry.Data.XMLDataSet.PathNode.prototype.findSubPath=function(path)
+{var numSubPaths=this.subPaths.length;for(var i=0;i<numSubPaths;i++)
+{var subPath=this.subPaths[i];if(path==subPath.path)
+return subPath;}
+return null;};Spry.Data.XMLDataSet.PathNode.prototype.consolidate=function()
+{var numSubPaths=this.subPaths.length;if(!this.xpath&&numSubPaths==1)
+{var subPath=this.subPaths[0];this.path+=((subPath[0]!="/")?"/":"")+subPath.path;this.xpath=subPath.xpath;this.subPaths=subPath.subPaths;this.consolidate();return;}
+for(var i=0;i<numSubPaths;i++)
+this.subPaths[i].consolidate();};Spry.Data.XMLDataSet.prototype.convertXPathsToPathTree=function(xpathArray)
+{var xpaLen=xpathArray.length;var root=new Spry.Data.XMLDataSet.PathNode("");for(var i=0;i<xpaLen;i++)
+{var xpath=xpathArray[i];var cleanXPath=xpath.replace(/\/\//g,"/__SPRYDS__");cleanXPath=cleanXPath.replace(/^\//,"");var pathItems=cleanXPath.split(/\//);var pathItemsLen=pathItems.length;var node=root;for(var j=0;j<pathItemsLen;j++)
+{var path=pathItems[j].replace(/__SPRYDS__/,"//");node=node.addSubPath(path);}
+node.xpath=xpath;}
+root.consolidate();return root;};Spry.Data.XMLDataSet.prototype.flattenSubPaths=function(rs,subPaths)
+{if(!rs||!subPaths)
+return;var numSubPaths=subPaths.length;if(numSubPaths<1)
+return;var data=rs.data;var dataHash={};var xpathArray=[];var cleanedXPathArray=[];for(var i=0;i<numSubPaths;i++)
+{var subPath=subPaths[i];if(typeof subPath=="object")
+subPath=subPath.path;if(!subPath)
+subPath="";xpathArray[i]=Spry.Data.Region.processDataRefString(null,subPath,this.dataSetsForDataRefStrings);cleanedXPathArray[i]=xpathArray[i].replace(/\[.*\]/g,"");}
+var row;var numRows=data.length;var newData=[];for(var i=0;i<numRows;i++)
+{row=data[i];var newRows=[row];for(var j=0;j<numSubPaths;j++)
+{var newRS=Spry.Data.XMLDataSet.getRecordSetFromXMLDoc(row.ds_XMLNode,xpathArray[j],(subPaths[j].xpath?false:true),this.entityEncodeStrings);if(newRS&&newRS.data&&newRS.data.length)
+{if(typeof subPaths[j]=="object"&&subPaths[j].subPaths)
+{var sp=subPaths[j].subPaths;spType=typeof sp;if(spType=="string")
+sp=[sp];else if(spType=="object"&&spType.constructor==Object)
+sp=[sp];this.flattenSubPaths(newRS,sp);}
+var newRSData=newRS.data;var numRSRows=newRSData.length;var cleanedXPath=cleanedXPathArray[j]+"/";var numNewRows=newRows.length;var joinedRows=[];for(var k=0;k<numNewRows;k++)
+{var newRow=newRows[k];for(var l=0;l<numRSRows;l++)
+{var newRowObj=new Object;var newRSRow=newRSData[l];for(prop in newRow)
+newRowObj[prop]=newRow[prop];for(var prop in newRSRow)
+{var newPropName=cleanedXPath+prop;if(cleanedXPath==(prop+"/")||cleanedXPath.search(new RegExp("\\/"+prop+"\\/$"))!=-1)
+newPropName=cleanedXPathArray[j];newRowObj[newPropName]=newRSRow[prop];}
+joinedRows.push(newRowObj);}}
+newRows=joinedRows;}}
+newData=newData.concat(newRows);}
+data=newData;numRows=data.length;for(i=0;i<numRows;i++)
+{row=data[i];row.ds_RowID=i;dataHash[row.ds_RowID]=row;}
+rs.data=data;rs.dataHash=dataHash;};Spry.Data.XMLDataSet.prototype.loadDataIntoDataSet=function(rawDataDoc)
+{var rs=null;var mainXPath=Spry.Data.Region.processDataRefString(null,this.xpath,this.dataSetsForDataRefStrings);var subPaths=this.subPaths;var suppressColumns=false;if(this.subPaths&&this.subPaths.length>0)
+{var processedSubPaths=[];var numSubPaths=subPaths.length;for(var i=0;i<numSubPaths;i++)
+{var subPathStr=Spry.Data.Region.processDataRefString(null,subPaths[i],this.dataSetsForDataRefStrings);if(subPathStr.charAt(0)!='/')
+subPathStr=mainXPath+"/"+subPathStr;processedSubPaths.push(subPathStr);}
+processedSubPaths.unshift(mainXPath);var commonParent=this.convertXPathsToPathTree(processedSubPaths);mainXPath=commonParent.path;subPaths=commonParent.subPaths;suppressColumns=commonParent.xpath?false:true;}
+rs=Spry.Data.XMLDataSet.getRecordSetFromXMLDoc(rawDataDoc,mainXPath,suppressColumns,this.entityEncodeStrings);if(!rs)
+{Spry.Debug.reportError("Spry.Data.XMLDataSet.loadDataIntoDataSet() failed to create dataSet '"+this.name+"'for '"+this.xpath+"' - "+this.url+"\n");return;}
+this.flattenSubPaths(rs,subPaths);this.doc=rs.xmlDoc;this.data=rs.data;this.dataHash=rs.dataHash;this.dataWasLoaded=(this.doc!=null);};Spry.Data.XMLDataSet.prototype.xhRequestProcessor=function(xhRequest)
+{var resp=xhRequest.responseXML;var manualParseRequired=false;if(xhRequest.status!=200)
+{if(xhRequest.status==0)
+{if(xhRequest.responseText&&(!resp||!resp.firstChild))
+manualParseRequired=true;}}
+else if(!resp)
+{manualParseRequired=true;}
+if(manualParseRequired)
+resp=Spry.Utils.stringToXMLDoc(xhRequest.responseText);if(!resp||!resp.firstChild||resp.firstChild.nodeName=="parsererror")
+return null;return resp;};Spry.Data.XMLDataSet.prototype.sessionExpiredChecker=function(req)
+{if(req.xhRequest.responseText=='session expired')
+return true;else
+{if(req.rawData)
+{var firstChild=req.rawData.documentElement.firstChild;if(firstChild&&firstChild.nodeValue=="session expired")
+return true;}}
+return false;};Spry.Data.Region=function(regionNode,name,isDetailRegion,data,dataSets,regionStates,regionStateMap,hasBehaviorAttributes)
+{this.regionNode=regionNode;this.name=name;this.isDetailRegion=isDetailRegion;this.data=data;this.dataSets=dataSets;this.hasBehaviorAttributes=hasBehaviorAttributes;this.tokens=null;this.currentState=null;this.states={ready:true};this.stateMap={};Spry.Utils.setOptions(this.states,regionStates);Spry.Utils.setOptions(this.stateMap,regionStateMap);for(var i=0;i<this.dataSets.length;i++)
+{var ds=this.dataSets[i];try
+{if(ds)
+ds.addObserver(this);}
+catch(e){Spry.Debug.reportError("Failed to add '"+this.name+"' as a dataSet observer!\n");}}};Spry.Data.Region.hiddenRegionClassName="SpryHiddenRegion";Spry.Data.Region.evenRowClassName="even";Spry.Data.Region.oddRowClassName="odd";Spry.Data.Region.notifiers={};Spry.Data.Region.evalScripts=true;Spry.Data.Region.addObserver=function(regionID,observer)
+{var n=Spry.Data.Region.notifiers[regionID];if(!n)
+{n=new Spry.Utils.Notifier();Spry.Data.Region.notifiers[regionID]=n;}
+n.addObserver(observer);};Spry.Data.Region.removeObserver=function(regionID,observer)
+{var n=Spry.Data.Region.notifiers[regionID];if(n)
+n.removeObserver(observer);};Spry.Data.Region.notifyObservers=function(methodName,region,data)
+{var n=Spry.Data.Region.notifiers[region.name];if(n)
+{var dataObj={};if(data&&typeof data=="object")
+dataObj=data;else
+dataObj.data=data;dataObj.region=region;dataObj.regionID=region.name;dataObj.regionNode=region.regionNode;n.notifyObservers(methodName,dataObj);}};Spry.Data.Region.RS_Error=0x01;Spry.Data.Region.RS_LoadingData=0x02;Spry.Data.Region.RS_PreUpdate=0x04;Spry.Data.Region.RS_PostUpdate=0x08;Spry.Data.Region.prototype.getState=function()
+{return this.currentState;};Spry.Data.Region.prototype.mapState=function(stateName,newStateName)
+{this.stateMap[stateName]=newStateName;};Spry.Data.Region.prototype.getMappedState=function(stateName)
+{var mappedState=this.stateMap[stateName];return mappedState?mappedState:stateName;};Spry.Data.Region.prototype.setState=function(stateName,suppressNotfications)
+{var stateObj={state:stateName,mappedState:this.getMappedState(stateName)};if(!suppressNotfications)
+Spry.Data.Region.notifyObservers("onPreStateChange",this,stateObj);this.currentState=stateObj.mappedState?stateObj.mappedState:stateName;if(this.states[stateName])
+{var notificationData={state:this.currentState};if(!suppressNotfications)
+Spry.Data.Region.notifyObservers("onPreUpdate",this,notificationData);var str=this.transform();if(Spry.Data.Region.debug)
+Spry.Debug.trace("<hr />Generated region markup for '"+this.name+"':<br /><br />"+Spry.Utils.encodeEntities(str));Spry.Utils.setInnerHTML(this.regionNode,str,!Spry.Data.Region.evalScripts);if(this.hasBehaviorAttributes)
+this.attachBehaviors();if(!suppressNotfications)
+Spry.Data.Region.notifyObservers("onPostUpdate",this,notificationData);}
+if(!suppressNotfications)
+Spry.Data.Region.notifyObservers("onPostStateChange",this,stateObj);};Spry.Data.Region.prototype.getDataSets=function()
+{return this.dataSets;};Spry.Data.Region.prototype.addDataSet=function(aDataSet)
+{if(!aDataSet)
+return;if(!this.dataSets)
+this.dataSets=new Array;for(var i=0;i<this.dataSets.length;i++)
+{if(this.dataSets[i]==aDataSet)
+return;}
+this.dataSets.push(aDataSet);aDataSet.addObserver(this);};Spry.Data.Region.prototype.removeDataSet=function(aDataSet)
+{if(!aDataSet||this.dataSets)
+return;for(var i=0;i<this.dataSets.length;i++)
+{if(this.dataSets[i]==aDataSet)
+{this.dataSets.splice(i,1);aDataSet.removeObserver(this);return;}}};Spry.Data.Region.prototype.onPreLoad=function(dataSet)
+{if(this.currentState!="loading")
+this.setState("loading");};Spry.Data.Region.prototype.onLoadError=function(dataSet)
+{if(this.currentState!="error")
+this.setState("error");Spry.Data.Region.notifyObservers("onError",this);};Spry.Data.Region.prototype.onSessionExpired=function(dataSet)
+{if(this.currentState!="expired")
+this.setState("expired");Spry.Data.Region.notifyObservers("onExpired",this);};Spry.Data.Region.prototype.onCurrentRowChanged=function(dataSet,data)
+{if(this.isDetailRegion)
+this.updateContent();};Spry.Data.Region.prototype.onPostSort=function(dataSet,data)
+{this.updateContent();};Spry.Data.Region.prototype.onDataChanged=function(dataSet,data)
+{this.updateContent();};Spry.Data.Region.enableBehaviorAttributes=true;Spry.Data.Region.behaviorAttrs={};Spry.Data.Region.behaviorAttrs["spry:select"]={attach:function(rgn,node,value)
+{var selectGroupName=null;try{selectGroupName=node.attributes.getNamedItem("spry:selectgroup").value;}catch(e){}
+if(!selectGroupName)
+selectGroupName="default";Spry.Utils.addEventListener(node,"click",function(event){Spry.Utils.SelectionManager.select(selectGroupName,node,value);},false);if(node.attributes.getNamedItem("spry:selected"))
+Spry.Utils.SelectionManager.select(selectGroupName,node,value);}};Spry.Data.Region.behaviorAttrs["spry:hover"]={attach:function(rgn,node,value)
+{Spry.Utils.addEventListener(node,"mouseover",function(event){Spry.Utils.addClassName(node,value);},false);Spry.Utils.addEventListener(node,"mouseout",function(event){Spry.Utils.removeClassName(node,value);},false);}};Spry.Data.Region.setUpRowNumberForEvenOddAttr=function(node,attr,value,rowNumAttrName)
+{if(!value)
+{Spry.Debug.showError("The "+attr+" attribute requires a CSS class name as its value!");node.attributes.removeNamedItem(attr);return;}
+var dsName="";var valArr=value.split(/\s/);if(valArr.length>1)
+{dsName=valArr[0];node.setAttribute(attr,valArr[1]);}
+node.setAttribute(rowNumAttrName,"{"+(dsName?(dsName+"::"):"")+"ds_RowNumber}");};Spry.Data.Region.behaviorAttrs["spry:even"]={setup:function(node,value)
+{Spry.Data.Region.setUpRowNumberForEvenOddAttr(node,"spry:even",value,"spryevenrownumber");},attach:function(rgn,node,value)
+{if(value)
+{rowNumAttr=node.attributes.getNamedItem("spryevenrownumber");if(rowNumAttr&&rowNumAttr.value)
+{var rowNum=parseInt(rowNumAttr.value);if(rowNum%2)
+Spry.Utils.addClassName(node,value);}}
+node.removeAttribute("spry:even");node.removeAttribute("spryevenrownumber");}};Spry.Data.Region.behaviorAttrs["spry:odd"]={setup:function(node,value)
+{Spry.Data.Region.setUpRowNumberForEvenOddAttr(node,"spry:odd",value,"spryoddrownumber");},attach:function(rgn,node,value)
+{if(value)
+{rowNumAttr=node.attributes.getNamedItem("spryoddrownumber");if(rowNumAttr&&rowNumAttr.value)
+{var rowNum=parseInt(rowNumAttr.value);if(rowNum%2==0)
+Spry.Utils.addClassName(node,value);}}
+node.removeAttribute("spry:odd");node.removeAttribute("spryoddrownumber");}};Spry.Data.Region.setRowAttrClickHandler=function(node,dsName,rowAttr,funcName)
+{if(dsName)
+{var ds=Spry.Data.getDataSetByName(dsName);if(ds)
+{rowIDAttr=node.attributes.getNamedItem(rowAttr);if(rowIDAttr)
+{var rowAttrVal=rowIDAttr.value;if(rowAttrVal)
+Spry.Utils.addEventListener(node,"click",function(event){ds[funcName](rowAttrVal);},false);}}}};Spry.Data.Region.behaviorAttrs["spry:setrow"]={setup:function(node,value)
+{if(!value)
+{Spry.Debug.reportError("The spry:setrow attribute requires a data set name as its value!");node.removeAttribute("spry:setrow");return;}
+node.setAttribute("spryrowid","{"+value+"::ds_RowID}");},attach:function(rgn,node,value)
+{Spry.Data.Region.setRowAttrClickHandler(node,value,"spryrowid","setCurrentRow");node.removeAttribute("spry:setrow");node.removeAttribute("spryrowid");}};Spry.Data.Region.behaviorAttrs["spry:setrownumber"]={setup:function(node,value)
+{if(!value)
+{Spry.Debug.reportError("The spry:setrownumber attribute requires a data set name as its value!");node.removeAttribute("spry:setrownumber");return;}
+node.setAttribute("spryrownumber","{"+value+"::ds_RowID}");},attach:function(rgn,node,value)
+{Spry.Data.Region.setRowAttrClickHandler(node,value,"spryrownumber","setCurrentRowNumber");node.removeAttribute("spry:setrownumber");node.removeAttribute("spryrownumber");}};Spry.Data.Region.behaviorAttrs["spry:sort"]={attach:function(rgn,node,value)
+{if(!value)
+return;var ds=rgn.getDataSets()[0];var sortOrder="toggle";var colArray=value.split(/\s/);if(colArray.length>1)
+{var specifiedDS=Spry.Data.getDataSetByName(colArray[0]);if(specifiedDS)
+{ds=specifiedDS;colArray.shift();}
+if(colArray.length>1)
+{var str=colArray[colArray.length-1];if(str=="ascending"||str=="descending"||str=="toggle")
+{sortOrder=str;colArray.pop();}}}
+if(ds&&colArray.length>0)
+Spry.Utils.addEventListener(node,"click",function(event){ds.sort(colArray,sortOrder);},false);node.removeAttribute("spry:sort");}};Spry.Data.Region.prototype.attachBehaviors=function()
+{var rgn=this;Spry.Utils.getNodesByFunc(this.regionNode,function(node)
+{if(!node||node.nodeType!=1)
+return false;try
+{var bAttrs=Spry.Data.Region.behaviorAttrs;for(var bAttrName in bAttrs)
+{var attr=node.attributes.getNamedItem(bAttrName);if(attr)
+{var behavior=bAttrs[bAttrName];if(behavior&&behavior.attach)
+behavior.attach(rgn,node,attr.value);}}}catch(e){}
+return false;});};Spry.Data.Region.prototype.updateContent=function()
+{var allDataSetsReady=true;var dsArray=this.getDataSets();if(!dsArray||dsArray.length<1)
+{Spry.Debug.reportError("updateContent(): Region '"+this.name+"' has no data set!\n");return;}
+for(var i=0;i<dsArray.length;i++)
+{var ds=dsArray[i];if(ds)
+{if(ds.getLoadDataRequestIsPending())
+allDataSetsReady=false;else if(!ds.getDataWasLoaded())
+{ds.loadData();allDataSetsReady=false;}}}
+if(!allDataSetsReady)
+{Spry.Data.Region.notifyObservers("onLoadingData",this);return;}
+this.setState("ready");};Spry.Data.Region.prototype.clearContent=function()
+{this.regionNode.innerHTML="";};Spry.Data.Region.processContentPI=function(inStr)
+{var outStr="";var regexp=/<!--\s*<\/?spry:content\s*[^>]*>\s*-->/mg;var searchStartIndex=0;var processingContentTag=0;while(inStr.length)
+{var results=regexp.exec(inStr);if(!results||!results[0])
+{outStr+=inStr.substr(searchStartIndex,inStr.length-searchStartIndex);break;}
+if(!processingContentTag&&results.index!=searchStartIndex)
+{outStr+=inStr.substr(searchStartIndex,results.index-searchStartIndex);}
+if(results[0].search(/<\//)!=-1)
+{--processingContentTag;if(processingContentTag)
+Spry.Debug.reportError("Nested spry:content regions are not allowed!\n");}
+else
+{++processingContentTag;var dataRefStr=results[0].replace(/.*\bdataref="/,"");outStr+=dataRefStr.replace(/".*$/,"");}
+searchStartIndex=regexp.lastIndex;}
+return outStr;};Spry.Data.Region.prototype.tokenizeData=function(dataStr)
+{if(!dataStr)
+return null;var rootToken=new Spry.Data.Region.Token(Spry.Data.Region.Token.LIST_TOKEN,null,null,null);var tokenStack=new Array;var parseStr=Spry.Data.Region.processContentPI(dataStr);tokenStack.push(rootToken);var regexp=/((<!--\s*){0,1}<\/{0,1}spry:[^>]+>(\s*-->){0,1})|((\{|%7[bB])[^\}\s%]+(\}|%7[dD]))/mg;var searchStartIndex=0;while(parseStr.length)
+{var results=regexp.exec(parseStr);var token=null;if(!results||!results[0])
+{var str=parseStr.substr(searchStartIndex,parseStr.length-searchStartIndex);token=new Spry.Data.Region.Token(Spry.Data.Region.Token.STRING_TOKEN,null,str,str);tokenStack[tokenStack.length-1].addChild(token);break;}
+if(results.index!=searchStartIndex)
+{var str=parseStr.substr(searchStartIndex,results.index-searchStartIndex);token=new Spry.Data.Region.Token(Spry.Data.Region.Token.STRING_TOKEN,null,str,str);tokenStack[tokenStack.length-1].addChild(token);}
+if(results[0].search(/^({|%7[bB])/)!=-1)
+{var valueName=results[0];var regionStr=results[0];valueName=valueName.replace(/^({|%7[bB])/,"");valueName=valueName.replace(/(}|%7[dD])$/,"");var dataSetName=null;var splitArray=valueName.split(/::/);if(splitArray.length>1)
+{dataSetName=splitArray[0];valueName=splitArray[1];}
+regionStr=regionStr.replace(/^%7[bB]/,"{");regionStr=regionStr.replace(/%7[dD]$/,"}");token=new Spry.Data.Region.Token(Spry.Data.Region.Token.VALUE_TOKEN,dataSetName,valueName,new String(regionStr));tokenStack[tokenStack.length-1].addChild(token);}
+else if(results[0].charAt(0)=='<')
+{var piName=results[0].replace(/^(<!--\s*){0,1}<\/?/,"");piName=piName.replace(/>(\s*-->){0,1}|\s.*$/,"");if(results[0].search(/<\//)!=-1)
+{if(tokenStack[tokenStack.length-1].tokenType!=Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN)
+{Spry.Debug.reportError("Invalid processing instruction close tag: "+piName+" -- "+results[0]+"\n");return null;}
+tokenStack.pop();}
+else
+{var piDesc=Spry.Data.Region.PI.instructions[piName];if(piDesc)
+{var dataSet=null;var selectedDataSetName="";if(results[0].search(/^.*\bselect=\"/)!=-1)
+{selectedDataSetName=results[0].replace(/^.*\bselect=\"/,"");selectedDataSetName=selectedDataSetName.replace(/".*$/,"");if(selectedDataSetName)
+{dataSet=Spry.Data.getDataSetByName(selectedDataSetName);if(!dataSet)
+{Spry.Debug.reportError("Failed to retrieve data set ("+selectedDataSetName+") for "+piName+"\n");selectedDataSetName="";}}}
+var jsExpr=null;if(results[0].search(/^.*\btest=\"/)!=-1)
+{jsExpr=results[0].replace(/^.*\btest=\"/,"");jsExpr=jsExpr.replace(/".*$/,"");jsExpr=Spry.Utils.decodeEntities(jsExpr);}
+var regionState=null;if(results[0].search(/^.*\bname=\"/)!=-1)
+{regionState=results[0].replace(/^.*\bname=\"/,"");regionState=regionState.replace(/".*$/,"");regionState=Spry.Utils.decodeEntities(regionState);}
+var piData=new Spry.Data.Region.Token.PIData(piName,selectedDataSetName,jsExpr,regionState);token=new Spry.Data.Region.Token(Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN,dataSet,piData,new String(results[0]));tokenStack[tokenStack.length-1].addChild(token);tokenStack.push(token);}
+else
+{Spry.Debug.reportError("Unsupported region processing instruction: "+results[0]+"\n");return null;}}}
+else
+{Spry.Debug.reportError("Invalid region token: "+results[0]+"\n");return null;}
+searchStartIndex=regexp.lastIndex;}
+return rootToken;};Spry.Data.Region.prototype.callScriptFunction=function(funcName,processContext)
+{var result=undefined;funcName=funcName.replace(/^\s*\{?\s*function::\s*|\s*\}?\s*$/g,"");var func=Spry.Utils.getObjectByName(funcName);if(func)
+result=func(this.name,function(){return processContext.getValueFromDataSet.apply(processContext,arguments);});return result;};Spry.Data.Region.prototype.evaluateExpression=function(exprStr,processContext)
+{var result=undefined;try
+{if(exprStr.search(/^\s*function::/)!=-1)
+result=this.callScriptFunction(exprStr,processContext);else
+result=Spry.Utils.eval(Spry.Data.Region.processDataRefString(processContext,exprStr,null,true));}
+catch(e)
+{Spry.Debug.trace("Caught exception in Spry.Data.Region.prototype.evaluateExpression() while evaluating: "+Spry.Utils.encodeEntities(exprStr)+"\n    Exception:"+e+"\n");}
+return result;};Spry.Data.Region.prototype.processTokenChildren=function(outputArr,token,processContext)
+{var children=token.children;var len=children.length;for(var i=0;i<len;i++)
+this.processTokens(outputArr,children[i],processContext);};Spry.Data.Region.prototype.processTokens=function(outputArr,token,processContext)
+{var i=0;switch(token.tokenType)
+{case Spry.Data.Region.Token.LIST_TOKEN:this.processTokenChildren(outputArr,token,processContext);break;case Spry.Data.Region.Token.STRING_TOKEN:outputArr.push(token.data);break;case Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN:if(token.data.name=="spry:repeat")
+{var dataSet=null;if(token.dataSet)
+dataSet=token.dataSet;else
+dataSet=this.dataSets[0];if(dataSet)
+{var dsContext=processContext.getDataSetContext(dataSet);if(!dsContext)
+{Spry.Debug.reportError("processTokens() failed to get a data set context!\n");break;}
+dsContext.pushState();var dataSetRows=dsContext.getData();var numRows=dataSetRows.length;for(i=0;i<numRows;i++)
+{dsContext.setRowIndex(i);var testVal=true;if(token.data.jsExpr)
+testVal=this.evaluateExpression(token.data.jsExpr,processContext);if(testVal)
+this.processTokenChildren(outputArr,token,processContext);}
+dsContext.popState();}}
+else if(token.data.name=="spry:if")
+{var testVal=true;if(token.data.jsExpr)
+testVal=this.evaluateExpression(token.data.jsExpr,processContext);if(testVal)
+this.processTokenChildren(outputArr,token,processContext);}
+else if(token.data.name=="spry:choose")
+{var defaultChild=null;var childToProcess=null;var testVal=false;var j=0;for(j=0;j<token.children.length;j++)
+{var child=token.children[j];if(child.tokenType==Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN)
+{if(child.data.name=="spry:when")
+{if(child.data.jsExpr)
+{testVal=this.evaluateExpression(child.data.jsExpr,processContext);if(testVal)
+{childToProcess=child;break;}}}
+else if(child.data.name=="spry:default")
+defaultChild=child;}}
+if(!childToProcess&&defaultChild)
+childToProcess=defaultChild;if(childToProcess)
+this.processTokenChildren(outputArr,childToProcess,processContext);}
+else if(token.data.name=="spry:state")
+{var testVal=true;if(!token.data.regionState||token.data.regionState==this.currentState)
+this.processTokenChildren(outputArr,token,processContext);}
+else
+{Spry.Debug.reportError("processTokens(): Unknown processing instruction: "+token.data.name+"\n");return"";}
+break;case Spry.Data.Region.Token.VALUE_TOKEN:var dataSet=token.dataSet;var val=undefined;if(dataSet&&dataSet=="function")
+{val=this.callScriptFunction(token.data,processContext);}
+else
+{if(!dataSet&&this.dataSets&&this.dataSets.length>0&&this.dataSets[0])
+{dataSet=this.dataSets[0];}
+if(!dataSet)
+{Spry.Debug.reportError("processTokens(): Value reference has no data set specified: "+token.regionStr+"\n");return"";}
+val=processContext.getValueFromDataSet(dataSet,token.data);}
+if(typeof val!="undefined")
+outputArr.push(val+"");break;default:Spry.Debug.reportError("processTokens(): Invalid token type: "+token.regionStr+"\n");break;}};Spry.Data.Region.prototype.transform=function()
+{if(this.data&&!this.tokens)
+this.tokens=this.tokenizeData(this.data);if(!this.tokens)
+return"";processContext=new Spry.Data.Region.ProcessingContext(this);if(!processContext)
+return"";var outputArr=[""];this.processTokens(outputArr,this.tokens,processContext);return outputArr.join("");};Spry.Data.Region.PI={};Spry.Data.Region.PI.instructions={};Spry.Data.Region.PI.buildOpenTagForValueAttr=function(ele,piName,attrName)
+{if(!ele||!piName)
+return"";var jsExpr="";try
+{var testAttr=ele.attributes.getNamedItem(piName);if(testAttr&&testAttr.value)
+jsExpr=Spry.Utils.encodeEntities(testAttr.value);}
+catch(e){jsExpr="";}
+if(!jsExpr)
+{Spry.Debug.reportError(piName+" attribute requires a JavaScript expression that returns true or false!\n");return"";}
+return"<"+Spry.Data.Region.PI.instructions[piName].tagName+" "+attrName+"=\""+jsExpr+"\">";};Spry.Data.Region.PI.buildOpenTagForTest=function(ele,piName)
+{return Spry.Data.Region.PI.buildOpenTagForValueAttr(ele,piName,"test");};Spry.Data.Region.PI.buildOpenTagForState=function(ele,piName)
+{return Spry.Data.Region.PI.buildOpenTagForValueAttr(ele,piName,"name");};Spry.Data.Region.PI.buildOpenTagForRepeat=function(ele,piName)
+{if(!ele||!piName)
+return"";var selectAttrStr="";try
+{var selectAttr=ele.attributes.getNamedItem(piName);if(selectAttr&&selectAttr.value)
+{selectAttrStr=selectAttr.value;selectAttrStr=selectAttrStr.replace(/\s/g,"");}}
+catch(e){selectAttrStr="";}
+if(!selectAttrStr)
+{Spry.Debug.reportError(piName+" attribute requires a data set name!\n");return"";}
+var testAttrStr="";try
+{var testAttr=ele.attributes.getNamedItem("spry:test");if(testAttr)
+{if(testAttr.value)
+testAttrStr=" test=\""+Spry.Utils.encodeEntities(testAttr.value)+"\"";ele.attributes.removeNamedItem(testAttr.nodeName);}}
+catch(e){testAttrStr="";}
+return"<"+Spry.Data.Region.PI.instructions[piName].tagName+" select=\""+selectAttrStr+"\""+testAttrStr+">";};Spry.Data.Region.PI.buildOpenTagForContent=function(ele,piName)
+{if(!ele||!piName)
+return"";var dataRefStr="";try
+{var contentAttr=ele.attributes.getNamedItem(piName);if(contentAttr&&contentAttr.value)
+dataRefStr=Spry.Utils.encodeEntities(contentAttr.value);}
+catch(e){dataRefStr="";}
+if(!dataRefStr)
+{Spry.Debug.reportError(piName+" attribute requires a data reference!\n");return"";}
+return"<"+Spry.Data.Region.PI.instructions[piName].tagName+" dataref=\""+dataRefStr+"\">";};Spry.Data.Region.PI.buildOpenTag=function(ele,piName)
+{return"<"+Spry.Data.Region.PI.instructions[piName].tagName+">";};Spry.Data.Region.PI.buildCloseTag=function(ele,piName)
+{return"</"+Spry.Data.Region.PI.instructions[piName].tagName+">";};Spry.Data.Region.PI.instructions["spry:state"]={tagName:"spry:state",childrenOnly:false,getOpenTag:Spry.Data.Region.PI.buildOpenTagForState,getCloseTag:Spry.Data.Region.PI.buildCloseTag};Spry.Data.Region.PI.instructions["spry:if"]={tagName:"spry:if",childrenOnly:false,getOpenTag:Spry.Data.Region.PI.buildOpenTagForTest,getCloseTag:Spry.Data.Region.PI.buildCloseTag};Spry.Data.Region.PI.instructions["spry:repeat"]={tagName:"spry:repeat",childrenOnly:false,getOpenTag:Spry.Data.Region.PI.buildOpenTagForRepeat,getCloseTag:Spry.Data.Region.PI.buildCloseTag};Spry.Data.Region.PI.instructions["spry:repeatchildren"]={tagName:"spry:repeat",childrenOnly:true,getOpenTag:Spry.Data.Region.PI.buildOpenTagForRepeat,getCloseTag:Spry.Data.Region.PI.buildCloseTag};Spry.Data.Region.PI.instructions["spry:choose"]={tagName:"spry:choose",childrenOnly:true,getOpenTag:Spry.Data.Region.PI.buildOpenTag,getCloseTag:Spry.Data.Region.PI.buildCloseTag};Spry.Data.Region.PI.instructions["spry:when"]={tagName:"spry:when",childrenOnly:false,getOpenTag:Spry.Data.Region.PI.buildOpenTagForTest,getCloseTag:Spry.Data.Region.PI.buildCloseTag};Spry.Data.Region.PI.instructions["spry:default"]={tagName:"spry:default",childrenOnly:false,getOpenTag:Spry.Data.Region.PI.buildOpenTag,getCloseTag:Spry.Data.Region.PI.buildCloseTag};Spry.Data.Region.PI.instructions["spry:content"]={tagName:"spry:content",childrenOnly:true,getOpenTag:Spry.Data.Region.PI.buildOpenTagForContent,getCloseTag:Spry.Data.Region.PI.buildCloseTag};Spry.Data.Region.PI.orderedInstructions=["spry:state","spry:if","spry:repeat","spry:repeatchildren","spry:choose","spry:when","spry:default","spry:content"];Spry.Data.Region.getTokensFromStr=function(str)
+{if(!str)
+return null;return str.match(/{[^}]+}/g);};Spry.Data.Region.processDataRefString=function(processingContext,regionStr,dataSetsToUse,isJSExpr)
+{if(!regionStr)
+return"";if(!processingContext&&!dataSetsToUse)
+return regionStr;var resultStr="";var re=new RegExp("\\{([^\\}:]+::)?[^\\}]+\\}","g");var startSearchIndex=0;while(startSearchIndex<regionStr.length)
+{var reArray=re.exec(regionStr);if(!reArray||!reArray[0])
+{resultStr+=regionStr.substr(startSearchIndex,regionStr.length-startSearchIndex);return resultStr;}
+if(reArray.index!=startSearchIndex)
+resultStr+=regionStr.substr(startSearchIndex,reArray.index-startSearchIndex);var dsName="";if(reArray[0].search(/^\{[^}:]+::/)!=-1)
+dsName=reArray[0].replace(/^\{|::.*/g,"");var fieldName=reArray[0].replace(/^\{|.*::|\}/g,"");var row=null;var val="";if(processingContext)
+val=processingContext.getValueFromDataSet(dsName,fieldName);else
+{var ds=dsName?dataSetsToUse[dsName]:dataSetsToUse[0];if(ds)
+val=ds.getValue(fieldName);}
+if(typeof val!="undefined")
+{val+="";resultStr+=isJSExpr?Spry.Utils.escapeQuotesAndLineBreaks(val):val;}
+if(startSearchIndex==re.lastIndex)
+{var leftOverIndex=reArray.index+reArray[0].length;if(leftOverIndex<regionStr.length)
+resultStr+=regionStr.substr(leftOverIndex);break;}
+startSearchIndex=re.lastIndex;}
+return resultStr;};Spry.Data.Region.strToDataSetsArray=function(str,returnRegionNames)
+{var dataSetsArr=new Array;var foundHash={};if(!str)
+return dataSetsArr;str=str.replace(/\s+/g," ");str=str.replace(/^\s|\s$/g,"");var arr=str.split(/ /);for(var i=0;i<arr.length;i++)
+{if(arr[i]&&!Spry.Data.Region.PI.instructions[arr[i]])
+{try{var dataSet=Spry.Data.getDataSetByName(arr[i]);if(!foundHash[arr[i]])
+{if(returnRegionNames)
+dataSetsArr.push(arr[i]);else
+dataSetsArr.push(dataSet);foundHash[arr[i]]=true;}}
+catch(e){}}}
+return dataSetsArr;};Spry.Data.Region.DSContext=function(dataSet,processingContext)
+{var m_dataSet=dataSet;var m_processingContext=processingContext;var m_curRowIndexArray=[{rowIndex:-1}];var m_parent=null;var m_children=[];var getInternalRowIndex=function(){return m_curRowIndexArray[m_curRowIndexArray.length-1].rowIndex;};this.resetAll=function(){m_curRowIndexArray=[{rowIndex:m_dataSet.getCurrentRow()}]};this.getDataSet=function(){return m_dataSet;};this.getNumRows=function(unfiltered)
+{var data=this.getCurrentState().data;return data?data.length:m_dataSet.getRowCount(unfiltered);};this.getData=function()
+{var data=this.getCurrentState().data;return data?data:m_dataSet.getData();};this.setData=function(data)
+{this.getCurrentState().data=data;};this.getValue=function(valueName,rowContext)
+{var result="";var curState=this.getCurrentState();var ds=curState.nestedDS?curState.nestedDS:this.getDataSet();if(ds)
+result=ds.getValue(valueName,rowContext);return result;};this.getCurrentRow=function()
+{if(m_curRowIndexArray.length<2||getInternalRowIndex()<0)
+return m_dataSet.getCurrentRow();var data=this.getData();var curRowIndex=getInternalRowIndex();if(curRowIndex<0||curRowIndex>data.length)
+{Spry.Debug.reportError("Invalid index used in Spry.Data.Region.DSContext.getCurrentRow()!\n");return null;}
+return data[curRowIndex];};this.getRowIndex=function()
+{var curRowIndex=getInternalRowIndex();if(curRowIndex>=0)
+return curRowIndex;return m_dataSet.getRowNumber(m_dataSet.getCurrentRow());};this.setRowIndex=function(rowIndex)
+{this.getCurrentState().rowIndex=rowIndex;var data=this.getData();var numChildren=m_children.length;for(var i=0;i<numChildren;i++)
+m_children[i].syncDataWithParentRow(this,rowIndex,data);};this.syncDataWithParentRow=function(parentDSContext,rowIndex,parentData)
+{var row=parentData[rowIndex];if(row)
+{nestedDS=m_dataSet.getNestedDataSetForParentRow(row);if(nestedDS)
+{var currentState=this.getCurrentState();currentState.nestedDS=nestedDS;currentState.data=nestedDS.getData();currentState.rowIndex=nestedDS.getCurrentRowNumber();currentState.rowIndex=currentState.rowIndex<0?0:currentState.rowIndex;var numChildren=m_children.length;for(var i=0;i<numChildren;i++)
+m_children[i].syncDataWithParentRow(this,currentState.rowIndex,currentState.data);}}};this.pushState=function()
+{var curState=this.getCurrentState();var newState=new Object;newState.rowIndex=curState.rowIndex;newState.data=curState.data;newState.nestedDS=curState.nestedDS;m_curRowIndexArray.push(newState);var numChildren=m_children.length;for(var i=0;i<numChildren;i++)
+m_children[i].pushState();};this.popState=function()
+{if(m_curRowIndexArray.length<2)
+{Spry.Debug.reportError("Stack underflow in Spry.Data.Region.DSContext.popState()!\n");return;}
+var numChildren=m_children.length;for(var i=0;i<numChildren;i++)
+m_children[i].popState();m_curRowIndexArray.pop();};this.getCurrentState=function()
+{return m_curRowIndexArray[m_curRowIndexArray.length-1];};this.addChild=function(childDSContext)
+{var numChildren=m_children.length;for(var i=0;i<numChildren;i++)
+{if(m_children[i]==childDSContext)
+return;}
+m_children.push(childDSContext);};};Spry.Data.Region.ProcessingContext=function(region)
+{this.region=region;this.dataSetContexts=[];if(region&&region.dataSets)
+{var dsArray=region.dataSets.slice(0);var dsArrayLen=dsArray.length;for(var i=0;i<dsArrayLen;i++)
+{var ds=region.dataSets[i];while(ds&&ds.getParentDataSet)
+{var doesExist=false;ds=ds.getParentDataSet();if(ds&&this.indexOf(dsArray,ds)==-1)
+dsArray.push(ds);}}
+for(i=0;i<dsArray.length;i++)
+this.dataSetContexts.push(new Spry.Data.Region.DSContext(dsArray[i],this));var dsContexts=this.dataSetContexts;var numDSContexts=dsContexts.length;for(i=0;i<numDSContexts;i++)
+{var dsc=dsContexts[i];var ds=dsc.getDataSet();if(ds.getParentDataSet)
+{var parentDS=ds.getParentDataSet();if(parentDS)
+{var pdsc=this.getDataSetContext(parentDS);if(pdsc)pdsc.addChild(dsc);}}}}};Spry.Data.Region.ProcessingContext.prototype.indexOf=function(arr,item)
+{if(arr)
+{var arrLen=arr.length;for(var i=0;i<arrLen;i++)
+if(arr[i]==item)
+return i;}
+return-1;};Spry.Data.Region.ProcessingContext.prototype.getDataSetContext=function(dataSet)
+{if(!dataSet)
+{if(this.dataSetContexts.length>0)
+return this.dataSetContexts[0];return null;}
+if(typeof dataSet=='string')
+{dataSet=Spry.Data.getDataSetByName(dataSet);if(!dataSet)
+return null;}
+for(var i=0;i<this.dataSetContexts.length;i++)
+{var dsc=this.dataSetContexts[i];if(dsc.getDataSet()==dataSet)
+return dsc;}
+return null;};Spry.Data.Region.ProcessingContext.prototype.getValueFromDataSet=function()
+{var dsName="";var columnName="";if(arguments.length>1)
+{dsName=arguments[0];columnName=arguments[1];}
+else
+{var dataRef=arguments[0].replace(/\s*{\s*|\s*}\s*/g,"");if(dataRef.search("::")!=-1)
+{dsName=dataRef.replace(/::.*/,"");columnName=dataRef.replace(/.*::/,"");}
+else
+columnName=dataRef;}
+var result="";var dsContext=this.getDataSetContext(dsName);if(dsContext)
+result=dsContext.getValue(columnName,dsContext.getCurrentRow());else
+Spry.Debug.reportError("getValueFromDataSet: Failed to get "+dsName+" context for the "+this.region.regionNode.id+" region.\n");return result;};Spry.Data.Region.ProcessingContext.prototype.$v=Spry.Data.Region.ProcessingContext.prototype.getValueFromDataSet;Spry.Data.Region.ProcessingContext.prototype.getCurrentRowForDataSet=function(dataSet)
+{var dsc=this.getDataSetContext(dataSet);if(dsc)
+return dsc.getCurrentRow();return null;};Spry.Data.Region.Token=function(tokenType,dataSet,data,regionStr)
+{var self=this;this.tokenType=tokenType;this.dataSet=dataSet;this.data=data;this.regionStr=regionStr;this.parent=null;this.children=null;};Spry.Data.Region.Token.prototype.addChild=function(child)
+{if(!child)
+return;if(!this.children)
+this.children=new Array;this.children.push(child);child.parent=this;};Spry.Data.Region.Token.LIST_TOKEN=0;Spry.Data.Region.Token.STRING_TOKEN=1;Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN=2;Spry.Data.Region.Token.VALUE_TOKEN=3;Spry.Data.Region.Token.PIData=function(piName,data,jsExpr,regionState)
+{var self=this;this.name=piName;this.data=data;this.jsExpr=jsExpr;this.regionState=regionState;};Spry.Utils.addLoadListener(function(){setTimeout(function(){if(Spry.Data.initRegionsOnLoad)Spry.Data.initRegions();},0);});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/SpryJSONDataSet.js	Sun Jun 22 18:13:59 2008 -0400
@@ -0,0 +1,142 @@
+// SpryJSONDataSet.js - version 0.6 - Spry Pre-Release 1.6.1
+//
+// Copyright (c) 2007. Adobe Systems Incorporated.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//   * Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//   * Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//   * Neither the name of Adobe Systems Incorporated nor the names of its
+//     contributors may be used to endorse or promote products derived from this
+//     software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+Spry.Data.JSONDataSet=function(dataSetURL,dataSetOptions)
+{this.path="";this.pathIsObjectOfArrays=false;this.doc=null;this.subPaths=[];this.useParser=false;this.preparseFunc=null;Spry.Data.HTTPSourceDataSet.call(this,dataSetURL,dataSetOptions);var jwType=typeof this.subPaths;if(jwType=="string"||(jwType=="object"&&this.subPaths.constructor!=Array))
+this.subPaths=[this.subPaths];};Spry.Data.JSONDataSet.prototype=new Spry.Data.HTTPSourceDataSet();Spry.Data.JSONDataSet.prototype.constructor=Spry.Data.JSONDataSet;Spry.Data.JSONDataSet.prototype.getDataRefStrings=function()
+{var strArr=[];if(this.url)strArr.push(this.url);if(this.path)strArr.push(this.path);if(this.requestInfo&&this.requestInfo.postData)strArr.push(this.requestInfo.postData);return strArr;};Spry.Data.JSONDataSet.prototype.getDocument=function(){return this.doc;};Spry.Data.JSONDataSet.prototype.getPath=function(){return this.path;};Spry.Data.JSONDataSet.prototype.setPath=function(path)
+{if(this.path!=path)
+{this.path=path;if(this.dataWasLoaded&&this.doc)
+{this.notifyObservers("onPreLoad");this.setDataFromDoc(this.doc);}}};Spry.Data.JSONDataSet.getMatchingObjects=function(path,jsonObj)
+{var results=[];if(path&&jsonObj)
+{var prop="";var leftOverPath="";var offset=path.search(/\./);if(offset!=-1)
+{prop=path.substring(0,offset);leftOverPath=path.substring(offset+1);}
+else
+prop=path;var matches=[];if(prop&&typeof jsonObj=="object")
+{var obj=jsonObj[prop];var objType=typeof obj;if(objType!=undefined&&objType!=null)
+{if(obj&&objType=="object"&&obj.constructor==Array)
+matches=matches.concat(obj);else
+matches.push(obj);}}
+var numMatches=matches.length;if(leftOverPath)
+{for(var i=0;i<numMatches;i++)
+results=results.concat(Spry.Data.JSONDataSet.getMatchingObjects(leftOverPath,matches[i]));}
+else
+results=matches;}
+return results;};Spry.Data.JSONDataSet.flattenObject=function(obj,basicColumnName)
+{var basicName=basicColumnName?basicColumnName:"column0";var row=new Object;var objType=typeof obj;if(objType=="object")
+Spry.Data.JSONDataSet.copyProps(row,obj);else
+row[basicName]=obj;row.ds_JSONObject=obj;return row;};Spry.Data.JSONDataSet.copyProps=function(dstObj,srcObj,suppressObjProps)
+{if(srcObj&&dstObj)
+{for(var prop in srcObj)
+{if(suppressObjProps&&typeof srcObj[prop]=="object")
+continue;dstObj[prop]=srcObj[prop];}}
+return dstObj;};Spry.Data.JSONDataSet.flattenDataIntoRecordSet=function(jsonObj,path,pathIsObjectOfArrays)
+{var rs=new Object;rs.data=[];rs.dataHash={};if(!path)
+path="";var obj=jsonObj;var objType=typeof obj;var basicColName="";if(objType!="object"||!obj)
+{if(obj!=null)
+{var row=new Object;row.column0=obj;row.ds_RowID=0;rs.data.push(row);rs.dataHash[row.ds_RowID]=row;}
+return rs;}
+var matches=[];if(obj.constructor==Array)
+{var arrLen=obj.length;if(arrLen<1)
+return rs;var eleType=typeof obj[0];if(eleType!="object")
+{for(var i=0;i<arrLen;i++)
+{var row=new Object;row.column0=obj[i];row.ds_RowID=i;rs.data.push(row);rs.dataHash[row.ds_RowID]=row;}
+return rs;}
+if(obj[0].constructor==Array)
+return rs;if(path)
+{for(var i=0;i<arrLen;i++)
+matches=matches.concat(Spry.Data.JSONDataSet.getMatchingObjects(path,obj[i]));}
+else
+{for(var i=0;i<arrLen;i++)
+matches.push(obj[i]);}}
+else
+{if(path)
+matches=Spry.Data.JSONDataSet.getMatchingObjects(path,obj);else
+matches.push(obj);}
+var numMatches=matches.length;if(path&&numMatches>=1&&typeof matches[0]!="object")
+basicColName=path.replace(/.*\./,"");if(!pathIsObjectOfArrays)
+{for(var i=0;i<numMatches;i++)
+{var row=Spry.Data.JSONDataSet.flattenObject(matches[i],basicColName,pathIsObjectOfArrays);row.ds_RowID=i;rs.dataHash[i]=row;rs.data.push(row);}}
+else
+{var rowID=0;for(var i=0;i<numMatches;i++)
+{var obj=matches[i];var colNames=[];var maxNumRows=0;for(var propName in obj)
+{var prop=obj[propName];var propyType=typeof prop;if(propyType=='object'&&prop.constructor==Array)
+{colNames.push(propName);maxNumRows=Math.max(maxNumRows,obj[propName].length);}}
+var numColNames=colNames.length;for(var j=0;j<maxNumRows;j++)
+{var row=new Object;for(var k=0;k<numColNames;k++)
+{var colName=colNames[k];row[colName]=obj[colName][j];}
+row.ds_RowID=rowID++;rs.dataHash[row.ds_RowID]=row;rs.data.push(row);}}}
+return rs;};Spry.Data.JSONDataSet.prototype.flattenSubPaths=function(rs,subPaths)
+{if(!rs||!subPaths)
+return;var numSubPaths=subPaths.length;if(numSubPaths<1)
+return;var data=rs.data;var dataHash={};var pathArray=[];var cleanedPathArray=[];var isObjectOfArraysArr=[];for(var i=0;i<numSubPaths;i++)
+{var subPath=subPaths[i];if(typeof subPath=="object")
+{isObjectOfArraysArr[i]=subPath.pathIsObjectOfArrays;subPath=subPath.path;}
+if(!subPath)
+subPath="";pathArray[i]=Spry.Data.Region.processDataRefString(null,subPath,this.dataSetsForDataRefStrings);cleanedPathArray[i]=pathArray[i].replace(/\[.*\]/g,"");}
+var row;var numRows=data.length;var newData=[];for(var i=0;i<numRows;i++)
+{row=data[i];var newRows=[row];for(var j=0;j<numSubPaths;j++)
+{var newRS=Spry.Data.JSONDataSet.flattenDataIntoRecordSet(row.ds_JSONObject,pathArray[j],isObjectOfArraysArr[j]);if(newRS&&newRS.data&&newRS.data.length)
+{if(typeof subPaths[j]=="object"&&subPaths[j].subPaths)
+{var sp=subPaths[j].subPaths;spType=typeof sp;if(spType=="string")
+sp=[sp];else if(spType=="object"&&spType.constructor==Object)
+sp=[sp];this.flattenSubPaths(newRS,sp);}
+var newRSData=newRS.data;var numRSRows=newRSData.length;var cleanedPath=cleanedPathArray[j]+".";var numNewRows=newRows.length;var joinedRows=[];for(var k=0;k<numNewRows;k++)
+{var newRow=newRows[k];for(var l=0;l<numRSRows;l++)
+{var newRowObj=new Object;var newRSRow=newRSData[l];for(var prop in newRSRow)
+{var newPropName=cleanedPath+prop;if(cleanedPath==prop||cleanedPath.search(new RegExp("\\."+prop+"\\.$"))!=-1)
+newPropName=cleanedPathArray[j];newRowObj[newPropName]=newRSRow[prop];}
+Spry.Data.JSONDataSet.copyProps(newRowObj,newRow);joinedRows.push(newRowObj);}}
+newRows=joinedRows;}}
+newData=newData.concat(newRows);}
+data=newData;numRows=data.length;for(i=0;i<numRows;i++)
+{row=data[i];row.ds_RowID=i;dataHash[row.ds_RowID]=row;}
+rs.data=data;rs.dataHash=dataHash;};Spry.Data.JSONDataSet.prototype.parseJSON=function(str,filter)
+{try
+{if(/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(str))
+{var j=eval('('+str+')');if(typeof filter==='function')
+{function walk(k,v)
+{if(v&&typeof v==='object')
+{for(var i in v)
+{if(v.hasOwnProperty(i))
+{v[i]=walk(i,v[i]);}}}
+return filter(k,v);}
+j=walk('',j);}
+return j;}}catch(e){}
+throw new Error("Failed to parse JSON string.");};Spry.Data.JSONDataSet.prototype.syncColumnTypesToData=function()
+{var row=this.getData()[0];for(var colName in row)
+{if(!this.columnTypes[colName])
+{var type=typeof row[colName];if(type=="number")
+this.setColumnType(colName,type);}}};Spry.Data.JSONDataSet.prototype.loadDataIntoDataSet=function(rawDataDoc)
+{if(this.preparseFunc)
+rawDataDoc=this.preparseFunc(this,rawDataDoc);var jsonObj;try{jsonObj=this.useParser?this.parseJSON(rawDataDoc):eval("("+rawDataDoc+")");}
+catch(e)
+{Spry.Debug.reportError("Caught exception in JSONDataSet.loadDataIntoDataSet: "+e);jsonObj={};}
+if(jsonObj==null)
+jsonObj="null";var rs=Spry.Data.JSONDataSet.flattenDataIntoRecordSet(jsonObj,Spry.Data.Region.processDataRefString(null,this.path,this.dataSetsForDataRefStrings),this.pathIsObjectOfArrays);this.flattenSubPaths(rs,this.subPaths);this.doc=rawDataDoc;this.docObj=jsonObj;this.data=rs.data;this.dataHash=rs.dataHash;this.dataWasLoaded=(this.doc!=null);this.syncColumnTypesToData();};
\ No newline at end of file
--- a/includes/clientside/static/ajax.js	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/static/ajax.js	Sun Jun 22 18:13:59 2008 -0400
@@ -2,6 +2,22 @@
  * AJAX applets
  */
  
+function ajaxMakeXHR()
+{
+  var ajax;
+  if (window.XMLHttpRequest) {
+    ajax = new XMLHttpRequest();
+  } else {
+    if (window.ActiveXObject) {           
+      ajax = new ActiveXObject("Microsoft.XMLHTTP");
+    } else {
+      alert('Enano client-side runtime error: No AJAX support, unable to continue');
+      return;
+    }
+  }
+  return ajax;
+}
+
 function ajaxGet(uri, f, call_editor_safe) {
   // Is the editor open?
   if ( editor_open && !call_editor_safe )
@@ -18,15 +34,11 @@
     editor_open = false;
     enableUnload();
   }
-  if (window.XMLHttpRequest) {
-    ajax = new XMLHttpRequest();
-  } else {
-    if (window.ActiveXObject) {           
-      ajax = new ActiveXObject("Microsoft.XMLHTTP");
-    } else {
-      alert('Enano client-side runtime error: No AJAX support, unable to continue');
-      return;
-    }
+  ajax = ajaxMakeXHR();
+  if ( !ajax )
+  {
+    console.error('ajaxMakeXHR() failed');
+    return false;
   }
   ajax.onreadystatechange = f;
   ajax.open('GET', uri, true);
@@ -50,15 +62,11 @@
     editor_open = false;
     enableUnload();
   }
-  if (window.XMLHttpRequest) {
-    ajax = new XMLHttpRequest();
-  } else {
-    if (window.ActiveXObject) {           
-      ajax = new ActiveXObject("Microsoft.XMLHTTP");
-    } else {
-      alert('Enano client-side runtime error: No AJAX support, unable to continue');
-      return;
-    }
+  ajax = ajaxMakeXHR();
+  if ( !ajax )
+  {
+    console.error('ajaxMakeXHR() failed');
+    return false;
   }
   ajax.onreadystatechange = f;
   ajax.open('POST', uri, true);
--- a/includes/clientside/static/autocomplete.js	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/static/autocomplete.js	Sun Jun 22 18:13:59 2008 -0400
@@ -3,296 +3,10 @@
  * NOTE: A more efficient version of the username field is used for Mozilla browsers. The updated code is in autofill.js.
  */
 
-// The ultimate Javascript app: AJAX auto-completion, which responds to up/down arrow keys, the enter key, and the escape key
-// The idea was pilfered mercilessly from vBulletin, but uses about 8
-// bytes of vB code. All the rest was coded by me, Mr. Javascript Newbie...
-
-// ...in about 8 hours.
-// You folks better like this stuff.
-
-function nameCompleteEventHandler(e)
-{
-  if(!e) e = window.event;
-  switch(e.keyCode)
-  {
-    case 38: // up
-      unSelectMove('up');
-      break;
-    case 40: // down
-      unSelectMove('down');
-      break;
-    case 27: // escape
-    case 9:  // tab
-      destroyUsernameDropdowns();
-      break;
-    case 13: // enter
-      unSelect();
-      break;
-    default: return false; break;
-  }
-  return true;
-}
-
-function unSelectMove(dir)
-{
-  if(submitAuthorized) return false;
-  var thediv = document.getElementById(unObjDivCurrentId);
-  thetable = thediv.firstChild;
-  cel = thetable.firstChild.firstChild;
-  d = true;
-  index = false;
-  changed = false;
-  // Object of the game: extract the username, determine its index in the userlist array, and then color the menu items and set unObjCurrentSelection
-  while(d) // Set to false if an exception occurs or if we arrive at our destination
-  {
-    //*
-    if(!cel) d=false;
-    celbak = cel;
-    cel = cel.nextSibling;
-    if(!cel) d=false;
-    try {
-      if(cel.firstChild.nextSibling) html = cel.firstChild.nextSibling.innerHTML;
-      else html = cel.firstChild.innerHTML;
-      cel.firstChild.className = 'row1';
-      if(cel.firstChild.nextSibling) cel.firstChild.nextSibling.className = 'row1';
-      thename = html.substr(7, html.length-15);
-      // FINALLY! we have extracted the username
-      // Now get its position in the userlist array
-      if(thename == unObjCurrentSelection)
-      {
-        index = parseInt(in_array(thename, userlist));
-      }
-      if(typeof(index) == 'number')
-      {
-        if(dir=='down')
-          n = index+1;
-        else if(dir == 'up')
-          n = index - 1;
-        
-        // Try to trap moving the selection up or down beyond the top of bottom
-        if(n > userlist.length-1 || n < 0)
-        {
-          cel.firstChild.className = 'row2';
-          if(cel.firstChild.nextSibling) cel.firstChild.nextSibling.className = 'row2';
-          return;
-        }
-        
-        if(dir=='down') no=cel.nextSibling;
-        else if(dir=='up') no=cel.previousSibling;
-        no.firstChild.className = 'row2';
-        if(no.firstChild.nextSibling) no.firstChild.nextSibling.className = 'row2';
-        if(no.firstChild.id)
-        {
-          scroll = getScrollOffset() + getHeight();
-          elemht = getElementHeight(no.firstChild.id);
-          elemoff = fetch_offset(no.firstChild);
-          whereto = elemoff['top'] + elemht;
-          if(whereto > scroll)
-          {
-            window.location.hash = '#'+no.firstChild.id;
-            unObj.focus();
-          }
-        }
-        cel=cel.nextSibling;
-        unObjCurrentSelection = userlist[n];
-        index = false;
-        changed = true;
-        return;
-      }
-    } catch(e) { }
-    //*/ d = false;
-  }
-}
-
-function unSelect()
-{
-  if(!unObj || submitAuthorized) return false;
-  if ( unObjCurrentSelection )
-    unObj.value = unObjCurrentSelection;
-  destroyUsernameDropdowns(); 
-}
-
-function in_array(needle, haystack)
-{
-  for(var i in haystack)
-  {
-    if(haystack[i] == needle) return i;
-  }
-  return false;
-}
-
-function ajaxUserNameComplete(o)
-{
-  if(!o) {destroyUsernameDropdowns(); return;}
-  if(!o.value) {destroyUsernameDropdowns(); return;}
-  if(o.value.length < 3) {destroyUsernameDropdowns(); return;}
-  //if(IE) return; // This control doesn't work in IE. Yes, it's true! document.createElement doesn't work.
-  if(!o.id)
-  {
-    o.id = 'usernametextboxobj_' + Math.floor(Math.random() * 10000000);
-  }
-  unObj = o;
-  o.setAttribute("autocomplete","off");
-  o.onkeyup = function(e, o) { o=unObj; if(!nameCompleteEventHandler(e)) ajaxUserNameComplete(o); }
-  val = escape(o.value).replace('+', '%2B');
-  ajaxGet(stdAjaxPrefix+'&_mode=fillusername&name='+val, function()
-    {
-      if ( ajax.readyState == 4 && ajax.status == 200 )
-      {
-        // Determine the appropriate left/top positions, then create a div to use for the drop-down list
-        // The trick here is to be able to make the div dynamically destroy itself depending on how far the user's mouse is from it
-        destroyUsernameDropdowns();
-        off = fetch_offset(unObj);
-        dim = fetch_dimensions(unObj);
-        left = off['left'];
-        i1 = off['top'];
-        i2 = dim['h'];
-        var top = 0;
-        top = i1 + i2;
-        var thediv = document.createElement('div');
-        thediv.className = 'tblholder';
-        thediv.style.marginTop = '0px';
-        thediv.style.position = 'absolute';
-        thediv.style.top  = top  + 'px';
-        thediv.style.left = left + 'px';
-        thediv.style.zIndex = getHighestZ() + 2;
-        id = 'usernamehoverobj_' + Math.floor(Math.random() * 10000000);
-        unObjDivCurrentId = id;
-        thediv.id = id;
-        unObj.onblur = function() { destroyUsernameDropdowns(); }
-        
-        var response = String(ajax.responseText) + ' ';
-        if ( response.substr(0,1) != '{' )
-        {
-          new MessageBox(MB_OK|MB_ICONSTOP, 'Invalid response', 'Invalid or unexpected JSON response from server:<pre>' + ajax.responseText + '</pre>');
-          return false;
-        }
-        
-        response = parseJSON(response);
-        var errorstring = false;
-        if ( response.mode == 'error' )
-        {
-          errorstring = response.error;
-        }
-        else
-        {
-          var userlist = response.users_real;
-        }
-        
-        if(errorstring)
-        {
-          html = '<span style="color: #555; padding: 4px;">'+errorstring+'</span>';
-        }
-        else
-        {
-          html = '<table border="0" cellspacing="1" cellpadding="3" style="width: auto;"><tr><th><small>' + $lang.get('user_autofill_heading_suggestions') + '</small></th></tr>';
-          cls = 'row2';
-          unObjCurrentSelection = userlist[0];
-          for(i=0;i<userlist.length;i++)
-          {
-            tmpnam = 'listobjnode_'+Math.floor(Math.random() * 10000000);
-            html = html + '<tr><td id="'+tmpnam+'" class="'+cls+'" style="cursor: pointer;" onclick="document.getElementById(\''+unObj.id+'\').value=\''+userlist[i]+'\';destroyUsernameDropdowns();"><small>'+userlist[i]+'</small></td></tr>';
-            if(cls=='row2') cls='row1';
-          }
-          html = html + '</table>';
-        }
-        
-        thediv.innerHTML = html;
-        var body = document.getElementsByTagName('body');
-        body = body[0];
-        unSelectMenuOn = true;
-        submitAuthorized = false;
-        body.appendChild(thediv);
-      }
-    });
-}
-
-function ajaxPageNameComplete(o)
-{
-  if(!o) {destroyUsernameDropdowns(); return;}
-  if(!o.value) {destroyUsernameDropdowns(); return;}
-  if(o.value.length < 3) {destroyUsernameDropdowns(); return;}
-  if(IE) return; // This control doesn't work in IE. Yes, it's true! document.createElement doesn't work.
-  if(!o.id)
-  {
-    o.id = 'usernametextboxobj_' + Math.floor(Math.random() * 10000000);
-  }
-  o.setAttribute("autocomplete","off");
-  unObj = o;
-  o.onkeyup = function(e, o) { o=unObj; if(!nameCompleteEventHandler(e)) ajaxPageNameComplete(o); }
-  val = escape(o.value).replace('+', '%2B');
-  ajaxGet(stdAjaxPrefix+'&_mode=fillpagename&name='+val, function()
-    {
-      if(!ajax) return;
-      if ( ajax.readyState == 4 && ajax.status == 200 )
-      {
-        // Determine the appropriate left/top positions, then create a div to use for the drop-down list
-        // The trick here is to be able to make the div dynamically destroy itself depending on how far the user's mouse is from it
-        destroyUsernameDropdowns();
-        off = fetch_offset(unObj);
-        dim = fetch_dimensions(unObj);
-        left = off['left'];
-        top = off['top'] + dim['h'];
-        var thediv = document.createElement('div');
-        thediv.className = 'tblholder';
-        thediv.style.marginTop = '0px';
-        thediv.style.position = 'absolute';
-        thediv.style.top  = top  + 'px';
-        thediv.style.left = left + 'px';
-        thediv.style.zIndex = getHighestZ() + 2;
-        id = 'usernamehoverobj_' + Math.floor(Math.random() * 10000000);
-        unObjDivCurrentId = id;
-        thediv.id = id;
-        
-        eval(ajax.responseText);
-        if(errorstring)
-        {
-          html = '<span style="color: #555; padding: 4px;">'+errorstring+'</span>';
-        }
-        else
-        {
-          html = '<table border="0" cellspacing="1" cellpadding="3" style="width: auto;"><tr><th colspan="2">' + $lang.get('page_autosuggest_heading') + '</th></tr><tr><th><small>' + $lang.get('page_autosuggest_col_name') + '</small></th><th><small>' + $lang.get('page_autosuggest_col_page_id') + '</small></th></tr>';
-          cls = 'row2';
-          unObjCurrentSelection = userlist[0];
-          for(i=0;i<userlist.length;i++)
-          {
-            tmpnam = 'listobjnode_'+Math.floor(Math.random() * 10000000);
-            html = html + '<tr><td id="'+tmpnam+'" class="'+cls+'" style="cursor: pointer;" onclick="document.getElementById(\''+unObj.id+'\').value=\''+userlist[i]+'\';destroyUsernameDropdowns();"><small>'+namelist[i]+'</small></td><td class="'+cls+'" style="cursor: pointer;" onclick="document.getElementById(\''+unObj.id+'\').value=\''+userlist[i]+'\';destroyUsernameDropdowns();"><small>'+userlist[i]+'</small></td></tr>';
-            if(cls=='row2') cls='row1';
-          }
-          html = html + '</table>';
-        }
-        
-        thediv.innerHTML = html;
-        var body = document.getElementsByTagName('body');
-        body = body[0];
-        unSelectMenuOn = true;
-        submitAuthorized = false;
-        body.appendChild(thediv);
-      }
-    });
-}
-
-function destroyUsernameDropdowns()
-{
-  var divs = document.getElementsByTagName('div');
-  var prefix = 'usernamehoverobj_';
-  for(i=0;i<divs.length;i++)                                                                                                                                                                                                                         
-  {
-    if ( divs[i].id )
-    {
-      if(divs[i].id.substr(0, prefix.length)==prefix)
-      {
-        divs[i].innerHTML = '';
-        divs[i].style.display = 'none';
-      }
-    }
-  }
-  unSelectMenuOn = false;
-  unObjDivCurrentId = false;
-  unObjCurrentSelection = false;
-  submitAuthorized = true;
-}
+//
+// **** 1.1.4: DEPRECATED ****
+// Replaced with Spry-based mechanism.
+//
 
 function get_parent_form(o)
 {
--- a/includes/clientside/static/autofill.js	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/static/autofill.js	Sun Jun 22 18:13:59 2008 -0400
@@ -2,505 +2,145 @@
  * Javascript auto-completion for form fields. This supercedes the code in autocomplete.js for MOZILLA ONLY. It doesn't seem to work real
  * well with other browsers yet.
  */
- 
-var af_current = false;
- 
-function AutofillUsername(parent, event, allowanon)
-{
-  // if this is IE, use the old code
-  if ( IE )
-  {
-    ajaxUserNameComplete(parent);
-    return false;
-  }
-  if ( parent.afobj )
-  {
-    parent.afobj.go();
-    return true;
-  }
-  
-  parent.autocomplete = 'off';
-  parent.setAttribute('autocomplete', 'off');
+
+// fill schemas
+var autofill_schemas = {};
+
+// default, generic schema
+autofill_schemas.generic = {
+  template: '<div id="--ID--_region" spry:region="autofill_region_--ID--" class="tblholder">' + "\n" +
+            '  <table border="0" cellspacing="1" cellpadding="3" style="font-size: smaller;">' + "\n" +
+            '    <tr spry:repeat="autofill_region_--ID--">' + "\n" +
+            '      <td class="row1" spry:suggest="{name}">{name}</td>' + "\n" +
+            '    </tr>' + "\n" +
+            '  </table>' + "\n" +
+            '</div>',
   
-  this.repeat = false;
-  this.event = event;
-  this.box_id = false;
-  this.boxes = new Array();
-  this.state = false;
-  this.allowanon = ( allowanon ) ? true : false;
-  
-  if ( !parent.id )
-    parent.id = 'afuser_' + Math.floor(Math.random() * 1000000);
-  
-  this.field_id = parent.id;
-  
-  // constants
-  this.KEY_UP    = 38;
-  this.KEY_DOWN  = 40;
-  this.KEY_ESC   = 27;
-  this.KEY_TAB   = 9;
-  this.KEY_ENTER = 13;
-  
-  // response cache
-  this.responses = new Object();
-  
-  // ajax placeholder
-  this.process_dataset = function(resp_json)
+  init: function(element, fillclass)
   {
-    // window.console.info('Processing the following dataset.');
-    // window.console.debug(resp_json);
-    var autofill = this;
+    // setup the dataset
+    window["autofill_region_" + element.id] = new Spry.Data.JSONDataSet(makeUrlNS('Special', 'Autofill', 'type=' + fillclass));
     
-    if ( typeof(autofill.event) == 'object' )
-    {
-      if ( autofill.event.keyCode )
-      {
-        if ( autofill.event.keyCode == autofill.KEY_ENTER && autofill.boxes.length < 1 && !autofill.box_id )
-        {
-          // user hit enter after accepting a suggestion - submit the form
-          var frm = findParentForm($dynano(autofill.field_id).object);
-          frm._af_acting = false;
-          frm.submit();
-          // window.console.info('Submitting form');
-          return false;
-        }
-        if ( autofill.event.keyCode == autofill.KEY_UP || autofill.event.keyCode == autofill.KEY_DOWN || autofill.event.keyCode == autofill.KEY_ESC || autofill.event.keyCode == autofill.KEY_TAB || autofill.event.keyCode == autofill.KEY_ENTER )
-        {
-          autofill.keyhandler();
-          // window.console.info('Control key detected, called keyhandler and exiting');
-          return true;
-        }
-      }
-    }
+    // inject our HTML wrapper
+    var template = this.template.replace(new RegExp('--ID--', 'g'), element.id);
+    var wrapper = element.parentNode; // document.createElement('div');
+    wrapper.id = 'autofill_wrap_' + element.id;
     
-    if ( this.box_id )
-    {
-      this.destroy();
-      // window.console.info('already have a box open - destroying and exiting');
-      //return false;
-    }
-    
-    var users = new Array();
-    for ( var i = 0; i < resp_json.users_real.length; i++ )
-    {
-      try
-      {
-        var user = resp_json.users_real[i].toLowerCase();
-        var inp  = $dynano(autofill.field_id).object.value;
-        inp = inp.toLowerCase();
-        if ( user.indexOf(inp) > -1 )
-        {
-          users.push(resp_json.users_real[i]);
-        }
-      }
-      catch(e)
-      {
-        users.push(resp_json.users_real[i]);
-      }
-    }
-
-    // This was used ONLY for debugging the DOM and list logic    
-    // resp_json.users = resp_json.users_real;
+    // a bunch of hacks to add a spry wrapper
+    // var el2 = element.cloneNode(false);
+    // wrapper.appendChild(el2);
+    wrapper.innerHTML += template;
+    // insertAfter(element.parentNode, wrapper, element);
+    // element.parentNode.removeChild(element);
+    // element = el2;
     
-    // construct table
-    var div = document.createElement('div');
-    div.className = 'tblholder';
-    div.style.clip = 'rect(0px,auto,auto,0px)';
-    div.style.maxHeight = '200px';
-    div.style.overflow = 'auto';
-    div.style.zIndex = '9999';
-    var table = document.createElement('table');
-    table.border = '0';
-    table.cellSpacing = '1';
-    table.cellPadding = '3';
+    var autosuggest = new Spry.Widget.AutoSuggest("autofill_wrap_" + element.id, element.id + '_region', window["autofill_region_" + element.id], 'name', {loadFromServer: true, urlParam: 'userinput', hoverSuggestClass: 'row2', minCharsType: 3});
+  }
+};
+
+function autofill_init_element(element, params)
+{
+  if ( element.parentNode.id.match(/^autofill_wrap_/) )
+    return false;
+  
+  params = params || {};
+  // assign an ID if it doesn't have one yet
+  if ( !element.id )
+  {
+    element.id = 'autofill_' + Math.floor(Math.random() * 100000);
+  }
+  var id = element.id;
+  
+  // get the fill type
+  var fillclass = element.className;
+  fillclass = fillclass.split(' ');
+  fillclass = fillclass[1];
+  
+  var schema = ( autofill_schemas[fillclass] ) ? autofill_schemas[fillclass] : autofill_schemas['generic'];
+  if ( typeof(schema.init) != 'function' )
+  {
+    schema.init = autofill_schemas.generic.init;
+  }
+  schema.init(element, fillclass, params);
+  
+  element.af_initted = true;
+}
+
+var autofill_onload = function()
+{
+  autofill_schemas.username = {
+    template: '<div id="--ID--_region" spry:region="autofill_region_--ID--" class="tblholder">' + "\n" +
+              '  <table border="0" cellspacing="1" cellpadding="3" style="font-size: smaller;">' + "\n" +
+              '    <tr>' + "\n" +
+              '      <th>' + $lang.get('user_autofill_heading_suggestions') + '</th>' + "\n" +
+              '    </tr>' + "\n" +
+              '    <tr spry:repeat="autofill_region_--ID--">' + "\n" +
+              '      <td class="row1" spry:suggest="{name}">{name_highlight}<br /><small style="{rank_style}">{rank_title}</small></td>' + "\n" +
+              '    </tr>' + "\n" +
+              '  </table>' + "\n" +
+              '</div>',
     
-    var tr = document.createElement('tr');
-    var th = document.createElement('th');
-    th.appendChild(document.createTextNode($lang.get('user_autofill_heading_suggestions')));
-    tr.appendChild(th);
-    table.appendChild(tr);
-    
-    if ( users.length < 1 )
+    init: function(element, fillclass, params)
     {
-      var tr = document.createElement('tr');
-      var td = document.createElement('td');
-      td.className = 'row1';
-      td.appendChild(document.createTextNode($lang.get('user_autofill_msg_no_suggestions')));
-      td.afobj = autofill;
-      tr.appendChild(td);
-      table.appendChild(tr);
-    }
-    else
-      
-      for ( var i = 0; i < users.length; i++ )
-      {
-        var user = users[i];
-        var tr = document.createElement('tr');
-        var td = document.createElement('td');
-        td.className = ( i == 0 ) ? 'row2' : 'row1';
-        td.appendChild(document.createTextNode(user));
-        td.afobj = autofill;
-        td.style.cursor = 'pointer';
-        td.onclick = function()
-        {
-          this.afobj.set(this.firstChild.nodeValue);
-        }
-        tr.appendChild(td);
-        table.appendChild(tr);
-      }
+      // calculate positions before spry f***s everything up
+      var top = $dynano(element).Top() + $dynano(element).Height() - 10; // tblholder has 10px top margin
+      var left = $dynano(element).Left();
       
-    // Finalize div
-    var tb_top    = $dynano(autofill.field_id).Top();
-    var tb_height = $dynano(autofill.field_id).Height();
-    var af_top    = tb_top + tb_height - 9;
-    var tb_left   = $dynano(autofill.field_id).Left();
-    var af_left   = tb_left;
-    
-    div.style.position = 'absolute';
-    div.style.left = af_left + 'px';
-    div.style.top  = af_top  + 'px';
-    div.style.width = '200px';
-    div.style.fontSize = '7pt';
-    div.style.fontFamily = 'Trebuchet MS, arial, helvetica, sans-serif';
-    div.id = 'afuserdrop_' + Math.floor(Math.random() * 1000000);
-    div.appendChild(table);
-    
-    autofill.boxes.push(div.id);
-    autofill.box_id = div.id;
-    if ( users.length > 0 )
-      autofill.state = users[0];
-    
-    var body = document.getElementsByTagName('body')[0];
-    body.appendChild(div);
-    
-    autofill.repeat = true;
-  }
+      var allow_anon = ( params.allow_anon ) ? '1' : '0';
+      // setup the dataset
+      window["autofill_region_" + element.id] = new Spry.Data.JSONDataSet(makeUrlNS('Special', 'Autofill', 'type=' + fillclass + '&allow_anon' + allow_anon));
+      Spry.Data.initRegions(document.body);
+      (window["autofill_region_" + element.id]).loadData();
+      
+      // inject our HTML wrapper
+      var template = this.template.replace(new RegExp('--ID--', 'g'), element.id);
+      var wrapper = element.parentNode; // document.createElement('div');
+      wrapper.id = 'autofill_wrap_' + element.id;
+      
+      // a bunch of hacks to add a spry wrapper
+      wrapper.innerHTML = template + wrapper.innerHTML;
+      
+      var autosuggest = new Spry.Widget.AutoSuggest("autofill_wrap_" + element.id, element.id + '_region', window["autofill_region_" + element.id], 'name', {loadFromServer: true, urlParam: 'userinput', hoverSuggestClass: 'row2', minCharsType: 3});
+      var regiondiv = document.getElementById(element.id + '_region');
+      regiondiv.style.position = 'absolute';
+      regiondiv.style.top = top + 'px';
+      regiondiv.style.left = left + 'px';
+    }
+  };
   
-  // perform ajax call
-  this.fetch_and_process = function()
-  {
-    af_current = this;
-    var processResponse = function()
-    {
-      if ( ajax.readyState == 4 && ajax.status == 200 )
-      {
-        var afobj = af_current;
-        af_current = false;
-        // parse the JSON response
-        var response = String(ajax.responseText) + ' ';
-        if ( response.substr(0,1) != '{' )
-        {
-          new MessageBox(MB_OK|MB_ICONSTOP, 'Invalid response', 'Invalid or unexpected JSON response from server:<pre>' + ajax.responseText + '</pre>');
-          return false;
-        }
-        if ( $dynano(afobj.field_id).object.value.length < 3 )
-          return false;
-        var resp_json = parseJSON(response);
-        var resp_code = $dynano(afobj.field_id).object.value.toLowerCase().substr(0, 3);
-        afobj.responses[resp_code] = resp_json;
-        afobj.process_dataset(resp_json);
-      }
-    }
-    var usernamefragment = ajaxEscape($dynano(this.field_id).object.value);
-    ajaxGet(stdAjaxPrefix + '&_mode=fillusername&name=' + usernamefragment + '&allowanon=' + ( this.allowanon ? '1' : '0' ), processResponse);
+  autofill_schemas.page = {
+    template: '<div id="--ID--_region" spry:region="autofill_region_--ID--" class="tblholder">' + "\n" +
+              '  <table border="0" cellspacing="1" cellpadding="3" style="font-size: smaller;">' + "\n" +
+              '    <tr>' + "\n" +
+              '      <th colspan="2">' + $lang.get('page_autosuggest_heading') + '</th>' + "\n" +
+              '    </tr>' + "\n" +
+              '    <tr spry:repeat="autofill_region_--ID--">' + "\n" +
+              '      <td class="row1" spry:suggest="{page_id}">{pid_highlight}<br /><small>{name_highlight}</small></td>' + "\n" +
+              '    </tr>' + "\n" +
+              '  </table>' + "\n" +
+              '</div>'
   }
   
-  this.go = function()
-  {
-    if ( document.getElementById(this.field_id).value.length < 3 )
-    {
-      this.destroy();
-      return false;
-    }
-    
-    if ( af_current )
-      return false;
-    
-    var resp_code = $dynano(this.field_id).object.value.toLowerCase().substr(0, 3);
-    if ( this.responses.length < 1 || ! this.responses[ resp_code ] )
-    {
-      // window.console.info('Cannot find dataset ' + resp_code + ' in cache, sending AJAX request');
-      this.fetch_and_process();
-    }
-    else
-    {
-      // window.console.info('Using cached dataset: ' + resp_code);
-      var resp_json = this.responses[ resp_code ];
-      this.process_dataset(resp_json);
-    }
-    document.getElementById(this.field_id).onkeyup = function(event)
-    {
-      this.afobj.event = event;
-      this.afobj.go();
-    }
-    document.getElementById(this.field_id).onkeydown = function(event)
-    {
-      var form = findParentForm(this);
-      if ( typeof(event) != 'object' )
-        var event = window.event;
-      if ( typeof(event) == 'object' )
-      {
-        if ( event.keyCode == this.afobj.KEY_ENTER && this.afobj.boxes.length < 1 && !this.afobj.box_id )
-        {
-          // user hit enter after accepting a suggestion - submit the form
-          form._af_acting = false;
-          return true;
-        }
-        else
-        {
-          form._af_acting = true;
-          return true;
-        }
-      }
-    }
-  }
+  var inputs = document.getElementsByClassName('input', 'autofill');
   
-  this.keyhandler = function()
-  {
-    var key = this.event.keyCode;
-    if ( key == this.KEY_ENTER && !this.repeat )
-    {
-      submitAuthorized = true;
-      var form = findParentForm($dynano(this.field_id).object);
-      form._af_acting = false;
-      return true;
-    }
-    switch(key)
-    {
-      case this.KEY_UP:
-        this.focus_up();
-        break;
-      case this.KEY_DOWN:
-        this.focus_down();
-        break;
-      case this.KEY_ESC:
-        this.destroy();
-        break;
-      case this.KEY_TAB:
-        this.destroy();
-        break;
-      case this.KEY_ENTER:
-        this.set();
-        break;
-    }
-    
-    var form = findParentForm($dynano(this.field_id).object);
-      form._af_acting = false;
-  }
-  
-  this.get_state_td = function()
-  {
-    var div = document.getElementById(this.box_id);
-    if ( !div )
-      return false;
-    if ( !this.state )
-      return false;
-    var table = div.firstChild;
-    for ( var i = 1; i < table.childNodes.length; i++ )
-    {
-      // the table is DOM-constructed so no cruddy HTML hacks :-)
-      var child = table.childNodes[i];
-      var tn = child.firstChild.firstChild;
-      if ( tn.nodeValue == this.state )
-        return child.firstChild;
-    }
-    return false;
-  }
-  
-  this.focus_down = function()
+  for ( var i = 0; i < inputs.length; i++ )
   {
-    var state_td = this.get_state_td();
-    if ( !state_td )
-      return false;
-    if ( state_td.parentNode.nextSibling )
-    {
-      // Ooh boy, DOM stuff can be so complicated...
-      //   <tr>   →   <tr>
-      // ↑ <td>       <td> ↓
-      //   user       user
-      
-      var newstate = state_td.parentNode.nextSibling.firstChild.firstChild.nodeValue;
-      if ( !newstate )
-        return false;
-      this.state = newstate;
-      state_td.className = 'row1';
-      state_td.parentNode.nextSibling.firstChild.className = 'row2';
-      
-      // Exception - automatically scroll around if the item is off-screen
-      var height = $dynano(this.box_id).Height();
-      var top = $dynano(this.box_id).object.scrollTop;
-      var scroll_bottom = height + top;
-      
-      var td_top = $dynano(state_td.parentNode.nextSibling.firstChild).Top() - $dynano(this.box_id).Top();
-      var td_height = $dynano(state_td.parentNode.nextSibling.firstChild).Height();
-      var td_bottom = td_top + td_height;
-      
-      if ( td_bottom > scroll_bottom )
-      {
-        var scrollY = td_top - height + 2*td_height - 7;
-        // window.console.debug(scrollY);
-        $dynano(this.box_id).object.scrollTop = scrollY;
-        /*
-        var newtd = state_td.parentNode.nextSibling.firstChild;
-        var a = document.createElement('a');
-        var id = 'autofill' + Math.floor(Math.random() * 100000);
-        a.name = id;
-        a.id = id;
-        newtd.appendChild(a);
-        window.location.hash = '#' + id;
-        */
-        
-        // In firefox, scrolling like that makes the field get unfocused
-        $dynano(this.field_id).object.focus();
-      }
-    }
-    else
-    {
-      return false;
-    }
+    autofill_init_element(inputs[i]);
   }
+}
+
+addOnloadHook(autofill_onload);
+
+function AutofillUsername(element, event, allowanon)
+{
+  element.onkeyup = element.onkeydown = element.onkeypress = function(e) {};
   
-  this.focus_up = function()
-  {
-    var state_td = this.get_state_td();
-    if ( !state_td )
-      return false;
-    if ( state_td.parentNode.previousSibling && state_td.parentNode.previousSibling.firstChild.tagName != 'TH' )
-    {
-      // Ooh boy, DOM stuff can be so complicated...
-      //   <tr>   ←   <tr>
-      // ↓ <td>       <td> ↑
-      //   user       user
-      
-      var newstate = state_td.parentNode.previousSibling.firstChild.firstChild.nodeValue;
-      if ( !newstate )
-      {
-        return false;
-      }
-      this.state = newstate;
-      state_td.className = 'row1';
-      state_td.parentNode.previousSibling.firstChild.className = 'row2';
-      
-      // Exception - automatically scroll around if the item is off-screen
-      var top = $dynano(this.box_id).object.scrollTop;
-      
-      var td_top = $dynano(state_td.parentNode.previousSibling.firstChild).Top() - $dynano(this.box_id).Top();
-      
-      if ( td_top < top )
-      {
-        $dynano(this.box_id).object.scrollTop = td_top - 10;
-        /*
-        var newtd = state_td.parentNode.previousSibling.firstChild;
-        var a = document.createElement('a');
-        var id = 'autofill' + Math.floor(Math.random() * 100000);
-        a.name = id;
-        a.id = id;
-        newtd.appendChild(a);
-        window.location.hash = '#' + id;
-        */
-        
-        // In firefox, scrolling like that makes the field get unfocused
-        $dynano(this.field_id).object.focus();
-      }
-    }
-    else
-    {
-      $dynano(this.box_id).object.scrollTop = 0;
-      return false;
-    }
-  }
-  
-  this.destroy = function()
-  {
-    this.repeat = false;
-    var body = document.getElementsByTagName('body')[0];
-    var div = document.getElementById(this.box_id);
-    if ( !div )
-      return false;
-    setTimeout('var body = document.getElementsByTagName("body")[0]; body.removeChild(document.getElementById("'+div.id+'"));', 20);
-    // hackish workaround for divs that stick around past their welcoming period
-    for ( var i = 0; i < this.boxes.length; i++ )
-    {
-      var div = document.getElementById(this.boxes[i]);
-      if ( div )
-        setTimeout('var body = document.getElementsByTagName("body")[0]; var div = document.getElementById("'+div.id+'"); if ( div ) body.removeChild(div);', 20);
-      delete(this.boxes[i]);
-    }
-    this.boxes = new Array();
-    this.box_id = false;
-    this.state = false;
-  }
+  element.className = 'autofill username';
   
-  this.set = function(val)
-  {
-    var ta = document.getElementById(this.field_id);
-    if ( val )
-      ta.value = val;
-    else if ( this.state )
-      ta.value = this.state;
-    this.destroy();
-    findParentForm($dynano(this.field_id.object))._af_acting = false;
-  }
-  
-  this.sleep = function()
-  {
-    if ( this.box_id )
-    {
-      var div = document.getElementById(this.box_id);
-      div.style.display = 'none';
-    }
-    var el = $dynano(this.field_id).object;
-    var fr = findParentForm(el);
-    el._af_acting = false;
-  }
-  
-  this.wake = function()
-  {
-    if ( this.box_id )
-    {
-      var div = document.getElementById(this.box_id);
-      div.style.display = 'block';
-    }
-  }
-  
-  parent.onblur = function()
-  {
-    af_current = this.afobj;
-    window.setTimeout('if ( af_current ) af_current.sleep(); af_current = false;', 50);
-  }
-  
-  parent.onfocus = function()
-  {
-    af_current = this.afobj;
-    window.setTimeout('if ( af_current ) af_current.wake(); af_current = false;', 50);
-  }
-  
-  parent.afobj = this;
-  var frm = findParentForm(parent);
-  if ( frm.onsubmit )
-  {
-    frm.orig_onsubmit = frm.onsubmit;
-    frm.onsubmit = function(e)
-    {
-      if ( this._af_acting )
-        return false;
-      this.orig_onsubmit(e);
-    }
-  }
-  else
-  {
-    frm.onsubmit = function()
-    {
-      if ( this._af_acting )
-        return false;
-    }
-  }
-  
-  if ( parent.value.length < 3 )
-  {
-    this.destroy();
-    return false;
-  }
+  allowanon = allowanon ? true : false;
+  autofill_init_element(element, {
+      allow_anon: allowanon
+    });
 }
 
 function findParentForm(o)
--- a/includes/clientside/static/enano-lib-basic.js	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/static/enano-lib-basic.js	Sun Jun 22 18:13:59 2008 -0400
@@ -292,10 +292,10 @@
 var thefiles = [
   'dynano.js',
   'misc.js',
+  'l10n.js',
   'login.js',
   'admin-menu.js',
   'ajax.js',
-  'autocomplete.js',
   'autofill.js',
   'base64.js',
   'dropdown.js',
@@ -311,7 +311,6 @@
   'sliders.js',
   'toolbar.js',
   'rijndael.js',
-  'l10n.js',
   'template-compiler.js',
   'acl.js',
   'comments.js',
@@ -322,6 +321,9 @@
   'theme-manager.js',
   'rank-manager.js',
   'SpryEffects.js',
+  'SpryData.js',
+  'SpryJSONDataSet.js',
+  'SpryAutoSuggest.js',
   'loader.js'
 ];
 
--- a/includes/clientside/static/l10n.js	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/static/l10n.js	Sun Jun 22 18:13:59 2008 -0400
@@ -10,6 +10,7 @@
     return false;
   this.strings = enano_lang[lang_id];
   this.lang_id = lang_id;
+  this.placeholder = false;
   
   this.get = function(string_id, subst)
   {
@@ -48,7 +49,10 @@
   
 }
 
-var $lang;
+var $lang = {
+  get: function(t) { return t; },
+  placeholder: true
+};
 
 var language_onload = function()
 {
--- a/includes/clientside/static/loader.js	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/static/loader.js	Sun Jun 22 18:13:59 2008 -0400
@@ -1,14 +1,13 @@
 // Some final stuff - loader routines, etc.
 
+var onload_complete = false;
+
 function mdgInnerLoader(e)
 {
-  if(window.location.hash == '#comments') ajaxComments();
-  window.onkeydown=isKeyPressed;
-  window.onkeyup=function(e) { isKeyPressed(e); };
+  window.onkeydown = isKeyPressed;
+  window.onkeyup = function(e) { isKeyPressed(e); };
   Fat.fade_all();
   fadeInfoBoxes();
-  //initTextareas();
-  //buildSearchBoxes();
   jBoxInit();
   if(typeof (dbx_set_key) == 'function')
   {
@@ -17,17 +16,32 @@
   initSliders();
   runOnloadHooks(e);
 }
+
+// if some dumb plugin set an onload function, preserve it 
 var ld;
-if(window.onload) ld = window.onload;
-else ld = function() {return;};
-function enano_init(e) {
+if ( window.onload)
+{
+  ld = window.onload;
+}
+else
+{
+  ld = function() {return;};
+}
+
+// Enano's main init function.
+function enano_init(e)
+{
   if ( typeof(ld) == 'function' )
   {
     ld(e);
   }
   mdgInnerLoader(e);
+  
+  // we're loaded; set flags to true
+  onload_complete = true;
 }
 
+// don't init the page if less than IE6
 if ( typeof(KILL_SWITCH) == 'boolean' && !KILL_SWITCH )
 {
   window.onload = enano_init;
--- a/includes/clientside/static/misc.js	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/clientside/static/misc.js	Sun Jun 22 18:13:59 2008 -0400
@@ -639,4 +639,17 @@
   return haystack.split(needle);
 }
 
+/**
+ * Equivalent to PHP's in_array function.
+ */
+
+function in_array(needle, haystack)
+{
+  for(var i in haystack)
+  {
+    if(haystack[i] == needle) return i;
+  }
+  return false;
+}
+
 addOnloadHook(expander_onload);
--- a/includes/functions.php	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/functions.php	Sun Jun 22 18:13:59 2008 -0400
@@ -2837,6 +2837,63 @@
 }
 
 /**
+ * Replaces all given occurences of needle in haystack, case insensitively.
+ * @param string Needle
+ * @param string Thread
+ * @param string Haystack
+ * @return string
+ */
+
+function str_replace_i($needle, $thread, $haystack)
+{
+  $needle_len = strlen($needle);
+  $haystack_len = strlen($haystack);
+  for ( $i = 0; $i < $haystack_len; $i++ )
+  {
+    $test = substr($haystack, $i, $needle_len);
+    if ( strtolower($test) == strtolower($needle) )
+    {
+      // Got it!
+      $upto = substr($haystack, 0, $i);
+      $from = substr($haystack, ( $i + $needle_len ));
+      $haystack = "{$upto}{$thread}{$from}";
+      $haystack_len = strlen($haystack);
+      $i = $i + strlen($thread);
+    }
+  }
+  return $haystack;
+}
+
+/**
+ * Highlights a term in a string.
+ * @param string Needle (term to highlight)
+ * @param string Haystack (search string)
+ * @param string Starting tag (<b>)
+ * @param string Ending tag (</b>)
+ * @return string
+ */
+
+function highlight_term($needle, $haystack, $start_tag = '<b>', $end_tag = '</b>')
+{
+  $needle_len = strlen($needle);
+  $haystack_len = strlen($haystack);
+  for ( $i = 0; $i < $haystack_len; $i++ )
+  {
+    $test = substr($haystack, $i, $needle_len);
+    if ( strtolower($test) == strtolower($needle) )
+    {
+      // Got it!
+      $upto = substr($haystack, 0, $i);
+      $from = substr($haystack, ( $i + $needle_len ));
+      $haystack = "{$upto}{$start_tag}{$test}{$end_tag}{$from}";
+      $haystack_len = strlen($haystack);
+      $i = $i + strlen($needle) + strlen($start_tag) + strlen($end_tag);
+    }
+  }
+  return $haystack;
+}
+
+/**
  * From http://us2.php.net/urldecode - decode %uXXXX
  * @param string The urlencoded string
  * @return string
--- a/includes/template.php	Wed Jun 18 22:43:16 2008 -0400
+++ b/includes/template.php	Sun Jun 22 18:13:59 2008 -0400
@@ -47,6 +47,7 @@
   function __construct()
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
+    
     $this->tpl_bool    = Array();
     $this->tpl_strings = Array();
     $this->sidebar_extra = '';
@@ -1166,14 +1167,6 @@
       header('Content-type: application/xhtml+xml');
     }
     
-    // Reassign one important variable that can need to be changed after init_vars(): ADDITIONAL_HEADERS
-    if ( !empty($this->additional_headers) )
-    {
-      $this->assign_vars(array(
-          'ADDITIONAL_HEADERS' => $this->additional_headers
-        ));
-    }
-    
     $headers_sent = true;
     if(!defined('ENANO_HEADERS_SENT'))
       define('ENANO_HEADERS_SENT', '');
@@ -1825,7 +1818,7 @@
   function username_field($name, $value = false)
   {
     $randomid = md5( time() . microtime() . mt_rand() );
-    $text = '<input name="'.$name.'" onkeyup="new AutofillUsername(this);" autocomplete="off" type="text" size="30" id="userfield_'.$randomid.'"';
+    $text = '<input name="'.$name.'" class="autofill username" type="text" size="30" id="userfield_'.$randomid.'"';
     if($value) $text .= ' value="'.$value.'"';
     $text .= ' />';
     return $text;
@@ -1840,24 +1833,9 @@
   function pagename_field($name, $value = false)
   {
     $randomid = md5( time() . microtime() . mt_rand() );
-    $text = '<input name="'.$name.'" onkeyup="ajaxPageNameComplete(this)" type="text" size="30" id="pagefield_'.$randomid.'"';
+    $text = '<input name="'.$name.'" class="autofill page" type="text" size="30" id="pagefield_'.$randomid.'"';
     if($value) $text .= ' value="'.$value.'"';
     $text .= ' />';
-    $text .= '<script type="text/javascript">
-        var inp = document.getElementById(\'pagefield_' . $randomid . '\');
-        var f = get_parent_form(inp);
-        if ( f )
-        {
-          if ( typeof(f.onsubmit) != \'function\' )
-          {
-            f.onsubmit = function() {
-              if ( !submitAuthorized )
-              {
-                return false;
-              }
-            }
-          }
-        }</script>';
     return $text;
   }
   
--- a/plugins/SpecialAdmin.php	Wed Jun 18 22:43:16 2008 -0400
+++ b/plugins/SpecialAdmin.php	Sun Jun 22 18:13:59 2008 -0400
@@ -2019,6 +2019,7 @@
           if ( ajax.readyState == 4 && ajax.status == 200 ) {
             document.getElementById('ajaxPageContainer').innerHTML = ajax.responseText;
             fadeInfoBoxes();
+            autofill_onload();
           }
         });
     }
--- a/plugins/SpecialPageFuncs.php	Wed Jun 18 22:43:16 2008 -0400
+++ b/plugins/SpecialPageFuncs.php	Sun Jun 22 18:13:59 2008 -0400
@@ -67,6 +67,13 @@
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
+    
+    $paths->add_page(Array(
+      \'name\'=>\'specialpage_autofill\',
+      \'urlname\'=>\'Autofill\',
+      \'namespace\'=>\'Special\',
+      \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
+      ));
     ');
 
 // function names are IMPORTANT!!! The name pattern is: page_<namespace ID>_<page URLname, without namespace>
@@ -828,4 +835,73 @@
 
 $plugins->attachHook('compile_template', 'sidebar_add_tag_cloud();');
 
+function page_Special_Autofill()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  global $lang;
+  
+  header('Content-type: text/javascript');
+  
+  $dataset = array();
+  if ( isset($_GET['type']) )
+  {
+    switch($_GET['type'])
+    {
+      case 'username':
+        if ( isset($_GET['userinput']) && strlen($_GET['userinput']) >= 3 )
+        {
+          $search = '%' . escape_string_like($_GET['userinput']) . '%';
+          $q = $db->sql_query('SELECT username FROM ' . table_prefix . "users WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) LIKE '$search' AND user_id > 1");
+          if ( !$q )
+            $db->die_json();
+          
+          while ( $row = $db->fetchrow() )
+          {
+            $key = array(
+              'name' => $row['username'],
+              'name_highlight' => highlight_term($_GET['userinput'], $row['username'], '<b>', '</b>')
+            );
+            $key = array_merge($key, $session->get_user_rank($row['username']));
+            $key['rank_title'] = $lang->get($key['rank_title']);
+            $dataset[] = $key;
+          }
+        }
+        break;
+      case 'page':
+        if ( isset($_GET['userinput']) && strlen($_GET['userinput']) >= 3 )
+        {
+          $search = '%' . escape_string_like($_GET['userinput']) . '%';
+          $q = $db->sql_query('SELECT urlname, namespace, name FROM ' . table_prefix . "users\n"
+                            . "  WHERE (\n"
+                            . "       " . ENANO_SQLFUNC_LOWERCASE . "(urlname) LIKE '$search'\n"
+                            . "    OR " . ENANO_SQLFUNC_LOWERCASE . "(name)    LIKE '$search'\n"
+                            . "  ) AND user_id > 1");
+          if ( !$q )
+            $db->die_json();
+          
+          while ( $row = $db->fetchrow() )
+          {
+            $pathskey = ( isset($paths->nslist[$row['namespace']]) ? $paths->nslist[$row['namespace']] : $row['namespace'] . substr($paths->nslist['Special'], -1) ) . $row['urlname'];
+            $key = array(
+              'page_id' => $pathskey,
+              'pid_highlight'  => highlight_term($_GET['userinput'], dirtify_page_id($pathskey), '<b>', '</b>'),
+              'name_highlight' => highlight_term($_GET['userinput'], $row['name'], '<b>', '</b>')
+            );
+            $dataset[] = $key;
+          }
+        }
+        break;
+      default:
+        $code = $plugins->setHook('autofill_json_request');
+        foreach ( $code as $cmd )
+        {
+          eval($cmd);
+        }
+        break;
+    }
+  }
+  
+  echo enano_json_encode($dataset);
+}
+
 ?>
\ No newline at end of file