/* SpryData.js - Revision: Spry Preview Release 1.4 */

// 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 = {};

//////////////////////////////////////////////////////////////////////
//
// Spry.Utils
//
//////////////////////////////////////////////////////////////////////

if (!Spry.Utils) Spry.Utils = {};

Spry.Utils.msProgIDs = ["MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];

Spry.Utils.createXMLHttpRequest = function()
{
var req = null;
try
{
if (window.XMLHttpRequest)
req = new XMLHttpRequest();
else 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);
}
}
}
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) { req = null; Spry.Debug.reportError("Exception caught while loading " + url + ": " + e); }

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 = $(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)
{
var method = (opts && opts.method) ? opts.method : "GET";
Spry.Utils.loadURL(method, url, false, function(req)
{
Spry.Utils.setInnerHTML(ele, req.xhRequest.responseText);
if (finishFunc)
finishFunc(ele, url);
}, opts);
};

Spry.Utils.addEventListener = function(element, eventType, handler, capture)
{
try
{
element = $(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 = $(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.eval = function(str)
{
// Call this method from your JS function when
// you don't want the JS expression to access or
// interfere with any local variables in your JS
// function.

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 /* Node.TEXT_NODE */ && d.firstChild.nextSibling == null)
str = d.firstChild.data;
else
{
// Hmmm, innerHTML processing of str produced content
// we weren't expecting, so just replace entities we
// expect folks will use in node attributes that contain
// JavaScript.
str = str.replace(/&lt;/, "<");
str = str.replace(/&gt;/, ">");
str = str.replace(/&quot;/, "\"");
str = str.replace(/&amp;/, "&");
}
return str;
};

Spry.Utils.fixupIETagAttributes = function(inStr)
{
var outStr = "";

// Break the tag string into 3 pieces.

var tagStart = inStr.match(/^<[^\s>]+\s*/)[0];
var tagEnd = inStr.match(/\s*\/?>$/)[0];
var tagAttrs = inStr.replace(/^<[^\s>]+\s*|\s*\/?>/g, "");

// Write out the start of the tag.
outStr += tagStart;

// If the tag has attributes, parse it out manually to avoid accidentally fixing up
// attributes that contain JavaScript expressions.

if (tagAttrs)
{
var startIndex = 0;
var endIndex = 0;

while (startIndex < tagAttrs.length)
{
// Find the '=' char of the attribute.
while (tagAttrs.charAt(endIndex) != '=' && endIndex < tagAttrs.length)
++endIndex;

// If we are at the end of the string, just write out what we've
// collected.

if (endIndex >= tagAttrs.length)
{
outStr += tagAttrs.substring(startIndex, endIndex);
break;
}

// Step past the '=' character and write out what we've
// collected so far.

++endIndex;
outStr += tagAttrs.substring(startIndex, endIndex);
startIndex = endIndex;

if (tagAttrs.charAt(endIndex) == '"' || tagAttrs.charAt(endIndex) == "'")
{
// Attribute is quoted. Advance us past the quoted value!
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
{
// This attribute value wasn't quoted! Wrap it with quotes and
// write out everything till we hit a space, or the end of the
// string.

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;

// Write out the end of the tag.
return outStr;
}

Spry.Utils.fixUpIEInnerHTML = function(inStr)
{
var outStr = "";

// Create a regular expression that will match:
//     <!--
//     <![CDATA[
//     <tag>
//     -->
//     ]]>
//     ]]&gt;   // Yet another workaround for an IE innerHTML bug.
//
// The idea here is that we only want to fix up attribute values on tags that
// are not in any comments or CDATA.

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)
{
// We found a match but it's not at the start of the inStr.
// Create a string token for everything that precedes the match.
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
{
// Attempt to parse the string using the IE method.

var xmlDOMObj = new ActiveXObject("Microsoft.XMLDOM");
xmlDOMObj.async = false;
xmlDOMObj.loadXML(str);
xmlDoc = xmlDOMObj;
}
catch (e)
{
// The IE method didn't work. Try the Mozilla way.

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)
{
// Create a JSON representation of a given object.

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.addClassName = function(ele, className)
{
ele = $(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 = $(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.getFirstChildWithNodeName = function(node, nodeName)
{
var child = node.firstChild;

while (child)
{
if (child.nodeName == nodeName)
return child;
child = child.nextSibling;
}

return null;
};

Spry.Utils.nodeContainsElementNode = function(node)
{
if (node)
{
node = node.firstChild;

while (node)
{
if (node.nodeType == 1 /* Node.ELEMENT_NODE */)
return true;

node = node.nextSibling;
}
}
return false;
};

Spry.Utils.getNodeText = function(node)
{
var txt = "";

if (!node)
return;

try
{
var child = node.firstChild;

while (child)
{
try
{
if (child.nodeType == 3 /* TEXT_NODE */)
txt += Spry.Utils.encodeEntities(child.data);
else if (child.nodeType == 4 /* CDATA_SECTION_NODE */)
txt += child.data;
} catch (e) { Spry.Debug.reportError("Spry.Utils.getNodeText() exception caught: " + e + "\n"); }

child = child.nextSibling;
}
}
catch (e) { Spry.Debug.reportError("Spry.Utils.getNodeText() exception caught: " + e + "\n"); }

return txt;
};

Spry.Utils.CreateObjectForNode = function(node)
{
if (!node)
return null;

var obj = null;
var i = 0;
var attr = null;

try
{
for (i = 0; i < node.attributes.length; i++)
{
attr = node.attributes[i];
if (attr && attr.nodeType == 2 /* Node.ATTRIBUTE_NODE */)
{
if (!obj)
{
obj = new Object();
if (!obj)
{
Spry.Debug.reportError("Spry.Utils.CreateObjectForNode(): Object creation failed!");
return null;
}
}

obj["@" + attr.name] = attr.value;
}
}
}
catch (e)
{
Spry.Debug.reportError("Spry.Utils.CreateObjectForNode() caught exception while accessing attributes: " + e + "\n");
}

var child = node.firstChild;

if (child && !child.nextSibling && child.nodeType != 1 /* Node.ELEMENT_NODE */)
{
// We have a single child and it's not an element. It must
// be the text value for this node. Add it to the record set and
// give it the column the same name as the node.

if (!obj)
{
obj = new Object();
if (!obj)
{
Spry.Debug.reportError("Spry.Utils.CreateObjectForNode(): Object creation failed!");
return null;
}
}

obj[node.nodeName] = Spry.Utils.getNodeText(node);
}

while (child)
{
// Add the text value for each child element. Note that
// We skip elements that have element children (sub-elements)
// because we don't handle multi-level data sets right now.

if (child.nodeType == 1 /* Node.ELEMENT_NODE */)
{
if (!Spry.Utils.nodeContainsElementNode(child))
{
var txt = Spry.Utils.getNodeText(child);
if (!obj)
{
obj = new Object();
if (!obj)
{
Spry.Debug.reportError("Spry.Utils.CreateObjectForNode(): Object creation failed!");
return null;
}
}

obj[child.nodeName] = txt;

// Now add properties for any attributes on the child. The property
// name will be of the form "<child.nodeName>/@<attr.name>".
try
{
var namePrefix = child.nodeName + "/@";

for (i = 0; i < child.attributes.length; i++)
{
attr = child.attributes[i];
if (attr && attr.nodeType == 2 /* Node.ATTRIBUTE_NODE */)
obj[namePrefix + attr.name] = attr.value;
}
}
catch (e)
{
Spry.Debug.reportError("Spry.Utils.CreateObjectForNode() caught exception while accessing attributes: " + e + "\n");
}

}
// else Spry.Debug.trace("WARNING: Skipping '" + child.nodeName + "' node! Multi-level data sets are not supported right now!\n");
}

child = child.nextSibling;
}

return obj;
};

Spry.Utils.getRecordSetFromXMLDoc = function(xmlDoc, path)
{
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; };

// Use the XPath library to find the nodes that will
// make up our data set. The result should be an array
// of subtrees that we need to flatten.

var ctx = new ExprContext(xmlDoc);
var pathExpr = xpathParse(path);
var e = pathExpr.evaluate(ctx);

// XXX: Note that we should check the result type of the evaluation
// just in case it's a boolean, string, or number value instead of
// a node set.

var nodeArray = e.nodeSetValue();

var isDOMNodeArray = true;

if (nodeArray && nodeArray.length > 0)
isDOMNodeArray = nodeArray[0].nodeType != 2 /* Node.ATTRIBUTE_NODE */;

var nextID = 0;

// We now have the set of nodes that make up our data set
// so process each one.

for (var i = 0; i < nodeArray.length; i++)
{
var rowObj = null;

if (isDOMNodeArray)
rowObj = Spry.Utils.CreateObjectForNode(nodeArray[i]);
else // Must be a Node.ATTRIBUTE_NODE array.
{
rowObj = new Object;
rowObj["@" + nodeArray[i].name] = nodeArray[i].value;
}

if (rowObj)
{
// We want to make sure that every row has a unique ID and since we
// we don't know which column, if any, in this recordSet is a unique
// identifier, we generate a unique ID ourselves and store it under
// the ds_RowID column in the row object.

rowObj['ds_RowID'] = nextID++;
recordSet.dataHash[rowObj['ds_RowID']] = rowObj;
recordSet.data.push(rowObj);
}
}

return recordSet;
};

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)
{
// Multiple selection is not enabled, so clear any
// selected elements from our list.

this.clearSelection();
}
else
{
// Multiple selection is enabled, so check to see if element
// is already in the array. If it is, make sure the className
// is the className that was passed in.

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;
}
}
}

// Add the element to our list of selected elements.

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();
};

//////////////////////////////////////////////////////////////////////
//
// Define Prototype's $() convenience function.
//
//////////////////////////////////////////////////////////////////////

function $()
{
var elements = new Array();

for (var i = 0; i < arguments.length; i++)
{
var element = arguments[i];
if (typeof element == 'string')
element = document.getElementById(element);

if (arguments.length == 1)
return element;

elements.push(element);
}

return elements;
}

Spry.Utils.Notifier = function()
{
this.observers = [];
this.suppressNotifications = 0;
};

Spry.Utils.Notifier.prototype.addObserver = function(observer)
{
if (!observer)
return;

// Make sure the observer isn't already on the list.

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 = {};
Spry.Debug.enableTrace = true;
Spry.Debug.debugWindow = null;

Spry.Debug.createDebugWindow = function()
{
if (!Spry.Debug.enableTrace || Spry.Debug.debugWindow)
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.style.opacity = "0.5";
// div.style.filter = "alpha(opacity=50)";
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)
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 = {};
Spry.Data.regionsArray = {};

Spry.Data.initRegions = function(rootNode)
{
if (!rootNode)
rootNode = document.body;

var lastRegionFound = null;

var regions = Spry.Utils.getNodesByFunc(rootNode, function(node)
{
try
{
if (node.nodeType != 1 /* Node.ELEMENT_NODE */)
return false;

// Region elements must have an spryregion attribute with a
// non-empty value. An id attribute is also required so we can
// reference the region by name if necessary.

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)
{
// The node is missing an id attribute so add one.
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;

for (i = 0; i < regions.length; i++)
{
var rgn = regions[i];

var isDetailRegion = false;

// Get the region name.
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;
}

// Remove the spry:region or spry:detailregion attribute so it doesn't appear in
// the output generated by our processing of the dynamic region.
rgn.attributes.removeNamedItem(attr.nodeName);

// Remove the hiddenRegionCSS class from the rgn.
Spry.Utils.removeClassName(rgn, Spry.Data.Region.hiddenRegionClassName);

// Get the DataSets that should be bound to the region.
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 = {};

// Check if there are any attributes on the region node that remap
// the default states.

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;

// Find all of the processing instruction regions in the region.
// Insert comments around the regions we find so we can identify them
// easily when tokenizing the region html string.

var piRegions = Spry.Utils.getNodesByFunc(rgn, function(node)
{
try
{
if (node.nodeType == 1 /* ELEMENT_NODE */)
{
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 this is a spry:state processing instruction, record the state name
// so we know that we should re-generate the region if we ever see that state.

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;
});

// Get the data in the region.
dataStr = rgn.innerHTML;

// Argh! IE has an innerHTML bug where it will remove the quotes around any
// attribute value that it thinks is a single word. This includes removing quotes
// around our data references which is problematic since a single data reference
// can be replaced with multiple words. If we are running in IE, we have to call
// fixUpIEInnerHTML to get around this problem.

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)
{
// Clear the region.
rgn.innerHTML = "";
}

// Create a Spry.Data.Region object for this region.
var region = new Spry.Data.Region(rgn, name, isDetailRegion, dataStr, dataSets, regionStates, regionStateMap, hasBehaviorAttributes);
Spry.Data.regionsArray[region.name] = region;
}

Spry.Data.updateAllRegions();
};

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.DataSet
//
//////////////////////////////////////////////////////////////////////

Spry.Data.DataSet = function()
{
Spry.Utils.Notifier.call(this);

this.name = "";
this.internalID = Spry.Data.DataSet.nextDataSetID++;
this.curRowID = 0;
this.data = null;
this.unfilteredData = null;
this.dataHash = null;
this.columnTypes = new Object;
this.filterFunc = null;		// non-destructive filter function
this.filterDataFunc = null;	// destructive filter function

this.distinctOnLoad = false;
this.sortOnLoad = null;
this.sortOrderOnLoad = "ascending";
this.keepSorted = false;

this.dataWasLoaded = false;
this.pendingRequest = null;

this.lastSortColumns = [];
this.lastSortOrder = "";

this.loadIntervalID = 0;
};

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()
{
// XXX: Deprecated.
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.loadData = function()
{
// The idea here is that folks using the base class DataSet directly
// would change the data in the DataSet manually and then call loadData()
// to fire off an async notifications to say that it was ready for consumption.
//
// Firing off data changed notificataions synchronously from this method
// can wreak havoc with complicated master/detail regions that use data sets
// that have master/detail relationships with other data sets. Our data set
// logic already handles async data loading nicely so we use a timer to fire
// off the data changed notification to insure that it happens after this
// function is finished and the JS stack unwinds.
//
// Other classes that derive from this class and load data synchronously
// inside their loadData() implementation should also fire off an async
// notification in this same manner to avoid this same problem.

var self = this;

this.pendingRequest = new Object;
this.dataWasLoaded = false;
this.pendingRequest.timer = setTimeout(function()
{
self.pendingRequest = null;
self.dataWasLoaded = true;

if (self.filterDataFunc)
self.filterData(self.filterDataFunc, true);

if (self.distinctOnLoad)
self.distinct();

if (self.keepSorted && self.getSortColumn())
self.sort(self.lastSortColumns, self.lastSortOrder)
else if (self.sortOnLoad)
self.sort(self.sortOnLoad, self.sortOrderOnLoad);

if (self.filterFunc)
self.filter(self.filterFunc, true);

self.notifyObservers("onDataChanged");
}, 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)
{
if (row && this.data && this.data.length)
{
var numRows = this.data.length;
for (var i = 0; i < numRows; i++)
{
if (this.data[i] == row)
return i;
}
}

return 0;
};

Spry.Data.DataSet.prototype.getCurrentRowNumber = function()
{
return this.getRowNumber(this.getCurrentRow());
};

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(columnName, columnType)
{
if (columnName)
this.columnTypes[columnName] = columnType;
};

Spry.Data.DataSet.prototype.getColumnType = function(columnName)
{
if (this.columnTypes[columnName])
return this.columnTypes[columnName];
return "string";
};

Spry.Data.DataSet.prototype.distinct = function()
{
if (this.data)
{
var oldData = this.data;
this.data = [];
this.dataHash = {};

var alreadySeenHash = {};
var i = 0;

for (var i = 0; i < oldData.length; i++)
{
var rec = oldData[i];
var hashStr = "";
for (var recField in rec)
{
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;
}
}
}
};

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)
{
// columnNames can be either the name of a column to
// sort on, or an array of column names, but it can't be
// null/undefined.

if (!columnNames)
return;

// If only one column name was specified for sorting, do a
// secondary sort on ds_RowID so we get a stable sort order.

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); // Copy the array.
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){ return a[prop]-b[prop]; };
else // order == "descending"
sortfunc = function(a, b){ return b[prop]-a[prop]; };
}
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 // order == "descending"
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 // type == "string"
{
if (order == "ascending")
sortfunc = function(a, b){
var tA = a[prop].toString();
var tB = b[prop].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;
}else{
return -1;
}
};
else // order == "descending"
sortfunc = function(a, b){
var tA = a[prop].toString();
var tB = b[prop].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;
}else{
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)
{
// This is a destructive filter function.

var dataChanged = false;

if (!filterFunc)
{
// Caller wants to remove the filter.

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)
{
// This is a non-destructive filter function.

var dataChanged = false;

if (!filterFunc)
{
if (this.filterFunc && this.unfilteredData)
{
// Caller wants to remove the filter. Restore the unfiltered
// data and trigger a data changed notification.

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.XMLDataSet
//
//////////////////////////////////////////////////////////////////////

Spry.Data.XMLDataSet = function(dataSetURL, dataSetPath, dataSetOptions)
{
// Call the constructor for our DataSet base class so that
// our base class properties get defined. We'll call setOptions
// manually after we set up our XMLDataSet properties.

Spry.Data.DataSet.call(this);

// XMLDataSet Properties:

this.url = dataSetURL;
this.xpath = dataSetPath;
this.doc = null;
this.dataSetsForDataRefStrings = new Array;
this.hasDataRefStrings = false;
this.useCache = true;

// Create a loadURL request object to store any load options
// the caller specified. We'll fill in the URL at the last minute
// before we make the actual load request because our URL needs
// to be processed at the last possible minute in case it contains
// data references.

this.requestInfo = new Spry.Utils.loadURL.Request();
this.requestInfo.extractRequestOptions(dataSetOptions, true);

// If the caller wants to use "POST" to fetch the data, but didn't
// provide the content type, default to x-www-form-urlencoded.

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.Utils.setOptions(this, dataSetOptions, true);

this.recalculateDataSetDependencies();

if (this.loadInterval > 0)
this.startLoadInterval(this.loadInterval);
}; // End of Spry.Data.XMLDataSet() constructor.

Spry.Data.XMLDataSet.prototype = new Spry.Data.DataSet();
Spry.Data.XMLDataSet.prototype.constructor = Spry.Data.XMLDataSet;

Spry.Data.XMLDataSet.prototype.recalculateDataSetDependencies = function()
{
this.hasDataRefStrings = false;

if (!this.url)
return;

// Clear all old callbacks that may have been registered.

var i = 0;
for (i = 0; i < this.dataSetsForDataRefStrings.length; i++)
{
var ds = this.dataSetsForDataRefStrings[i];
if (ds)
ds.removeObserver(this);
}

// Now run through the strings that may contain data references and figure
// out what data sets they require. Note that the data references in these
// strings must be fully qualified with a data set name. (ex: {dsDataSetName::columnName})

this.dataSetsForDataRefStrings = new Array();

var regionStrs = [ this.url, this.xpath, this.requestInfo.postData ];

// If postData exists, and is a string, we want to check it for data refs.
var postData = this.requestInfo.postData;
if (postData && (typeof postData) == "string")
regionStrs.push(postData);

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])
{
try { ds = eval(dsName); } catch (e) { ds = null; }

if (dsName && ds)
{
// The dataSetsForDataRefStrings array serves as both an
// array of data sets and a hash lookup by name.

this.dataSetsForDataRefStrings[dsName] = ds;
this.dataSetsForDataRefStrings[dsCount++] = ds;
this.hasDataRefStrings = true;
}
}
}
}
}

// Set up observers on any data sets our URL depends on.

for (i = 0; i < this.dataSetsForDataRefStrings.length; i++)
{
var ds = this.dataSetsForDataRefStrings[i];
ds.addObserver(this);
}
};

Spry.Data.XMLDataSet.prototype.attemptLoadData = function()
{
// We only want to trigger a load when all of our data sets have data!
for (var i = 0; i < this.dataSetsForDataRefStrings.length; i++)
{
var ds = this.dataSetsForDataRefStrings[i];
if (ds.getLoadDataRequestIsPending() || !ds.getDataWasLoaded())
return;
}

this.loadData();
};

Spry.Data.XMLDataSet.prototype.onCurrentRowChanged = function(ds, data)
{
this.attemptLoadData();
};

Spry.Data.XMLDataSet.prototype.onPostSort = function(ds, data)
{
this.attemptLoadData();
};

Spry.Data.XMLDataSet.prototype.onDataChanged = function(ds, data)
{
this.attemptLoadData();
};

Spry.Data.XMLDataSet.prototype.loadData = function()
{
if (!this.url || !this.xpath)
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())
{
// Kick off the load of this data set!
ds.loadData();
allDataSetsReady = false;
}
}

// If our data sets aren't ready, just return. We'll
// get called back to load our data when they are all
// done.

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;

// At this point the url should've been processed if it contained any
// data references. Set the url of the requestInfo structure and pass it
// to LoadManager.loadData().

var req = this.requestInfo.clone();
req.url = url;
req.postData = postData;

this.pendingRequest = new Object;
this.pendingRequest.data = Spry.Data.XMLDataSet.LoadManager.loadData(req, this, this.useCache);
};

Spry.Data.XMLDataSet.prototype.cancelLoadData = function()
{
if (this.pendingRequest)
{
Spry.Data.XMLDataSet.LoadManager.cancelLoadData(this.pendingRequest.data, this);
this.pendingRequest = null;
}
};

Spry.Data.XMLDataSet.prototype.getURL = function() { return this.url; };
Spry.Data.XMLDataSet.prototype.setURL = function(url, requestOptions)
{
if (this.url == url)
return;
this.url = url;

if (requestOptions)
this.requestInfo.extractRequestOptions(requestOptions);

this.cancelLoadData();
this.recalculateDataSetDependencies();
this.dataWasLoaded = false;
};
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.setDataFromDoc(this.doc);
}
};

Spry.Data.XMLDataSet.prototype.setDataFromDoc = function(doc)
{
this.pendingRequest = null;

var rs = null;

rs = Spry.Utils.getRecordSetFromXMLDoc(doc, Spry.Data.Region.processDataRefString(null, this.xpath, this.dataSetsForDataRefStrings));

if (!rs)
{
Spry.Debug.reportError("Spry.Data.XMLDataSet.setDataFromDoc() failed to create dataSet '" + this.name + "'for '" + this.xpath + "' - " + this.url + "\n");
return;
}

this.doc = rs.xmlDoc;
this.data = rs.data;
this.dataHash = rs.dataHash;
this.dataWasLoaded = (this.doc != null);

// If there is a data filter installed, run it.

if (this.filterDataFunc)
this.filterData(this.filterDataFunc, true);

// If the distinct flag was set, run through all the records in the recordset
// and toss out any that are duplicates.

if (this.distinctOnLoad)
this.distinct();

// If sortOnLoad was set, sort the data based on the columns
// specified in sortOnLoad.

if (this.keepSorted && this.getSortColumn())
this.sort(this.lastSortColumns, this.lastSortOrder)
else if (this.sortOnLoad)
this.sort(this.sortOnLoad, this.sortOrderOnLoad);

// If there is a view filter installed, run it.

if (this.filterFunc)
this.filter(this.filterFunc, true);

// The default "current" row is the first row of the data set.
if (this.data && this.data.length > 0)
this.curRowID = this.data[0]['ds_RowID'];
else
this.curRowID = 0;

this.notifyObservers("onPostLoad");
this.notifyObservers("onDataChanged");
};

Spry.Data.XMLDataSet.prototype.onRequestResponse = function(cachedRequest, req)
{
this.setDataFromDoc(cachedRequest.doc);
};

Spry.Data.XMLDataSet.prototype.onRequestError = function(cachedRequest, req)
{
this.notifyObservers("onLoadError", req);
// Spry.Debug.reportError("Spry.Data.XMLDataSet.LoadManager.CachedRequest.loadDataCallback(" + req.xhRequest.status + ") failed to load: " + req.url + "\n");
};

Spry.Data.XMLDataSet.LoadManager = {};
Spry.Data.XMLDataSet.LoadManager.cache = [];

Spry.Data.XMLDataSet.LoadManager.CachedRequest = function(reqInfo)
{
Spry.Utils.Notifier.call(this);

this.reqInfo = reqInfo;
this.doc = null;
this.timer = null;
this.state = Spry.Data.XMLDataSet.LoadManager.CachedRequest.NOT_LOADED;
};

Spry.Data.XMLDataSet.LoadManager.CachedRequest.prototype = new Spry.Utils.Notifier();
Spry.Data.XMLDataSet.LoadManager.CachedRequest.prototype.constructor = Spry.Data.XMLDataSet.LoadManager.CachedRequest;

Spry.Data.XMLDataSet.LoadManager.CachedRequest.NOT_LOADED      = 1;
Spry.Data.XMLDataSet.LoadManager.CachedRequest.LOAD_REQUESTED  = 2;
Spry.Data.XMLDataSet.LoadManager.CachedRequest.LOAD_FAILED     = 3;
Spry.Data.XMLDataSet.LoadManager.CachedRequest.LOAD_SUCCESSFUL = 4;

Spry.Data.XMLDataSet.LoadManager.CachedRequest.prototype.loadDataCallback = function(req)
{
if (req.xhRequest.readyState != 4)
return;

var xmlDoc = req.xhRequest.responseXML;

if (req.xhRequest.status != 200)
{
if (req.xhRequest.status == 0)
{
// The page that is attempting to load data was probably loaded with
// a file:// url. Mozilla based browsers will actually provide the complete DOM
// tree for the data, but IE provides an empty document node so try to parse
// the xml text manually to create a dom tree we can use.

if (req.xhRequest.responseText && (!xmlDoc || !xmlDoc.firstChild))
xmlDoc = Spry.Utils.stringToXMLDoc(req.xhRequest.responseText);
}
}

if (!xmlDoc  || !xmlDoc.firstChild || xmlDoc.firstChild.nodeName == "parsererror")
{
this.state = Spry.Data.XMLDataSet.LoadManager.CachedRequest.LOAD_FAILED;
this.notifyObservers("onRequestError", req);
this.observers.length = 0; // Clear the observers list.
return;
}

this.doc = xmlDoc;
this.state = Spry.Data.XMLDataSet.LoadManager.CachedRequest.LOAD_SUCCESSFUL;

// Notify all of the cached request's observers!
this.notifyObservers("onRequestResponse", req);

// Clear the observers list.
this.observers.length = 0;
};

Spry.Data.XMLDataSet.LoadManager.CachedRequest.prototype.loadData = function()
{
// IE will synchronously fire our loadDataCallback() during the call
// to an async Spry.Utils.loadURL() if the data for the url is already
// in the browser's local cache. This can wreak havoc with complicated master/detail
// regions that use data sets that have master/detail relationships with other
// data sets. Our data set logic already handles async data loading nicely so we
// use a timer to fire off the async Spry.Utils.loadURL() call to insure that any
// data loading happens asynchronously after this function is finished.

var self = this;
this.cancelLoadData();
this.doc = null;
this.state = Spry.Data.XMLDataSet.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.XMLDataSet.LoadManager.CachedRequest.prototype.cancelLoadData = function()
{
if (this.state == Spry.Data.XMLDataSet.LoadManager.CachedRequest.LOAD_REQUESTED)
{
if (this.timer)
{
this.timer.clearTimeout();
this.timer = null;
}

this.doc = null;
this.state = Spry.Data.XMLDataSet.LoadManager.CachedRequest.NOT_LOADED;
}
};

Spry.Data.XMLDataSet.LoadManager.getCacheKey = function(reqInfo)
{
return reqInfo.method + "::" + reqInfo.url + "::" + reqInfo.postData + "::" + reqInfo.username;
};

Spry.Data.XMLDataSet.LoadManager.loadData = function(reqInfo, ds, useCache)
{
if (!reqInfo)
return null;

var cacheObj = null;
var cacheKey = null;

if (useCache)
{
cacheKey = Spry.Data.XMLDataSet.LoadManager.getCacheKey(reqInfo);
cacheObj = Spry.Data.XMLDataSet.LoadManager.cache[cacheKey];
}

if (cacheObj)
{
if (cacheObj.state == Spry.Data.XMLDataSet.LoadManager.CachedRequest.LOAD_REQUESTED)
{
if (ds)
cacheObj.addObserver(ds);
return cacheObj;
}
else if (cacheObj.state == Spry.Data.XMLDataSet.LoadManager.CachedRequest.LOAD_SUCCESSFUL)
{
// Data is already cached so if we have a data set, trigger an async call
// that tells it to load its data.
if (ds)
setTimeout(function() { ds.setDataFromDoc(cacheObj.doc); }, 0);
return cacheObj;
}
}

// We're either loading this url for the first time, or an error occurred when
// we last tried to load it, or the caller requested a forced load.

if (!cacheObj)
{
cacheObj = new Spry.Data.XMLDataSet.LoadManager.CachedRequest(reqInfo);
if (useCache)
{
Spry.Data.XMLDataSet.LoadManager.cache[cacheKey] = cacheObj;

// Add an observer that will remove the cacheObj from the cache
// if there is a load request failure.
cacheObj.addObserver({ onRequestError: function() { Spry.Data.XMLDataSet.LoadManager.cache[cacheKey] = undefined; }});
}
}

if (ds)
cacheObj.addObserver(ds);

cacheObj.loadData();

return cacheObj;
};

Spry.Data.XMLDataSet.LoadManager.cancelLoadData = function(cacheObj, ds)
{
if (cacheObj)
{
if (ds)
cacheObj.removeObserver(ds);
else
cacheObj.cancelLoadData();
}
};

//////////////////////////////////////////////////////////////////////
//
// Spry.Data.Region
//
//////////////////////////////////////////////////////////////////////

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);

// Add the region as an observer to the dataSet!
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"); }
}
}; // End of Spry.Data.Region() constructor.

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 the region has content that is specific to this
// state, regenerate the region so that its markup is updated.

if (this.states[stateName])
{
if (!suppressNotfications)
Spry.Data.Region.notifyObservers("onPreUpdate", this, { state: this.currentState });

// Make the region transform the xml data. The result is
// a string that we need to parse and insert into the document.
var str = this.transform();

// Clear out any previous transformed content.
// this.clearContent();

if (Spry.Data.Region.debug)
Spry.Debug.trace("<hr />Generated region markup for '" + this.name + "':<br /><br />" + Spry.Utils.encodeEntities(str));

// Now insert the new transformed content into the document.
Spry.Utils.setInnerHTML(this.regionNode, str, !Spry.Data.Region.evalScripts);

// Now run through the content looking for attributes
// that tell us what behaviors to attach to each element.
if (this.hasBehaviorAttributes)
this.attachBehaviors();

if (!suppressNotfications)
Spry.Data.Region.notifyObservers("onPostUpdate", this, { state: this.currentState });
}

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;

// Check to see if the data set is already in our list.

for (var i = 0; i < this.dataSets.length; i++)
{
if (this.dataSets[i] == aDataSet)
return; // It's already in our list!
}

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.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)
{
// The format for the spry:even and spry:odd attributes are as follows:
//
// <div spry:even="dataSetName cssEvenClassName" spry:odd="dataSetName cssOddClassName">
//
// The dataSetName is optional, and if not specified, the first data set
// listed for the region is used.
//
// cssEvenClassName and cssOddClassName are required and *must* be specified. They can be
// any user defined CSS class name.

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)
{
// Extract out the data set name and reset the attribute so
// that it only contains the CSS class name to use.

dsName = valArr[0];
node.setAttribute(attr, valArr[1]);
}

// Tag the node with an attribute that will allow us to fetch the row
// number used when it is written out during the re-generation process.

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 = null;
try { ds = Spry.Utils.eval(dsName); } catch(e) { ds = null; };
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;
}

// Tag the node with an attribute that will allow us to fetch the id of the
// row used when it is written out during the re-generation process.

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;
}

// Tag the node with an attribute that will allow us to fetch the row number
// of the row used when it is written out during the re-generation process.

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;

// The format of a spry:sort attribute is as follows:
//
// <div spry:sort="dataSetName column1Name column2Name ... sortOrderName">
//
// The dataSetName and sortOrderName are optional, but when specified, they
// must appear in the order mentioned above. If the dataSetName is not specified,
// the first data set listed for the region is used. If the sortOrderName is not
// specified, the sort defaults to "toggle".
//
// The user *must* specify at least one column name.

var ds = rgn.getDataSets()[0];
var sortOrder = "toggle";

var colArray = value.split(/\s/);
if (colArray.length > 1)
{
// Check the first string in the attribute to see if a data set was
// specified. If so, make sure we use it for the sort.

try
{
var specifiedDS = eval(colArray[0]);
if (specifiedDS && (typeof specifiedDS) == "object")
{
ds = specifiedDS;
colArray.shift();
}

} catch(e) {}

// Check to see if the last string in the attribute is the name of
// a sort order. If so, use that sort order during the sort.

if (colArray.length > 1)
{
var str = colArray[colArray.length - 1];
if (str == "ascending" || str == "descending" || str == "toggle")
{
sortOrder = str;
colArray.pop();
}
}
}

// If we have a data set and some column names, add a non-destructive
// onclick handler that will perform a toggle sort on the data set.

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 /* Node.ELEMENT_NODE */)
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())
{
// Kick off the loading of the data if it hasn't happened yet.
ds.loadData();
allDataSetsReady = false;
}
}
}

if (!allDataSetsReady)
{
Spry.Data.Region.notifyObservers("onLoadingData", this);

// Just return, this method will get called again automatically
// as each data set load completes!
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)
{
// We found a match but it's not at the start of the inStr.
// Create a string token for everything that precedes the match.
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 there is no data, there's nothing to do.
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);

// Create a regular expression that will match one of the following:
//
//   <spry:repeat select="regionName" test="true">
//   </spry:repeat>
//   {valueReference}
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])
{
// If we get here, the rest of the parseStr should be
// just a plain string. Create a token for it and then
// break out of the list.
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)
{
// We found a match but it's not at the start of the parseStr.
// Create a string token for everything that precedes the match.
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);
}

// We found a string that needs to be turned into a token. Create a token
// for it and then update parseStr for the next iteration.
if (results[0].search(/^({|%7[bB])/) != -1 /* results[0].charAt(0) == '{' */)
{
var valueName = results[0];
var regionStr = results[0];

// Strip off brace and url encode brace chars inside the valueName.

valueName = valueName.replace(/^({|%7[bB])/, "");
valueName = valueName.replace(/(}|%7[dD])$/, "");

// Check to see if our value begins with the name of a data set.
// For example: {dataSet:tokenValue}. If it is, we need to save
// the data set name so we know which data set to use to get the
// value for the token during the region transform.

var dataSetName = null;
var splitArray = valueName.split(/::/);

if (splitArray.length > 1)
{
dataSetName = splitArray[0];
valueName = splitArray[1];
}

// Convert any url encoded braces to regular brace chars.

regionStr = regionStr.replace(/^%7[bB]/, "{");
regionStr = regionStr.replace(/%7[dD]$/, "}");

// Now create a token for the placeholder.

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) == '<')
{
// Extract out the name of the processing instruction.
var piName = results[0].replace(/^(<!--\s*){0,1}<\/?/, "");
piName = piName.replace(/>(\s*-->){0,1}|\s.*$/, "");

if (results[0].search(/<\//) != -1 /* results[0].charAt(1) == '/' */)
{
// We found a processing instruction close tag. Pop the top of the
// token stack!
//
// XXX: We need to make sure that the close tag name matches the one
//      on the top of the token stack!
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
{
// Create the processing instruction token, add it as a child of the token
// at the top of the token stack, and then push it on the stack so that it
// becomes the parent of any tokens between it and its close tag.

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)
{
try
{
dataSet = eval(selectedDataSetName);
}
catch (e)
{
Spry.Debug.reportError("Caught exception in tokenizeData() while trying to retrieve data set (" + selectedDataSetName + "): " + e + "\n");
dataSet = null;
selectedDataSetName = "";
}
}
}

// Check if the repeat has a test attribute.
var jsExpr = null;
if (results[0].search(/^.*\btest=\"/) != -1)
{
jsExpr = results[0].replace(/^.*\btest=\"/, "");
jsExpr = jsExpr.replace(/".*$/, "");
jsExpr = Spry.Utils.decodeEntities(jsExpr);
}

// Check if the instruction has a state name specified.
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.processTokenChildren = function(token, processContext)
{
// The use of an array to gather the strings returned from processing
// the child tokens is actually a performance enhancement for IE.
// The original code:
//
//     for (var i = 0; i < token.children.length; i++)
//       outputStr += this.processTokens(token.children[i], processContext);
//
// seemed to cause an n-square problem in IE. Using an array with
// a final join reduced one of our test cases (SelectExample.html) from over
// a minute to about 15 seconds.

var strArr = [ "" ];
var len = token.children.length;
var children = token.children;

for (var i = 0; i < len; i++)
strArr.push(this.processTokens(children[i], processContext));

return strArr.join("");
};

Spry.Data.Region.prototype.processTokens = function(token, processContext)
{
if (!processContext)
{
processContext = new Spry.Data.Region.ProcessingContext(this);
if (!processContext)
return "";
}

var outputStr = "";
var i = 0;

switch(token.tokenType)
{
case Spry.Data.Region.Token.LIST_TOKEN:
outputStr += this.processTokenChildren(token, processContext);
break;
case Spry.Data.Region.Token.STRING_TOKEN:
outputStr += 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;
}

var numRows = dsContext.getNumRows();
var dataSetRows = dataSet.getData();
dsContext.pushState();

for (i = 0; i < numRows; i++)
{
dsContext.setRowIndex(i);
var testVal = true;
if (token.data.jsExpr)
{
var jsExpr = Spry.Data.Region.processDataRefString(processContext, token.data.jsExpr, null, true);
try { testVal = Spry.Utils.eval(jsExpr); }
catch(e)
{
Spry.Debug.trace("Caught exception in Spry.Data.Region.prototype.processTokens while evaluating: " + jsExpr + "\n    Exception:" + e + "\n");
testVal = true;
}
}

if (testVal)
outputStr += this.processTokenChildren(token, processContext);
}

dsContext.popState();
}
}
else if (token.data.name == "spry:if")
{
var testVal = true;

if (token.data.jsExpr)
{
var jsExpr = Spry.Data.Region.processDataRefString(processContext, token.data.jsExpr, null, true);

try { testVal = Spry.Utils.eval(jsExpr); }
catch(e)
{
Spry.Debug.trace("Caught exception in Spry.Data.Region.prototype.processTokens while evaluating: " + jsExpr + "\n    Exception:" + e + "\n");
testVal = true;
}
}

if (testVal)
outputStr += this.processTokenChildren(token, processContext);
}
else if (token.data.name == "spry:choose")
{
var defaultChild = null;
var childToProcess = null;
var testVal = false;
var j = 0;

// All of the children of the spry:choose token should be of the type spry:when or spry:default.
// Run through all of the spry:when children and see if any of their test expressions return true.
// If one does, then process its children tokens. If none of the test expressions return true,
// process the spry:default token's children, if it exists.

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)
{
var jsExpr = Spry.Data.Region.processDataRefString(processContext, child.data.jsExpr, null, true);
try { testVal = Spry.Utils.eval(jsExpr); }
catch(e)
{
Spry.Debug.trace("Caught exception in Spry.Data.Region.prototype.processTokens while evaluating: " + jsExpr + "\n    Exception:" + e + "\n");
testVal = false;
}

if (testVal)
{
childToProcess = child;
break;
}
}
}
else if (child.data.name == "spry:default")
defaultChild = child;
}
}

// If we didn't find a match, use the token for the default case.

if (!childToProcess && defaultChild)
childToProcess = defaultChild;

if (childToProcess)
outputStr += this.processTokenChildren(childToProcess, processContext);
}
else if (token.data.name == "spry:state")
{
var testVal = true;

if (!token.data.regionState || token.data.regionState == this.currentState)
outputStr += this.processTokenChildren(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;
if (!dataSet && this.dataSets && this.dataSets.length > 0 && this.dataSets[0])
{
// No dataSet was specified by the token, so use whatever the first
// data set specified in the region.

dataSet = this.dataSets[0];
}
if (!dataSet)
{
Spry.Debug.reportError("processTokens(): Value reference has no data set specified: " + token.regionStr + "\n");
return "";
}

var dsContext = processContext.getDataSetContext(dataSet);
if (!dsContext)
{
Spry.Debug.reportError("processTokens: Failed to get a data set context!\n");
return "";
}

var ds = dsContext.getDataSet();

if (token.data == "ds_RowNumber")
outputStr += dsContext.getRowIndex();
else if (token.data == "ds_RowNumberPlus1")
outputStr += (dsContext.getRowIndex() + 1);
else if (token.data == "ds_RowCount")
outputStr += dsContext.getNumRows();
else if (token.data == "ds_UnfilteredRowCount")
outputStr += dsContext.getNumRows(true);
else if (token.data == "ds_CurrentRowNumber")
outputStr += ds.getRowNumber(ds.getCurrentRow());
else if (token.data == "ds_CurrentRowID")
outputStr += ds.curRowID;
else if (token.data == "ds_EvenOddRow")
outputStr += (dsContext.getRowIndex() % 2) ? Spry.Data.Region.evenRowClassName : Spry.Data.Region.oddRowClassName;
else if (token.data == "ds_SortOrder")
outputStr += ds.getSortOrder();
else if (token.data == "ds_SortColumn")
outputStr += ds.getSortColumn();
else
{
var curDataSetRow = dsContext.getCurrentRow();
if (curDataSetRow)
outputStr += curDataSetRow[token.data];
}
break;
default:
Spry.Debug.reportError("processTokens(): Invalid token type: " + token.regionStr + "\n");
break;
}

return outputStr;
};

Spry.Data.Region.prototype.transform = function()
{
if (this.data && !this.tokens)
this.tokens = this.tokenizeData(this.data);

if (!this.tokens)
return "";

return this.processTokens(this.tokens, null);
};

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)
{
// XXX: This will need to be modified if we support
// tokens that use javascript between the braces!
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;

if (processingContext)
{
var dsContext = processingContext.getDataSetContext(dsName);

if (fieldName == "ds_RowNumber")
{
resultStr += dsContext.getRowIndex();
row = null;
}
else if (fieldName == "ds_RowNumberPlus1")
{
resultStr += (dsContext.getRowIndex() + 1);
row = null;
}
else if (fieldName == "ds_RowCount")
{
resultStr += dsContext.getNumRows();
row = null;
}
else if (fieldName == "ds_UnfilteredRowCount")
{
resultStr += dsContext.getNumRows(true);
row = null;
}
else if (fieldName == "ds_CurrentRowNumber")
{
var ds = dsContext.getDataSet();
resultStr += ds.getRowNumber(ds.getCurrentRow());
row = null;
}
else if (fieldName == "ds_CurrentRowID")
{
var ds = dsContext.getDataSet();
resultStr += "" + ds.curRowID;
row = null;
}
else if (fieldName == "ds_EvenOddRow")
{
resultStr += (dsContext.getRowIndex() % 2) ? Spry.Data.Region.evenRowClassName : Spry.Data.Region.oddRowClassName;
row = null;
}
else if (fieldName == "ds_SortOrder")
{
resultStr += dsContext.getDataSet().getSortOrder();;
row = null;
}
else if (fieldName == "ds_SortColumn")
{
resultStr += dsContext.getDataSet().getSortColumn();
row = null;
}
else
row = processingContext.getCurrentRowForDataSet(dsName);
}
else
{
var ds = dsName ? dataSetsToUse[dsName] : dataSetsToUse[0];
if (ds)
row = ds.getCurrentRow();
}

if (row)
resultStr += isJSExpr ? Spry.Utils.escapeQuotesAndLineBreaks("" + row[fieldName]) : row[fieldName];

if (startSearchIndex == re.lastIndex)
{
// On IE if there was a match near the end of the string, it sometimes
// leaves re.lastIndex pointing to the value it had before the last time
// we called re.exec. We check for this case to prevent an infinite loop!
// We need to write out any text in regionStr that comes after the last
// match.

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 = eval(arr[i]);

if (!foundHash[arr[i]])
{
if (returnRegionNames)
dataSetsArr.push(arr[i]);
else
dataSetsArr.push(dataSet);
foundHash[arr[i]] = true;
}
}
catch (e) { /* Spry.Debug.trace("Caught exception: " + e + "\n"); */ }
}
}

return dataSetsArr;
};

Spry.Data.Region.DSContext = function(dataSet)
{
var m_self = this;
var m_dataSet = dataSet;
var m_curRowIndexArray = [ -1 ]; // -1 means return whatever the current row is inside the data set.

// Private Methods:

function getInternalRowIndex() { return m_curRowIndexArray[m_curRowIndexArray.length - 1]; }

// Public Methods:
this.resetAll = function() { m_curRowIndexArray = [ m_dataSet.getCurrentRow() ] };
this.getDataSet = function() { return m_dataSet; };
this.getNumRows = function(unfiltered)
{
return m_dataSet.getRowCount(unfiltered);
};
this.getCurrentRow = function()
{
if (m_curRowIndexArray.length < 2 || getInternalRowIndex() < 0)
return m_dataSet.getCurrentRow();

var data = m_dataSet.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) { m_curRowIndexArray[m_curRowIndexArray.length - 1] = rowIndex; };
this.pushState = function() { m_curRowIndexArray.push( getInternalRowIndex()); };
this.popState = function()
{
if (m_curRowIndexArray.length < 2)
{
// Our array should always have at least one element in it!
Spry.Debug.reportError("Stack underflow in Spry.Data.Region.DSContext.popState()!\n");
return;
}
m_curRowIndexArray.pop();
};
};

Spry.Data.Region.ProcessingContext = function(region)
{
var m_self = this;
var m_region = region;
var m_dataSetContexts = [];

if (region && region.dataSets)
{
for (var i = 0; i < region.dataSets.length; i++)
m_dataSetContexts.push(new Spry.Data.Region.DSContext(region.dataSets[i]));
}

this.getDataSetContext = function(dataSet)
{
if (!dataSet)
{
// We were called without a specified data set or
// data set name. Assume the caller wants the first
// data set in the processing context.

if (m_dataSetContexts.length > 0)
return m_dataSetContexts[0];
return null;
}

if (typeof dataSet == 'string')
{
try { dataSet = eval(dataSet); } catch (e) { dataSet = null; }
if (!dataSet)
return null;
}

for (var i = 0; i < m_dataSetContexts.length; i++)
{
var dsc = m_dataSetContexts[i];
if (dsc.getDataSet() == dataSet)
return dsc;
}

return null;
};

this.getCurrentRowForDataSet = function(dataSet)
{
var dsc = m_self.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() { Spry.Data.initRegions(); }, 0); });