PageSourceSearch

https://geneinfinity.org/scripts/SpryData.js

js geneinfinity.org collected 2026-10-02 06:47:50 UTC 129,526 bytes, 4,782 lines download raw bytes

1// SpryData.js - version 0.45 - Spry Pre-Release 1.6.1
2//
3// Copyright (c) 2006. Adobe Systems Incorporated.
4// All rights reserved.
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are met:
8//
9//   * Redistributions of source code must retain the above copyright notice,
10//     this list of conditions and the following disclaimer.
11//   * Redistributions in binary form must reproduce the above copyright notice,
12//     this list of conditions and the following disclaimer in the documentation
13//     and/or other materials provided with the distribution.
14//   * Neither the name of Adobe Systems Incorporated nor the names of its
15//     contributors may be used to endorse or promote products derived from this
16//     software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28// POSSIBILITY OF SUCH DAMAGE.
29
30//Gene Infinity
31// Modified Spry.Utils.setInnerHTML
32// From http://www.adobe.com/cfusion/webforums/forum/messageview.cfm?forumid=72&catid=602&threadid=1306334&highlight_key=y&keyword1=setInnerHTML
33// 09/20/2009
34
35var Spry; if (!Spry) Spry = {};
36
37//////////////////////////////////////////////////////////////////////
38//
39// Spry.Utils
40//
41//////////////////////////////////////////////////////////////////////
42
43if (!Spry.Utils) Spry.Utils = {};
44
45Spry.Utils.msProgIDs = ["MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.3.0"];
46
47Spry.Utils.createXMLHttpRequest = function()
48{
49	var req = null;
50	try
51	{
52		// Try to use the ActiveX version of XMLHttpRequest. This will
53		// allow developers to load file URLs in IE7 when running in the
54		// local zone.
55
56		if (window.ActiveXObject)
57		{
58			while (!req && Spry.Utils.msProgIDs.length)
59			{
60				try { req = new ActiveXObject(Spry.Utils.msProgIDs[0]); } catch (e) { req = null; }
61				if (!req)
62					Spry.Utils.msProgIDs.splice(0, 1);
63			}
64		}
65
66		// We're either running in a non-IE browser, or we failed to
67		// create the ActiveX version of the XMLHttpRequest object.
68		// Try to use the native version of XMLHttpRequest if it exists.
69
70		if (!req && window.XMLHttpRequest)
71			req = new XMLHttpRequest();
72	}
73	catch (e) { req = null;	}
74
75	if (!req)
76		Spry.Debug.reportError("Failed to create an XMLHttpRequest object!" );
77
78	return req;
79};
80
81Spry.Utils.loadURL = function(method, url, async, callback, opts)
82{
83	var req = new Spry.Utils.loadURL.Request();
84	req.method = method;
85	req.url = url;
86	req.async = async;
87	req.successCallback = callback;
88	Spry.Utils.setOptions(req, opts);
89
90	try
91	{
92		req.xhRequest = Spry.Utils.createXMLHttpRequest();
93		if (!req.xhRequest)
94			return null;
95
96		if (req.async)
97			req.xhRequest.onreadystatechange = function() { Spry.Utils.loadURL.callback(req); };
98
99		req.xhRequest.open(req.method, req.url, req.async, req.username, req.password);
100
101		if (req.headers)
102		{
103			for (var name in req.headers)
104				req.xhRequest.setRequestHeader(name, req.headers[name]);
105		}
106
107		req.xhRequest.send(req.postData);
108
109		if (!req.async)
110			Spry.Utils.loadURL.callback(req);
111	}
112	catch(e)
113	{
114		if (req.errorCallback)
115			req.errorCallback(req);
116		else
117			Spry.Debug.reportError("Exception caught while loading " + url + ": " + e);
118		req = null;
119	}
120
121	return req;
122};
123
124Spry.Utils.loadURL.callback = function(req)
125{
126	if (!req || req.xhRequest.readyState != 4)
127		return;
128	if (req.successCallback && (req.xhRequest.status == 200 || req.xhRequest.status == 0))
129		req.successCallback(req);
130	else if (req.errorCallback)
131		req.errorCallback(req);
132};
133
134Spry.Utils.loadURL.Request = function()
135{
136	var props = Spry.Utils.loadURL.Request.props;
137	var numProps = props.length;
138
139	for (var i = 0; i < numProps; i++)
140		this[props[i]] = null;
141
142	this.method = "GET";
143	this.async = true;
144	this.headers = {};
145};
146
147Spry.Utils.loadURL.Request.props = [ "method", "url", "async", "username", "password", "postData", "successCallback", "errorCallback", "headers", "userData", "xhRequest" ];
148
149Spry.Utils.loadURL.Request.prototype.extractRequestOptions = function(opts, undefineRequestProps)
150{
151	if (!opts)
152		return;
153
154	var props = Spry.Utils.loadURL.Request.props;
155	var numProps = props.length;
156
157	for (var i = 0; i < numProps; i++)
158	{
159		var prop = props[i];
160		if (opts[prop] != undefined)
161		{
162			this[prop] = opts[prop];
163			if (undefineRequestProps)
164				opts[prop] = undefined;
165		}
166	}
167};
168
169Spry.Utils.loadURL.Request.prototype.clone = function()
170{
171	var props = Spry.Utils.loadURL.Request.props;
172	var numProps = props.length;
173	var req = new Spry.Utils.loadURL.Request;
174	for (var i = 0; i < numProps; i++)
175		req[props[i]] = this[props[i]];
176	if (this.headers)
177	{
178		req.headers = {};
179		Spry.Utils.setOptions(req.headers, this.headers);
180	}
181	return req;
182};
183
184/*
185Spry.Utils.setInnerHTML = function(ele, str, preventScripts)
186{
187	if (!ele)
188		return;
189	ele = Spry.$(ele);
190	var scriptExpr = "<script[^>]*>(.|\s|\n|\r)*?</script>";
191	ele.innerHTML = str.replace(new RegExp(scriptExpr, "img"), "");
192
193	if (preventScripts)
194		return;
195
196	var matches = str.match(new RegExp(scriptExpr, "img"));
197	if (matches)
198	{
199		var numMatches = matches.length;
200		for (var i = 0; i < numMatches; i++)
201		{
202			var s = matches[i].replace(/<script[^>]*>[\s\r\n]*(<\!--)?|(-->)?[\s\r\n]*<\/script>/img, "");
203			Spry.Utils.eval(s);
204		}
205	}
206};
207*/
208
209
210Spry.Utils.setInnerHTML = function(ele, str, preventScripts)
211{
212if (!ele)
213return;
214ele = Spry.$(ele);
215var scriptExpr = "<script[^>]*>(.|\s|\n|\r)*?</script>";
216ele.innerHTML = str.replace(new RegExp(scriptExpr, "img"), "");
217
218if (preventScripts)
219return;
220
221//bodyMatches is used to store the script tags that have body. For example, <script type='text/javascript'> some JS </script>
222var bodyMatches = str.match(new RegExp(scriptExpr, "img"));
223if (bodyMatches)
224{
225var numMatches = bodyMatches.length;
226for (var i = 0; i < numMatches; i++)
227{
228var s = bodyMatches[ i ].replace(/<script[^>]*>[\s\r\n]*(<\!--)?|(-->)?[\s\r\n]*<\/script>/img, "");
229//make sure that we are not create a script tag if there is no body to it.
230if(s.length > 0)
231{
232//alert(s);
233var scriptEle= document.createElement("script");
234scriptEle.text = s;
235ele.appendChild(scriptEle);
236}
237}
238}
239
240//now we begin taking care of the script includes (those that have a src attribute defined)
241var srcScript = "<script[^>].*src.*>(.|\s|\n|\r)*?</script>";
242var srcMatches = str.match(new RegExp(srcScript, "img"));
243if(srcMatches)
244{
245var numMatches2 = srcMatches.length;
246var srcAttrib = "[\s]*src=[\'\"](.*\.js)[\'\"]";
247for(var j=0; j<numMatches2; j++)
248{
249// when the below statement executes, m should have the src attrib and value:
250// src='sompath.js' as a string.
251var m = srcMatches[j].match(new RegExp(srcAttrib, "img")).toString();
252
253//now we strip out the src= part. this will leave us with just the path along with the double quotes.
254var n = m.replace(/^[(?=src=)]*/img, "");
255if(n.length > 0)
256{
257var scriptEle2 = document.createElement("script");
258
259//get rid of the quotes as well.
260//This is done to take care of single quotes too so that it will work if they defined either:
261// <script src='somefile.js' type="text/javascript></script>
262// <script src="somefile.js" type="text/javascript></script>
263scriptEle2.src=n.replace(/[\"\']/img, "");
264scriptEle2.type="text/javascript";
265ele.appendChild(scriptEle2);
266}
267}
268}
269//we need to initialize the newly inserted region since spry doesn't know about it.
270Spry.Data.initRegions(ele);
271}; 
272
273
274Spry.Utils.updateContent = function (ele, url, finishFunc, opts)
275{
276	Spry.Utils.loadURL("GET", url, true, function(req)
277	{
278		Spry.Utils.setInnerHTML(ele, req.xhRequest.responseText);
279		if (finishFunc)
280			finishFunc(ele, url);
281	}, opts);
282};
283
284//////////////////////////////////////////////////////////////////////
285//
286// Functions from SpryDOMUtils.js
287//   - These have been left in for backwards compatibility, but they
288//     should only be defined if Spry.$$ (SpryDOMUtils.js) is not
289//     already included.
290//   - If SpryDOMUtils.js is included *after* SpryData.js, these
291//     functions will be replaced with the latest versions in
292//     SpryDOMUtils.js.
293//
294//////////////////////////////////////////////////////////////////////
295
296if (!Spry.$$)
297{
298Spry.Utils.addEventListener = function(element, eventType, handler, capture)
299{
300	try
301	{
302		element = Spry.$(element);
303		if (element.addEventListener)
304			element.addEventListener(eventType, handler, capture);
305		else if (element.attachEvent)
306			element.attachEvent("on" + eventType, handler);
307	}
308	catch (e) {}
309};
310
311Spry.Utils.removeEventListener = function(element, eventType, handler, capture)
312{
313	try
314	{
315		element = Spry.$(element);
316		if (element.removeEventListener)
317			element.removeEventListener(eventType, handler, capture);
318		else if (element.detachEvent)
319			element.detachEvent("on" + eventType, handler);
320	}
321	catch (e) {}
322};
323
324Spry.Utils.addLoadListener = function(handler)
325{
326	if (typeof window.addEventListener != 'undefined')
327		window.addEventListener('load', handler, false);
328	else if (typeof document.addEventListener != 'undefined')
329		document.addEventListener('load', handler, false);
330	else if (typeof window.attachEvent != 'undefined')
331		window.attachEvent('onload', handler);
332};
333
334Spry.Utils.addClassName = function(ele, className)
335{
336	ele = Spry.$(ele);
337	if (!ele || !className || (ele.className && ele.className.search(new RegExp("\\b" + className + "\\b")) != -1))
338		return;
339	ele.className += (ele.className ? " " : "") + className;
340};
341
342Spry.Utils.removeClassName = function(ele, className)
343{
344	ele = Spry.$(ele);
345	if (!ele || !className || (ele.className && ele.className.search(new RegExp("\\b" + className + "\\b")) == -1))
346		return;
347	ele.className = ele.className.replace(new RegExp("\\s*\\b" + className + "\\b", "g"), "");
348};
349
350Spry.Utils.getObjectByName = function(name)
351{
352	var result = null;
353	if (name)
354	{
355		var lu = window;
356		var objPath = name.split(".");
357		for (var i = 0; lu && i < objPath.length; i++)
358		{
359			result = lu[objPath[i]];
360			lu = result;
361		}
362	}
363	return result;
364};
365
366//////////////////////////////////////////////////////////////////////
367//
368// Define Prototype's $() convenience function, but make sure it is
369// namespaced under Spry so that we avoid collisions with other
370// toolkits.
371//
372//////////////////////////////////////////////////////////////////////
373
374Spry.$ = function(element)
375{
376	if (arguments.length > 1)
377	{
378		for (var i = 0, elements = [], length = arguments.length; i < length; i++)
379			elements.push(Spry.$(arguments[i]));
380		return elements;
381	}
382	if (typeof element == 'string')
383		element = document.getElementById(element);
384	return element;
385};
386} // if (!Spry.$$)
387
388//////////////////////////////////////////////////////////////////////
389
390Spry.Utils.eval = function(str)
391{
392	// Call this method from your JS function when
393	// you don't want the JS expression to access or
394	// interfere with any local variables in your JS
395	// function.
396
397	return eval(str);
398};
399
400Spry.Utils.escapeQuotesAndLineBreaks = function(str)
401{
402	if (str)
403	{
404		str = str.replace(/\\/g, "\\\\");
405		str = str.replace(/["']/g, "\\$&");
406		str = str.replace(/\n/g, "\\n");
407		str = str.replace(/\r/g, "\\r");
408	}
409	return str;
410};
411
412Spry.Utils.encodeEntities = function(str)
413{
414	if (str && str.search(/[&<>"]/) != -1)
415	{
416		str = str.replace(/&/g, "&amp;");
417		str = str.replace(/</g, "&lt;");
418		str = str.replace(/>/g, "&gt;");
419		str = str.replace(/"/g, "&quot;");
420	}
421	return str
422};
423
424Spry.Utils.decodeEntities = function(str)
425{
426	var d = Spry.Utils.decodeEntities.div;
427	if (!d)
428	{
429		d = document.createElement('div');
430		Spry.Utils.decodeEntities.div = d;
431		if (!d) return str;
432	}
433	d.innerHTML = str;
434	if (d.childNodes.length == 1 && d.firstChild.nodeType == 3 /* Node.TEXT_NODE */ && d.firstChild.nextSibling == null)
435		str = d.firstChild.data;
436	else
437	{
438		// Hmmm, innerHTML processing of str produced content
439		// we weren't expecting, so just replace entities we
440		// expect folks will use in node attributes that contain
441		// JavaScript.
442		str = str.replace(/&lt;/gi, "<");
443		str = str.replace(/&gt;/gi, ">");
444		str = str.replace(/&quot;/gi, "\"");
445		str = str.replace(/&amp;/gi, "&");
446	}
447	return str;
448};
449
450Spry.Utils.fixupIETagAttributes = function(inStr)
451{
452	var outStr = "";
453
454	// Break the tag string into 3 pieces.
455
456	var tagStart = inStr.match(/^<[^\s>]+\s*/)[0];
457	var tagEnd = inStr.match(/\s*\/?>$/)[0];
458	var tagAttrs = inStr.replace(/^<[^\s>]+\s*|\s*\/?>/g, "");
459
460	// Write out the start of the tag.
461	outStr += tagStart;
462
463	// If the tag has attributes, parse it out manually to avoid accidentally fixing up
464	// attributes that contain JavaScript expressions.
465
466	if (tagAttrs)
467	{
468		var startIndex = 0;
469		var endIndex = 0;
470
471		while (startIndex < tagAttrs.length)
472		{
473			// Find the '=' char of the attribute.
474			while (tagAttrs.charAt(endIndex) != '=' && endIndex < tagAttrs.length)
475				++endIndex;
476
477			// If we are at the end of the string, just write out what we've
478			// collected.
479
480			if (endIndex >= tagAttrs.length)
481			{
482				outStr += tagAttrs.substring(startIndex, endIndex);
483				break;
484			}
485
486			// Step past the '=' character and write out what we've
487			// collected so far.
488
489			++endIndex;
490			outStr += tagAttrs.substring(startIndex, endIndex);
491			startIndex = endIndex;
492
493			if (tagAttrs.charAt(endIndex) == '"' || tagAttrs.charAt(endIndex) == "'")
494			{
495				// Attribute is quoted. Advance us past the quoted value!
496				var savedIndex = endIndex++;
497				while (endIndex < tagAttrs.length)
498				{
499					if (tagAttrs.charAt(endIndex) == tagAttrs.charAt(savedIndex))
500					{
501						endIndex++;
502						break;
503					}
504					else if (tagAttrs.charAt(endIndex) == "\\")
505						endIndex++;
506					endIndex++;
507				}
508
509				outStr += tagAttrs.substring(startIndex, endIndex);
510				startIndex = endIndex;
511			}
512			else
513			{
514				// This attribute value wasn't quoted! Wrap it with quotes and
515				// write out everything till we hit a space, or the end of the
516				// string.
517
518				outStr += "\"";
519
520				var sIndex = tagAttrs.slice(endIndex).search(/\s/);
521				endIndex = (sIndex != -1) ? (endIndex + sIndex) : tagAttrs.length;
522				outStr += tagAttrs.slice(startIndex, endIndex);
523				outStr += "\"";
524				startIndex = endIndex;
525			}
526		}
527	}
528
529	outStr += tagEnd;
530
531	// Write out the end of the tag.
532	return outStr;
533};
534
535Spry.Utils.fixUpIEInnerHTML = function(inStr)
536{
537	var outStr = "";
538
539	// Create a regular expression that will match:
540	//     <!--
541	//     <![CDATA[
542	//     <tag>
543	//     -->
544	//     ]]>
545	//     ]]&gt;   // Yet another workaround for an IE innerHTML bug.
546	//
547	// The idea here is that we only want to fix up attribute values on tags that
548	// are not in any comments or CDATA.
549
550	var regexp = new RegExp("<\\!--|<\\!\\[CDATA\\[|<\\w+[^<>]*>|-->|\\]\\](>|\&gt;)", "g");
551	var searchStartIndex = 0;
552	var skipFixUp = 0;
553
554	while (inStr.length)
555	{
556		var results = regexp.exec(inStr);
557		if (!results || !results[0])
558		{
559			outStr += inStr.substr(searchStartIndex, inStr.length - searchStartIndex);
560			break;
561		}
562
563		if (results.index != searchStartIndex)
564		{
565			// We found a match but it's not at the start of the inStr.
566			// Create a string token for everything that precedes the match.
567			outStr += inStr.substr(searchStartIndex, results.index - searchStartIndex);
568		}
569
570		if (results[0] == "<!--" || results[0] == "<![CDATA[")
571		{
572			++skipFixUp;
573			outStr += results[0];
574		}
575		else if (results[0] == "-->" || results[0] == "]]>" || (skipFixUp && results[0] == "]]&gt;"))
576		{
577			--skipFixUp;
578			outStr += results[0];
579		}
580		else if (!skipFixUp && results[0].charAt(0) == '<')
581			outStr += Spry.Utils.fixupIETagAttributes(results[0]);
582		else
583			outStr += results[0];
584
585		searchStartIndex = regexp.lastIndex;
586	}
587
588	return outStr;
589};
590
591Spry.Utils.stringToXMLDoc = function(str)
592{
593	var xmlDoc = null;
594
595	try
596	{
597		// Attempt to parse the string using the IE method.
598
599		var xmlDOMObj = new ActiveXObject("Microsoft.XMLDOM");
600		xmlDOMObj.async = false;
601		xmlDOMObj.loadXML(str);
602		xmlDoc = xmlDOMObj;
603	}
604	catch (e)
605	{
606		// The IE method didn't work. Try the Mozilla way.
607
608		try
609		{
610			var domParser = new DOMParser;
611			xmlDoc = domParser.parseFromString(str, 'text/xml');
612		}
613		catch (e)
614		{
615			Spry.Debug.reportError("Caught exception in Spry.Utils.stringToXMLDoc(): " + e + "\n");
616			xmlDoc = null;
617		}
618	}
619
620	return xmlDoc;
621};
622
623Spry.Utils.serializeObject = function(obj)
624{
625	// Create a JSON representation of a given object.
626
627	var str = "";
628	var firstItem = true;
629
630	if (obj == null || obj == undefined)
631		return str + obj;
632
633	var objType = typeof obj;
634
635	if (objType == "number" || objType == "boolean")
636		str += obj;
637	else if (objType == "string")
638		str += "\"" + Spry.Utils.escapeQuotesAndLineBreaks(obj) + "\"";
639	else if (obj.constructor == Array)
640	{
641		str += "[";
642		for (var i = 0; i < obj.length; i++)
643		{
644			if (!firstItem)
645				str += ", ";
646			str += Spry.Utils.serializeObject(obj[i]);
647			firstItem = false;
648		}
649		str += "]";
650	}
651	else if (objType == "object")
652	{
653		str += "{";
654		for (var p in obj)
655		{
656			if (!firstItem)
657				str += ", ";
658			str += "\"" + p + "\": " + Spry.Utils.serializeObject(obj[p]);
659			firstItem = false;
660		}
661		str += "}";
662	}
663	return str;
664};
665
666Spry.Utils.getNodesByFunc = function(root, func)
667{
668	var nodeStack = new Array;
669	var resultArr = new Array;
670	var node = root;
671
672	while (node)
673	{
674		if (func(node))
675			resultArr.push(node);
676
677		if (node.hasChildNodes())
678		{
679			nodeStack.push(node);
680			node = node.firstChild;
681		}
682		else
683		{
684			if (node == root)
685				node = null;
686			else
687				try { node = node.nextSibling; } catch (e) { node = null; };
688		}
689
690		while (!node && nodeStack.length > 0)
691		{
692			node = nodeStack.pop();
693			if (node == root)
694				node = null;
695			else
696				try { node = node.nextSibling; } catch (e) { node = null; }
697		}
698	}
699
700	if (nodeStack && nodeStack.length > 0)
701		Spry.Debug.trace("-- WARNING: Spry.Utils.getNodesByFunc() failed to traverse all nodes!\n");
702
703	return resultArr;
704};
705
706// XXX: UNUSED FUNCTION
707Spry.Utils.getFirstChildWithNodeName = function(node, nodeName)
708{
709	var child = node.firstChild;
710
711	while (child)
712	{
713		if (child.nodeName == nodeName)
714			return child;
715		child = child.nextSibling;
716	}
717
718	return null;
719};
720
721Spry.Utils.setOptions = function(obj, optionsObj, ignoreUndefinedProps)
722{
723	if (!optionsObj)
724		return;
725
726	for (var optionName in optionsObj)
727	{
728		if (ignoreUndefinedProps && optionsObj[optionName] == undefined)
729			continue;
730		obj[optionName] = optionsObj[optionName];
731	}
732};
733
734Spry.Utils.SelectionManager = {};
735Spry.Utils.SelectionManager.selectionGroups = new Object;
736
737Spry.Utils.SelectionManager.SelectionGroup = function()
738{
739	this.selectedElements = new Array;
740};
741
742Spry.Utils.SelectionManager.SelectionGroup.prototype.select = function(element, className, multiSelect)
743{
744	var selObj = null;
745
746	if (!multiSelect)
747	{
748		// Multiple selection is not enabled, so clear any
749		// selected elements from our list.
750
751		this.clearSelection();
752	}
753	else
754	{
755		// Multiple selection is enabled, so check to see if element
756		// is already in the array. If it is, make sure the className
757		// is the className that was passed in.
758
759		for (var i = 0; i < this.selectedElements.length; i++)
760		{
761			selObj = this.selectedElements[i].element;
762
763			if (selObj.element == element)
764			{
765				if (selObj.className != className)
766				{
767					Spry.Utils.removeClassName(element, selObj.className);
768					Spry.Utils.addClassName(element, className);
769				}
770				return;
771			}
772		}
773	}
774
775	// Add the element to our list of selected elements.
776
777	selObj = new Object;
778	selObj.element = element;
779	selObj.className = className;
780	this.selectedElements.push(selObj);
781	Spry.Utils.addClassName(element, className);
782};
783
784Spry.Utils.SelectionManager.SelectionGroup.prototype.unSelect = function(element)
785{
786	for (var i = 0; i < this.selectedElements.length; i++)
787	{
788		var selObj = this.selectedElements[i].element;
789
790		if (selObj.element == element)
791		{
792			Spry.Utils.removeClassName(selObj.element, selObj.className);
793			return;
794		}
795	}
796};
797
798Spry.Utils.SelectionManager.SelectionGroup.prototype.clearSelection = function()
799{
800	var selObj = null;
801
802	do
803	{
804		selObj = this.selectedElements.shift();
805		if (selObj)
806			Spry.Utils.removeClassName(selObj.element, selObj.className);
807	}
808	while (selObj);
809};
810
811Spry.Utils.SelectionManager.getSelectionGroup = function(selectionGroupName)
812{
813	if (!selectionGroupName)
814		return null;
815
816	var groupObj = Spry.Utils.SelectionManager.selectionGroups[selectionGroupName];
817
818	if (!groupObj)
819	{
820		groupObj = new Spry.Utils.SelectionManager.SelectionGroup();
821		Spry.Utils.SelectionManager.selectionGroups[selectionGroupName] = groupObj;
822	}
823
824	return groupObj;
825};
826
827Spry.Utils.SelectionManager.select = function(selectionGroupName, element, className, multiSelect)
828{
829	var groupObj = Spry.Utils.SelectionManager.getSelectionGroup(selectionGroupName);
830
831	if (!groupObj)
832		return;
833
834	groupObj.select(element, className, multiSelect);
835};
836
837Spry.Utils.SelectionManager.unSelect = function(selectionGroupName, element)
838{
839	var groupObj = Spry.Utils.SelectionManager.getSelectionGroup(selectionGroupName);
840
841	if (!groupObj)
842		return;
843
844	groupObj.unSelect(element, className);
845};
846
847Spry.Utils.SelectionManager.clearSelection = function(selectionGroupName)
848{
849	var groupObj = Spry.Utils.SelectionManager.getSelectionGroup(selectionGroupName);
850
851	if (!groupObj)
852		return;
853
854	groupObj.clearSelection();
855};
856
857Spry.Utils.Notifier = function()
858{
859	this.observers = [];
860	this.suppressNotifications = 0;
861};
862
863Spry.Utils.Notifier.prototype.addObserver = function(observer)
864{
865	if (!observer)
866		return;
867
868	// Make sure the observer isn't already on the list.
869
870	var len = this.observers.length;
871	for (var i = 0; i < len; i++)
872	{
873		if (this.observers[i] == observer)
874			return;
875	}
876	this.observers[len] = observer;
877};
878
879Spry.Utils.Notifier.prototype.removeObserver = function(observer)
880{
881	if (!observer)
882		return;
883
884	for (var i = 0; i < this.observers.length; i++)
885	{
886		if (this.observers[i] == observer)
887		{
888			this.observers.splice(i, 1);
889			break;
890		}
891	}
892};
893
894Spry.Utils.Notifier.prototype.notifyObservers = function(methodName, data)
895{
896	if (!methodName)
897		return;
898
899	if (!this.suppressNotifications)
900	{
901		var len = this.observers.length;
902		for (var i = 0; i < len; i++)
903		{
904			var obs = this.observers[i];
905			if (obs)
906			{
907				if (typeof obs == "function")
908					obs(methodName, this, data);
909				else if (obs[methodName])
910					obs[methodName](this, data);
911			}
912		}
913	}
914};
915
916Spry.Utils.Notifier.prototype.enableNotifications = function()
917{
918	if (--this.suppressNotifications < 0)
919	{
920		this.suppressNotifications = 0;
921		Spry.Debug.reportError("Unbalanced enableNotifications() call!\n");
922	}
923};
924
925Spry.Utils.Notifier.prototype.disableNotifications = function()
926{
927	++this.suppressNotifications;
928};
929
930//////////////////////////////////////////////////////////////////////
931//
932// Spry.Debug
933//
934//////////////////////////////////////////////////////////////////////
935
936Spry.Debug = {};
937Spry.Debug.enableTrace = true;
938Spry.Debug.debugWindow = null;
939Spry.Debug.onloadDidFire = false;
940
941Spry.Utils.addLoadListener(function() { Spry.Debug.onloadDidFire = true; Spry.Debug.flushQueuedMessages(); });
942
943Spry.Debug.flushQueuedMessages = function()
944{
945	if (Spry.Debug.flushQueuedMessages.msgs)
946	{
947		var msgs = Spry.Debug.flushQueuedMessages.msgs;
948		for (var i = 0; i < msgs.length; i++)
949			Spry.Debug.debugOut(msgs[i].msg, msgs[i].color);
950		Spry.Debug.flushQueuedMessages.msgs = null;
951	}
952};
953
954Spry.Debug.createDebugWindow = function()
955{
956	if (!Spry.Debug.enableTrace || Spry.Debug.debugWindow || !Spry.Debug.onloadDidFire)
957		return;
958	try
959	{
960		Spry.Debug.debugWindow = document.createElement("div");
961		var div = Spry.Debug.debugWindow;
962		div.style.fontSize = "12px";
963		div.style.fontFamily = "console";
964		div.style.position = "absolute";
965		div.style.width = "400px";
966		div.style.height = "300px";
967		div.style.overflow = "auto";
968		div.style.border = "solid 1px black";
969		div.style.backgroundColor = "white";
970		div.style.color = "black";
971		div.style.bottom = "0px";
972		div.style.right = "0px";
973		// div.style.opacity = "0.5";
974		// div.style.filter = "alpha(opacity=50)";
975		div.setAttribute("id", "SpryDebugWindow");
976		document.body.appendChild(Spry.Debug.debugWindow);
977	}
978	catch (e) {}
979};
980
981Spry.Debug.debugOut = function(str, bgColor)
982{
983	if (!Spry.Debug.debugWindow)
984	{
985		Spry.Debug.createDebugWindow();
986		if (!Spry.Debug.debugWindow)
987		{
988			if (!Spry.Debug.flushQueuedMessages.msgs)
989				Spry.Debug.flushQueuedMessages.msgs = new Array;
990			Spry.Debug.flushQueuedMessages.msgs.push({msg: str, color: bgColor});
991			return;
992		}
993	}
994
995	var d = document.createElement("div");
996	if (bgColor)
997		d.style.backgroundColor = bgColor;
998	d.innerHTML = str;
999	Spry.Debug.debugWindow.appendChild(d);
1000};
1001
1002Spry.Debug.trace = function(str)
1003{
1004	Spry.Debug.debugOut(str);
1005};
1006
1007Spry.Debug.reportError = function(str)
1008{
1009	Spry.Debug.debugOut(str, "red");
1010};
1011
1012//////////////////////////////////////////////////////////////////////
1013//
1014// Spry.Data
1015//
1016//////////////////////////////////////////////////////////////////////
1017
1018Spry.Data = {};
1019Spry.Data.regionsArray = {};
1020Spry.Data.initRegionsOnLoad = true;
1021
1022Spry.Data.initRegions = function(rootNode)
1023{
1024	rootNode = rootNode ? Spry.$(rootNode) : document.body;
1025
1026	var lastRegionFound = null;
1027
1028	var regions = Spry.Utils.getNodesByFunc(rootNode, function(node)
1029	{
1030		try
1031		{
1032			if (node.nodeType != 1 /* Node.ELEMENT_NODE */)
1033				return false;
1034
1035			// Region elements must have an spryregion attribute with a
1036			// non-empty value. An id attribute is also required so we can
1037			// reference the region by name if necessary.
1038
1039			var attrName = "spry:region";
1040			var attr = node.attributes.getNamedItem(attrName);
1041			if (!attr)
1042			{
1043				attrName = "spry:detailregion";
1044				attr = node.attributes.getNamedItem(attrName);
1045			}
1046			if (attr)
1047			{
1048				if (lastRegionFound)
1049				{
1050					var parent = node.parentNode;
1051					while (parent)
1052					{
1053						if (parent == lastRegionFound)
1054						{
1055							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>");
1056							return false;
1057						}
1058						parent = parent.parentNode;
1059					}
1060				}
1061
1062				if (attr.value)
1063				{
1064					attr = node.attributes.getNamedItem("id");
1065					if (!attr || !attr.value)
1066					{
1067						// The node is missing an id attribute so add one.
1068						node.setAttribute("id", "spryregion" + (++Spry.Data.initRegions.nextUniqueRegionID));
1069					}
1070
1071					lastRegionFound = node;
1072					return true;
1073				}
1074				else
1075					Spry.Debug.reportError(attrName + " attributes require one or more data set names as values!");
1076			}
1077		}
1078		catch(e) {}
1079		return false;
1080	});
1081
1082	var name, dataSets, i;
1083	var newRegions = [];
1084
1085	for (i = 0; i < regions.length; i++)
1086	{
1087		var rgn = regions[i];
1088
1089		var isDetailRegion = false;
1090
1091		// Get the region name.
1092		name = rgn.attributes.getNamedItem("id").value;
1093
1094		attr = rgn.attributes.getNamedItem("spry:region");
1095		if (!attr)
1096		{
1097			attr = rgn.attributes.getNamedItem("spry:detailregion");
1098			isDetailRegion = true;
1099		}
1100
1101		if (!attr.value)
1102		{
1103			Spry.Debug.reportError("spry:region and spry:detailregion attributes require one or more data set names as values!");
1104			continue;
1105		}
1106
1107		// Remove the spry:region or spry:detailregion attribute so it doesn't appear in
1108		// the output generated by our processing of the dynamic region.
1109		rgn.attributes.removeNamedItem(attr.nodeName);
1110
1111		// Remove the hiddenRegionCSS class from the rgn.
1112		Spry.Utils.removeClassName(rgn, Spry.Data.Region.hiddenRegionClassName);
1113
1114		// Get the DataSets that should be bound to the region.
1115		dataSets = Spry.Data.Region.strToDataSetsArray(attr.value);
1116
1117		if (!dataSets.length)
1118		{
1119			Spry.Debug.reportError("spry:region or spry:detailregion attribute has no data set!");
1120			continue;
1121		}
1122
1123		var hasBehaviorAttributes = false;
1124		var hasSpryContent = false;
1125		var dataStr = "";
1126
1127		var parent = null;
1128		var regionStates = {};
1129		var regionStateMap = {};
1130
1131		// Check if there are any attributes on the region node that remap
1132		// the default states.
1133
1134		attr = rgn.attributes.getNamedItem("spry:readystate");
1135		if (attr && attr.value)
1136			regionStateMap["ready"] = attr.value;
1137		attr = rgn.attributes.getNamedItem("spry:errorstate");
1138		if (attr && attr.value)
1139			regionStateMap["error"] = attr.value;
1140		attr = rgn.attributes.getNamedItem("spry:loadingstate");
1141		if (attr && attr.value)
1142			regionStateMap["loading"] = attr.value;
1143		attr = rgn.attributes.getNamedItem("spry:expiredstate");
1144		if (attr && attr.value)
1145			regionStateMap["expired"] = attr.value;
1146
1147		// Find all of the processing instruction regions in the region.
1148		// Insert comments around the regions we find so we can identify them
1149		// easily when tokenizing the region html string.
1150
1151		var piRegions = Spry.Utils.getNodesByFunc(rgn, function(node)
1152		{
1153			try
1154			{
1155				if (node.nodeType == 1 /* ELEMENT_NODE */)
1156				{
1157					var attributes = node.attributes;
1158					var numPI = Spry.Data.Region.PI.orderedInstructions.length;
1159					var lastStartComment = null;
1160					var lastEndComment = null;
1161
1162					for (var i = 0; i < numPI; i++)
1163					{
1164						var piName = Spry.Data.Region.PI.orderedInstructions[i];
1165						var attr = attributes.getNamedItem(piName);
1166						if (!attr)
1167							continue;
1168
1169						var piDesc = Spry.Data.Region.PI.instructions[piName];
1170						var childrenOnly = (node == rgn) ? true : piDesc.childrenOnly;
1171						var openTag = piDesc.getOpenTag(node, piName);
1172						var closeTag = piDesc.getCloseTag(node, piName);
1173
1174						if (childrenOnly)
1175						{
1176								var oComment = document.createComment(openTag);
1177								var cComment = document.createComment(closeTag);
1178
1179								if (!lastStartComment)
1180									node.insertBefore(oComment, node.firstChild);
1181								else
1182									node.insertBefore(oComment, lastStartComment.nextSibling);
1183								lastStartComment = oComment;
1184
1185								if (!lastEndComment)
1186									node.appendChild(cComment);
1187								else
1188									node.insertBefore(cComment, lastEndComment);
1189								lastEndComment = cComment;
1190						}
1191						else
1192						{
1193							var parent = node.parentNode;
1194							parent.insertBefore(document.createComment(openTag), node);
1195							parent.insertBefore(document.createComment(closeTag), node.nextSibling);
1196						}
1197
1198						// If this is a spry:state processing instruction, record the state name
1199						// so we know that we should re-generate the region if we ever see that state.
1200
1201						if (piName == "spry:state")
1202							regionStates[attr.value] = true;
1203
1204						node.removeAttribute(piName);
1205					}
1206
1207					if (Spry.Data.Region.enableBehaviorAttributes)
1208					{
1209						var bAttrs = Spry.Data.Region.behaviorAttrs;
1210						for (var behaviorAttrName in bAttrs)
1211						{
1212							var bAttr = attributes.getNamedItem(behaviorAttrName);
1213							if (bAttr)
1214							{
1215								hasBehaviorAttributes = true;
1216								if (bAttrs[behaviorAttrName].setup)
1217									bAttrs[behaviorAttrName].setup(node, bAttr.value);
1218							}
1219						}
1220					}
1221				}
1222			}
1223			catch(e) {}
1224			return false;
1225		});
1226
1227		// Get the data in the region.
1228		dataStr = rgn.innerHTML;
1229
1230		// Argh! IE has an innerHTML bug where it will remove the quotes around any
1231		// attribute value that it thinks is a single word. This includes removing quotes
1232		// around our data references which is problematic since a single data reference
1233		// can be replaced with multiple words. If we are running in IE, we have to call
1234		// fixUpIEInnerHTML to get around this problem.
1235
1236		if (window.ActiveXObject && !Spry.Data.Region.disableIEInnerHTMLFixUp && dataStr.search(/=\{/) != -1)
1237		{
1238			if (Spry.Data.Region.debug)
1239				Spry.Debug.trace("<hr />Performing IE innerHTML fix up of Region: " + name + "<br /><br />" + Spry.Utils.encodeEntities(dataStr));
1240
1241			dataStr = Spry.Utils.fixUpIEInnerHTML(dataStr);
1242		}
1243
1244		if (Spry.Data.Region.debug)
1245			Spry.Debug.trace("<hr />Region template markup for '" + name + "':<br /><br />" + Spry.Utils.encodeEntities(dataStr));
1246
1247		if (!hasSpryContent)
1248		{
1249			// Clear the region.
1250			rgn.innerHTML = "";
1251		}
1252
1253		// Create a Spry.Data.Region object for this region.
1254		var region = new Spry.Data.Region(rgn, name, isDetailRegion, dataStr, dataSets, regionStates, regionStateMap, hasBehaviorAttributes);
1255		Spry.Data.regionsArray[region.name] = region;
1256		newRegions.push(region);
1257	}
1258
1259	for (var i = 0; i < newRegions.length; i++)
1260		newRegions[i].updateContent();
1261};
1262
1263Spry.Data.initRegions.nextUniqueRegionID = 0;
1264
1265Spry.Data.updateRegion = function(regionName)
1266{
1267	if (!regionName || !Spry.Data.regionsArray || !Spry.Data.regionsArray[regionName])
1268		return;
1269
1270	try { Spry.Data.regionsArray[regionName].updateContent(); }
1271	catch(e) { Spry.Debug.reportError("Spry.Data.updateRegion(" + regionName + ") caught an exception: " + e + "\n"); }
1272};
1273
1274Spry.Data.getRegion = function(regionName)
1275{
1276	return Spry.Data.regionsArray[regionName];
1277};
1278
1279
1280Spry.Data.updateAllRegions = function()
1281{
1282	if (!Spry.Data.regionsArray)
1283		return;
1284
1285	for (var regionName in Spry.Data.regionsArray)
1286		Spry.Data.updateRegion(regionName);
1287};
1288
1289Spry.Data.getDataSetByName = function(dataSetName)
1290{
1291	// Currently, there is no registry of mappings between
1292	// data set names and data set objects. For now, the assumption
1293	// is that the user has declared and created a data set in the
1294	// global space.
1295	//
1296	// We check for the presence of a global variable with the
1297	// specified name, and then make sure that its value is an
1298	// object with at least 2 of the data set base functions defined.
1299
1300	var ds = window[dataSetName];
1301	if (typeof ds != "object" || !ds.getData || !ds.filter)
1302		return null;
1303	return ds;
1304};
1305
1306//////////////////////////////////////////////////////////////////////
1307//
1308// Spry.Data.DataSet
1309//
1310//////////////////////////////////////////////////////////////////////
1311
1312Spry.Data.DataSet = function(options)
1313{
1314	Spry.Utils.Notifier.call(this);
1315
1316	this.name = "";
1317	this.internalID = Spry.Data.DataSet.nextDataSetID++;
1318	this.curRowID = 0;
1319	this.data = [];
1320	this.unfilteredData = null;
1321	this.dataHash = {};
1322	this.columnTypes = {};
1323	this.filterFunc = null;		// non-destructive filter function
1324	this.filterDataFunc = null;	// destructive filter function
1325
1326	this.distinctOnLoad = false;
1327	this.distinctFieldsOnLoad = null;
1328	this.sortOnLoad = null;
1329	this.sortOrderOnLoad = "ascending";
1330	this.keepSorted = false;
1331
1332	this.dataWasLoaded = false;
1333	this.pendingRequest = null;
1334
1335	this.lastSortColumns = [];
1336	this.lastSortOrder = "";
1337
1338	this.loadIntervalID = 0;
1339
1340	Spry.Utils.setOptions(this, options);
1341};
1342
1343Spry.Data.DataSet.prototype = new Spry.Utils.Notifier();
1344Spry.Data.DataSet.prototype.constructor = Spry.Data.DataSet;
1345
1346Spry.Data.DataSet.prototype.getData = function(unfiltered)
1347{
1348	return (unfiltered && this.unfilteredData) ? this.unfilteredData : this.data;
1349};
1350
1351Spry.Data.DataSet.prototype.getUnfilteredData = function()
1352{
1353	// XXX: Deprecated.
1354	return this.getData(true);
1355};
1356
1357Spry.Data.DataSet.prototype.getLoadDataRequestIsPending = function()
1358{
1359	return this.pendingRequest != null;
1360};
1361
1362Spry.Data.DataSet.prototype.getDataWasLoaded = function()
1363{
1364	return this.dataWasLoaded;
1365};
1366
1367Spry.Data.DataSet.prototype.getValue = function(valueName, rowContext)
1368{
1369	var result = undefined;
1370
1371	// If a rowContext is not defined, we default to
1372	// using the current row.
1373
1374	if (!rowContext)
1375		rowContext = this.getCurrentRow();
1376
1377	switch(valueName)
1378	{
1379		case "ds_RowNumber":
1380			result = this.getRowNumber(rowContext);
1381			break;
1382		case "ds_RowNumberPlus1":
1383			result = this.getRowNumber(rowContext) + 1;
1384			break;
1385		case "ds_RowCount":
1386			result = this.getRowCount();
1387			break;
1388		case "ds_UnfilteredRowCount":
1389			result = this.getRowCount(true);
1390			break;
1391		case "ds_CurrentRowNumber":
1392			result = this.getCurrentRowNumber();
1393			break;
1394		case "ds_CurrentRowID":
1395			result = this.getCurrentRowID();
1396			break;
1397		case "ds_EvenOddRow":
1398			result = (this.getRowNumber(rowContext) % 2) ? Spry.Data.Region.evenRowClassName : Spry.Data.Region.oddRowClassName;
1399			break;
1400		case "ds_SortOrder":
1401			result = this.getSortOrder();
1402			break;
1403		case "ds_SortColumn":
1404			result = this.getSortColumn();
1405			break;
1406		default:
1407			// We have an unknown value, check to see if the current
1408			// row has column value that matches the valueName.
1409			if (rowContext)
1410				result = rowContext[valueName];
1411			break;
1412	}
1413
1414	return result;
1415};
1416
1417Spry.Data.DataSet.prototype.setDataFromArray = function(arr, fireSyncLoad)
1418{
1419	this.notifyObservers("onPreLoad");
1420
1421	this.unfilteredData = null;
1422	this.filteredData = null;
1423	this.data = [];
1424	this.dataHash = {};
1425
1426	var arrLen = arr.length;
1427
1428	for (var i = 0; i < arrLen; i++)
1429	{
1430		var row = arr[i];
1431		if (row.ds_RowID == undefined)
1432			row.ds_RowID = i;
1433		this.dataHash[row.ds_RowID] = row;
1434		this.data.push(row);
1435	}
1436
1437	this.loadData(fireSyncLoad);
1438};
1439
1440Spry.Data.DataSet.prototype.loadData = function(syncLoad)
1441{
1442	// The idea here is that folks using the base class DataSet directly
1443	// would change the data in the DataSet manually and then call loadData()
1444	// to fire off an async notifications to say that it was ready for consumption.
1445	//
1446	// Firing off data changed notificataions synchronously from this method
1447	// can wreak havoc with complicated master/detail regions that use data sets
1448	// that have master/detail relationships with other data sets. Our data set
1449	// logic already handles async data loading nicely so we use a timer to fire
1450	// off the data changed notification to insure that it happens after this
1451	// function is finished and the JS stack unwinds.
1452	//
1453	// Other classes that derive from this class and load data synchronously
1454	// inside their loadData() implementation should also fire off an async
1455	// notification in this same manner to avoid this same problem.
1456
1457	var self = this;
1458
1459	this.pendingRequest = new Object;
1460	this.dataWasLoaded = false;
1461
1462	var loadCallbackFunc = function()
1463	{
1464		self.pendingRequest = null;
1465		self.dataWasLoaded = true;
1466
1467		self.applyColumnTypes();
1468
1469		self.disableNotifications();
1470		self.filterAndSortData();
1471		self.enableNotifications();
1472
1473		self.notifyObservers("onPostLoad");
1474		self.notifyObservers("onDataChanged");
1475	};
1476
1477	if (syncLoad)
1478		loadCallbackFunc();
1479	else
1480		this.pendingRequest.timer = setTimeout(loadCallbackFunc, 0);
1481};
1482
1483
1484Spry.Data.DataSet.prototype.filterAndSortData = function()
1485{
1486	// If there is a data filter installed, run it.
1487
1488	if (this.filterDataFunc)
1489		this.filterData(this.filterDataFunc, true);
1490
1491	// If the distinct flag was set, run through all the records in the recordset
1492	// and toss out any that are duplicates.
1493
1494	if (this.distinctOnLoad)
1495		this.distinct(this.distinctFieldsOnLoad);
1496
1497	// If sortOnLoad was set, sort the data based on the columns
1498	// specified in sortOnLoad.
1499
1500	if (this.keepSorted && this.getSortColumn())
1501		this.sort(this.lastSortColumns, this.lastSortOrder);
1502	else if (this.sortOnLoad)
1503		this.sort(this.sortOnLoad, this.sortOrderOnLoad);
1504
1505	// If there is a view filter installed, run it.
1506
1507	if (this.filterFunc)
1508		this.filter(this.filterFunc, true);
1509
1510	// The default "current" row is the first row of the data set.
1511	if (this.data && this.data.length > 0)
1512		this.curRowID = this.data[0]['ds_RowID'];
1513	else
1514		this.curRowID = 0;
1515};
1516
1517Spry.Data.DataSet.prototype.cancelLoadData = function()
1518{
1519	if (this.pendingRequest && this.pendingRequest.timer)
1520		clearTimeout(this.pendingRequest.timer);
1521	this.pendingRequest = null;
1522};
1523
1524Spry.Data.DataSet.prototype.getRowCount = function(unfiltered)
1525{
1526	var rows = this.getData(unfiltered);
1527	return rows ? rows.length : 0;
1528};
1529
1530Spry.Data.DataSet.prototype.getRowByID = function(rowID)
1531{
1532	if (!this.data)
1533		return null;
1534	return this.dataHash[rowID];
1535};
1536
1537Spry.Data.DataSet.prototype.getRowByRowNumber = function(rowNumber, unfiltered)
1538{
1539	var rows = this.getData(unfiltered);
1540	if (rows && rowNumber >= 0 && rowNumber < rows.length)
1541		return rows[rowNumber];
1542	return null;
1543};
1544
1545Spry.Data.DataSet.prototype.getCurrentRow = function()
1546{
1547	return this.getRowByID(this.curRowID);
1548};
1549
1550Spry.Data.DataSet.prototype.setCurrentRow = function(rowID)
1551{
1552	if (this.curRowID == rowID)
1553		return;
1554
1555	var nData = { oldRowID: this.curRowID, newRowID: rowID };
1556	this.curRowID = rowID;
1557	this.notifyObservers("onCurrentRowChanged", nData);
1558};
1559
1560Spry.Data.DataSet.prototype.getRowNumber = function(row, unfiltered)
1561{
1562	if (row)
1563	{
1564		var rows = this.getData(unfiltered);
1565		if (rows && rows.length)
1566		{
1567			var numRows = rows.length;
1568			for (var i = 0; i < numRows; i++)
1569			{
1570				if (rows[i] == row)
1571					return i;
1572			}
1573		}
1574	}
1575	return -1;
1576};
1577
1578Spry.Data.DataSet.prototype.getCurrentRowNumber = function()
1579{
1580	return this.getRowNumber(this.getCurrentRow());
1581};
1582
1583Spry.Data.DataSet.prototype.getCurrentRowID = function()
1584{
1585	return this.curRowID;
1586};
1587
1588Spry.Data.DataSet.prototype.setCurrentRowNumber = function(rowNumber)
1589{
1590	if (!this.data || rowNumber >= this.data.length)
1591	{
1592		Spry.Debug.trace("Invalid row number: " + rowNumber + "\n");
1593		return;
1594	}
1595
1596	var rowID = this.data[rowNumber]["ds_RowID"];
1597
1598	if (rowID == undefined || this.curRowID == rowID)
1599		return;
1600
1601	this.setCurrentRow(rowID);
1602};
1603
1604Spry.Data.DataSet.prototype.findRowsWithColumnValues = function(valueObj, firstMatchOnly, unfiltered)
1605{
1606	var results = [];
1607	var rows = this.getData(unfiltered);
1608	if (rows)
1609	{
1610		var numRows = rows.length;
1611		for (var i = 0; i < numRows; i++)
1612		{
1613			var row = rows[i];
1614			var matched = true;
1615
1616			for (var colName in valueObj)
1617			{
1618				if (valueObj[colName] != row[colName])
1619				{
1620					matched = false;
1621					break;
1622				}
1623			}
1624
1625			if (matched)
1626			{
1627				if (firstMatchOnly)
1628					return row;
1629				results.push(row);
1630			}
1631		}
1632	}
1633
1634	return firstMatchOnly ? null : results;
1635};
1636
1637Spry.Data.DataSet.prototype.setColumnType = function(columnNames, columnType)
1638{
1639	if (columnNames)
1640	{
1641		if (typeof columnNames == "string")
1642			columnNames = [ columnNames ];
1643		for (var i = 0; i < columnNames.length; i++)
1644			this.columnTypes[columnNames[i]] = columnType;
1645	}
1646};
1647
1648Spry.Data.DataSet.prototype.getColumnType = function(columnName)
1649{
1650	if (this.columnTypes[columnName])
1651		return this.columnTypes[columnName];
1652	return "string";
1653};
1654
1655Spry.Data.DataSet.prototype.applyColumnTypes = function()
1656{
1657	var rows = this.getData(true);
1658	var numRows = rows.length;
1659	var colNames = [];
1660
1661	if (numRows < 1)
1662		return;
1663
1664	for (var cname in this.columnTypes)
1665	{
1666		var ctype = this.columnTypes[cname];
1667		if (ctype != "string")
1668		{
1669			for (var i = 0; i < numRows; i++)
1670			{
1671				var row = rows[i];
1672				var val = row[cname];
1673				if (val != undefined)
1674				{
1675					if (ctype == "number")
1676						row[cname] = new Number(val);
1677					else if (ctype == "html")
1678						row[cname] = Spry.Utils.decodeEntities(val);
1679				}
1680			}
1681		}
1682	}
1683};
1684
1685Spry.Data.DataSet.prototype.distinct = function(columnNames)
1686{
1687	if (this.data)
1688	{
1689		var oldData = this.data;
1690		this.data = [];
1691		this.dataHash = {};
1692		var dataChanged = false;
1693
1694		var alreadySeenHash = {};
1695		var i = 0;
1696
1697		var keys = [];
1698
1699		if (typeof columnNames == "string")
1700			keys = [columnNames];
1701		else if (columnNames)
1702			keys = columnNames;
1703		else
1704			for (var recField in oldData[0])
1705				keys[i++] = recField;
1706
1707		for (var i = 0; i < oldData.length; i++)
1708		{
1709			var rec = oldData[i];
1710			var hashStr = "";
1711			for (var j=0; j < keys.length; j++)
1712			{
1713				recField = keys[j];
1714				if (recField != "ds_RowID")
1715				{
1716					if (hashStr)
1717						hashStr += ",";
1718					hashStr += recField + ":" + "\"" + rec[recField] + "\"";
1719				}
1720			}
1721			if (!alreadySeenHash[hashStr])
1722			{
1723				this.data.push(rec);
1724				this.dataHash[rec['ds_RowID']] = rec;
1725				alreadySeenHash[hashStr] = true;
1726			}
1727			else
1728				dataChanged = true;
1729		}
1730		if (dataChanged)
1731			this.notifyObservers('onDataChanged');
1732	}
1733};
1734
1735Spry.Data.DataSet.prototype.getSortColumn = function() {
1736	return (this.lastSortColumns && this.lastSortColumns.length > 0) ? this.lastSortColumns[0] : "";
1737};
1738
1739Spry.Data.DataSet.prototype.getSortOrder = function() {
1740	return this.lastSortOrder ? this.lastSortOrder : "";
1741};
1742
1743Spry.Data.DataSet.prototype.sort = function(columnNames, sortOrder)
1744{
1745	// columnNames can be either the name of a column to
1746	// sort on, or an array of column names, but it can't be
1747	// null/undefined.
1748
1749	if (!columnNames)
1750		return;
1751
1752	// If only one column name was specified for sorting, do a
1753	// secondary sort on ds_RowID so we get a stable sort order.
1754
1755	if (typeof columnNames == "string")
1756		columnNames = [ columnNames, "ds_RowID" ];
1757	else if (columnNames.length < 2 && columnNames[0] != "ds_RowID")
1758		columnNames.push("ds_RowID");
1759
1760	if (!sortOrder)
1761		sortOrder = "toggle";
1762
1763	if (sortOrder == "toggle")
1764	{
1765		if (this.lastSortColumns.length > 0 && this.lastSortColumns[0] == columnNames[0] && this.lastSortOrder == "ascending")
1766			sortOrder = "descending";
1767		else
1768			sortOrder = "ascending";
1769	}
1770
1771	if (sortOrder != "ascending" && sortOrder != "descending")
1772	{
1773		Spry.Debug.reportError("Invalid sort order type specified: " + sortOrder + "\n");
1774		return;
1775	}
1776
1777	var nData = {
1778		oldSortColumns: this.lastSortColumns,
1779		oldSortOrder: this.lastSortOrder,
1780		newSortColumns: columnNames,
1781		newSortOrder: sortOrder
1782	};
1783	this.notifyObservers("onPreSort", nData);
1784
1785	var cname = columnNames[columnNames.length - 1];
1786	var sortfunc = Spry.Data.DataSet.prototype.sort.getSortFunc(cname, this.getColumnType(cname), sortOrder);
1787
1788	for (var i = columnNames.length - 2; i >= 0; i--)
1789	{
1790		cname = columnNames[i];
1791		sortfunc = Spry.Data.DataSet.prototype.sort.buildSecondarySortFunc(Spry.Data.DataSet.prototype.sort.getSortFunc(cname, this.getColumnType(cname), sortOrder), sortfunc);
1792	}
1793
1794	if (this.unfilteredData)
1795	{
1796		this.unfilteredData.sort(sortfunc);
1797		if (this.filterFunc)
1798			this.filter(this.filterFunc, true);
1799	}
1800	else
1801		this.data.sort(sortfunc);
1802
1803	this.lastSortColumns = columnNames.slice(0); // Copy the array.
1804	this.lastSortOrder = sortOrder;
1805
1806	this.notifyObservers("onPostSort", nData);
1807};
1808
1809Spry.Data.DataSet.prototype.sort.getSortFunc = function(prop, type, order)
1810{
1811	var sortfunc = null;
1812	if (type == "number")
1813	{
1814		if (order == "ascending")
1815			sortfunc = function(a, b)
1816			{
1817				a = a[prop]; b = b[prop];
1818				if (a == undefined || b == undefined)
1819					return (a == b) ? 0 : (a ? 1 : -1);
1820				return a-b;
1821			};
1822		else // order == "descending"
1823			sortfunc = function(a, b)
1824			{
1825				a = a[prop]; b = b[prop];
1826				if (a == undefined || b == undefined)
1827					return (a == b) ? 0 : (a ? -1 : 1);
1828				return b-a;
1829			};
1830	}
1831	else if (type == "date")
1832	{
1833		if (order == "ascending")
1834			sortfunc = function(a, b)
1835			{
1836				var dA = a[prop];
1837				var dB = b[prop];
1838				dA = dA ? (new Date(dA)) : 0;
1839				dB = dB ? (new Date(dB)) : 0;
1840				return dA - dB;
1841			};
1842		else // order == "descending"
1843			sortfunc = function(a, b)
1844			{
1845				var dA = a[prop];
1846				var dB = b[prop];
1847				dA = dA ? (new Date(dA)) : 0;
1848				dB = dB ? (new Date(dB)) : 0;
1849				return dB - dA;
1850			};
1851	}
1852	else // type == "string" || type == "html"
1853	{
1854		if (order == "ascending")
1855			sortfunc = function(a, b){
1856				a = a[prop];
1857				b = b[prop];
1858				if (a == undefined || b == undefined)
1859					return (a == b) ? 0 : (a ? 1 : -1);
1860				var tA = a.toString();
1861				var tB = b.toString();
1862				var tA_l = tA.toLowerCase();
1863				var tB_l = tB.toLowerCase();
1864				var min_len = tA.length > tB.length ? tB.length : tA.length;
1865
1866				for (var i=0; i < min_len; i++)
1867				{
1868					var a_l_c = tA_l.charAt(i);
1869					var b_l_c = tB_l.charAt(i);
1870					var a_c = tA.charAt(i);
1871					var b_c = tB.charAt(i);
1872					if (a_l_c > b_l_c)
1873						return 1;
1874					else if (a_l_c < b_l_c)
1875						return -1;
1876					else if (a_c > b_c)
1877						return 1;
1878					else if (a_c < b_c)
1879						return -1;
1880				}
1881				if(tA.length == tB.length)
1882					return 0;
1883				else if (tA.length > tB.length)
1884					return 1;
1885				return -1;
1886			};
1887		else // order == "descending"
1888			sortfunc = function(a, b){
1889				a = a[prop];
1890				b = b[prop];
1891				if (a == undefined || b == undefined)
1892					return (a == b) ? 0 : (a ? -1 : 1);
1893				var tA = a.toString();
1894				var tB = b.toString();
1895				var tA_l = tA.toLowerCase();
1896				var tB_l = tB.toLowerCase();
1897				var min_len = tA.length > tB.length ? tB.length : tA.length;
1898				for (var i=0; i < min_len; i++)
1899				{
1900					var a_l_c = tA_l.charAt(i);
1901					var b_l_c = tB_l.charAt(i);
1902					var a_c = tA.charAt(i);
1903					var b_c = tB.charAt(i);
1904					if (a_l_c > b_l_c)
1905						return -1;
1906					else if (a_l_c < b_l_c)
1907						return 1;
1908					else if (a_c > b_c)
1909						return -1;
1910					else if (a_c < b_c)
1911						return 1;
1912				}
1913				if(tA.length == tB.length)
1914					return 0;
1915				else if (tA.length > tB.length)
1916					return -1;
1917				return 1;
1918			};
1919	}
1920
1921	return sortfunc;
1922};
1923
1924Spry.Data.DataSet.prototype.sort.buildSecondarySortFunc = function(funcA, funcB)
1925{
1926	return function(a, b)
1927	{
1928		var ret = funcA(a, b);
1929		if (ret == 0)
1930			ret = funcB(a, b);
1931		return ret;
1932	};
1933};
1934
1935Spry.Data.DataSet.prototype.filterData = function(filterFunc, filterOnly)
1936{
1937	// This is a destructive filter function.
1938
1939	var dataChanged = false;
1940
1941	if (!filterFunc)
1942	{
1943		// Caller wants to remove the filter.
1944
1945		this.filterDataFunc = null;
1946		dataChanged = true;
1947	}
1948	else
1949	{
1950		this.filterDataFunc = filterFunc;
1951
1952		if (this.dataWasLoaded && ((this.unfilteredData && this.unfilteredData.length) || (this.data && this.data.length)))
1953		{
1954			if (this.unfilteredData)
1955			{
1956				this.data = this.unfilteredData;
1957				this.unfilteredData = null;
1958			}
1959
1960			var oldData = this.data;
1961			this.data = [];
1962			this.dataHash = {};
1963
1964			for (var i = 0; i < oldData.length; i++)
1965			{
1966				var newRow = filterFunc(this, oldData[i], i);
1967				if (newRow)
1968				{
1969					this.data.push(newRow);
1970					this.dataHash[newRow["ds_RowID"]] = newRow;
1971				}
1972			}
1973
1974			dataChanged = true;
1975		}
1976	}
1977
1978	if (dataChanged)
1979	{
1980		if (!filterOnly)
1981		{
1982			this.disableNotifications();
1983			if (this.filterFunc)
1984				this.filter(this.filterFunc, true);
1985			this.enableNotifications();
1986		}
1987
1988		this.notifyObservers("onDataChanged");
1989	}
1990};
1991
1992Spry.Data.DataSet.prototype.filter = function(filterFunc, filterOnly)
1993{
1994	// This is a non-destructive filter function.
1995
1996	var dataChanged = false;
1997
1998	if (!filterFunc)
1999	{
2000		if (this.filterFunc && this.unfilteredData)
2001		{
2002			// Caller wants to remove the filter. Restore the unfiltered
2003			// data and trigger a data changed notification.
2004
2005			this.data = this.unfilteredData;
2006			this.unfilteredData = null;
2007			this.filterFunc = null;
2008			dataChanged = true;
2009		}
2010	}
2011	else
2012	{
2013		this.filterFunc = filterFunc;
2014
2015		if (this.dataWasLoaded && (this.unfilteredData || (this.data && this.data.length)))
2016		{
2017			if (!this.unfilteredData)
2018				this.unfilteredData = this.data;
2019
2020			var udata = this.unfilteredData;
2021			this.data = [];
2022
2023			for (var i = 0; i < udata.length; i++)
2024			{
2025				var newRow = filterFunc(this, udata[i], i);
2026
2027				if (newRow)
2028					this.data.push(newRow);
2029			}
2030
2031			dataChanged = true;
2032		}
2033	}
2034
2035	if (dataChanged)
2036		this.notifyObservers("onDataChanged");
2037};
2038
2039Spry.Data.DataSet.prototype.startLoadInterval = function(interval)
2040{
2041	this.stopLoadInterval();
2042	if (interval > 0)
2043	{
2044		var self = this;
2045		this.loadInterval = interval;
2046		this.loadIntervalID = setInterval(function() { self.loadData(); }, interval);
2047	}
2048};
2049
2050Spry.Data.DataSet.prototype.stopLoadInterval = function()
2051{
2052	if (this.loadIntervalID)
2053		clearInterval(this.loadIntervalID);
2054	this.loadInterval = 0;
2055	this.loadIntervalID = null;
2056};
2057
2058Spry.Data.DataSet.nextDataSetID = 0;
2059
2060//////////////////////////////////////////////////////////////////////
2061//
2062// Spry.Data.HTTPSourceDataSet
2063// base class for any DataSet that uses external
2064//
2065//////////////////////////////////////////////////////////////////////
2066
2067Spry.Data.HTTPSourceDataSet = function(dataSetURL, dataSetOptions)
2068{
2069	// Call the constructor for our DataSet base class so that
2070	// our base class properties get defined. We'll call setOptions
2071	// manually after we set up our HTTPSourceDataSet properties.
2072
2073	Spry.Data.DataSet.call(this);
2074
2075	// HTTPSourceDataSet Properties:
2076
2077	this.url = dataSetURL;
2078	this.dataSetsForDataRefStrings = new Array;
2079	this.hasDataRefStrings = false;
2080	this.useCache = true;
2081
2082	this.setRequestInfo(dataSetOptions, true);
2083
2084	Spry.Utils.setOptions(this, dataSetOptions, true);
2085
2086	this.recalculateDataSetDependencies();
2087
2088	if (this.loadInterval > 0)
2089		this.startLoadInterval(this.loadInterval);
2090}; // End of Spry.Data.HTTPSourceDataSet() constructor.
2091
2092Spry.Data.HTTPSourceDataSet.prototype = new Spry.Data.DataSet();
2093Spry.Data.HTTPSourceDataSet.prototype.constructor = Spry.Data.HTTPSourceDataSet;
2094
2095Spry.Data.HTTPSourceDataSet.prototype.setRequestInfo = function(requestInfo, undefineRequestProps)
2096{
2097	// Create a loadURL request object to store any load options
2098	// the caller specified. We'll fill in the URL at the last minute
2099	// before we make the actual load request because our URL needs
2100	// to be processed at the last possible minute in case it contains
2101	// data references.
2102
2103	this.requestInfo = new Spry.Utils.loadURL.Request();
2104	this.requestInfo.extractRequestOptions(requestInfo, undefineRequestProps);
2105
2106	// If the caller wants to use "POST" to fetch the data, but didn't
2107	// provide the content type, default to x-www-form-urlencoded.
2108
2109	if (this.requestInfo.method == "POST")
2110	{
2111		if (!this.requestInfo.headers)
2112			this.requestInfo.headers = {};
2113		if (!this.requestInfo.headers['Content-Type'])
2114			this.requestInfo.headers['Content-Type'] = "application/x-www-form-urlencoded; charset=UTF-8";
2115	}
2116};
2117
2118Spry.Data.HTTPSourceDataSet.prototype.recalculateDataSetDependencies = function()
2119{
2120	this.hasDataRefStrings = false;
2121
2122	// Clear all old callbacks that may have been registered.
2123
2124	var i = 0;
2125	for (i = 0; i < this.dataSetsForDataRefStrings.length; i++)
2126	{
2127		var ds = this.dataSetsForDataRefStrings[i];
2128		if (ds)
2129			ds.removeObserver(this);
2130	}
2131
2132	// Now run through the strings that may contain data references and figure
2133	// out what data sets they require. Note that the data references in these
2134	// strings must be fully qualified with a data set name. (ex: {dsDataSetName::columnName})
2135
2136	this.dataSetsForDataRefStrings = new Array();
2137
2138	var regionStrs = this.getDataRefStrings();
2139
2140	var dsCount = 0;
2141
2142	for (var n = 0; n < regionStrs.length; n++)
2143	{
2144		var tokens = Spry.Data.Region.getTokensFromStr(regionStrs[n]);
2145
2146		for (i = 0; tokens && i < tokens.length; i++)
2147		{
2148			if (tokens[i].search(/{[^}:]+::[^}]+}/) != -1)
2149			{
2150				var dsName = tokens[i].replace(/^\{|::.*\}/g, "");
2151				var ds = null;
2152				if (!this.dataSetsForDataRefStrings[dsName])
2153				{
2154					ds = Spry.Data.getDataSetByName(dsName);
2155					if (dsName && ds)
2156					{
2157						// The dataSetsForDataRefStrings array serves as both an
2158						// array of data sets and a hash lookup by name.
2159
2160						this.dataSetsForDataRefStrings[dsName] = ds;
2161						this.dataSetsForDataRefStrings[dsCount++] = ds;
2162						this.hasDataRefStrings = true;
2163					}
2164				}
2165			}
2166		}
2167	}
2168
2169	// Set up observers on any data sets our URL depends on.
2170
2171	for (i = 0; i < this.dataSetsForDataRefStrings.length; i++)
2172	{
2173		var ds = this.dataSetsForDataRefStrings[i];
2174		ds.addObserver(this);
2175	}
2176};
2177
2178Spry.Data.HTTPSourceDataSet.prototype.getDataRefStrings = function()
2179{
2180	var strArr = [];
2181	if (this.url) strArr.push(this.url);
2182	if (this.requestInfo && this.requestInfo.postData) strArr.push(this.requestInfo.postData);
2183	return strArr;
2184};
2185
2186Spry.Data.HTTPSourceDataSet.prototype.attemptLoadData = function()
2187{
2188	// We only want to trigger a load when all of our data sets have data!
2189	for (var i = 0; i < this.dataSetsForDataRefStrings.length; i++)
2190	{
2191		var ds = this.dataSetsForDataRefStrings[i];
2192		if (ds.getLoadDataRequestIsPending() || !ds.getDataWasLoaded())
2193			return;
2194	}
2195
2196	this.loadData();
2197};
2198
2199Spry.Data.HTTPSourceDataSet.prototype.onCurrentRowChanged = function(ds, data)
2200{
2201	this.attemptLoadData();
2202};
2203
2204Spry.Data.HTTPSourceDataSet.prototype.onPostSort = function(ds, data)
2205{
2206	this.attemptLoadData();
2207};
2208
2209Spry.Data.HTTPSourceDataSet.prototype.onDataChanged = function(ds, data)
2210{
2211	this.attemptLoadData();
2212};
2213
2214Spry.Data.HTTPSourceDataSet.prototype.loadData = function()
2215{
2216	if (!this.url)
2217		return;
2218
2219	this.cancelLoadData();
2220
2221	var url = this.url;
2222	var postData = this.requestInfo.postData;
2223
2224	if (this.hasDataRefStrings)
2225	{
2226		var allDataSetsReady = true;
2227
2228		for (var i = 0; i < this.dataSetsForDataRefStrings.length; i++)
2229		{
2230			var ds = this.dataSetsForDataRefStrings[i];
2231			if (ds.getLoadDataRequestIsPending())
2232				allDataSetsReady = false;
2233			else if (!ds.getDataWasLoaded())
2234			{
2235				// Kick off the load of this data set!
2236				ds.loadData();
2237				allDataSetsReady = false;
2238			}
2239		}
2240
2241		// If our data sets aren't ready, just return. We'll
2242		// get called back to load our data when they are all
2243		// done.
2244
2245		if (!allDataSetsReady)
2246			return;
2247
2248		url = Spry.Data.Region.processDataRefString(null, this.url, this.dataSetsForDataRefStrings);
2249		if (!url)
2250			return;
2251
2252		if (postData && (typeof postData) == "string")
2253			postData = Spry.Data.Region.processDataRefString(null, postData, this.dataSetsForDataRefStrings);
2254	}
2255
2256	this.notifyObservers("onPreLoad");
2257
2258	this.data = null;
2259	this.dataWasLoaded = false;
2260	this.unfilteredData = null;
2261	this.dataHash = null;
2262	this.curRowID = 0;
2263
2264	// At this point the url should've been processed if it contained any
2265	// data references. Set the url of the requestInfo structure and pass it
2266	// to LoadManager.loadData().
2267
2268	var req = this.requestInfo.clone();
2269	req.url = url;
2270	req.postData = postData;
2271
2272	this.pendingRequest = new Object;
2273	this.pendingRequest.data = Spry.Data.HTTPSourceDataSet.LoadManager.loadData(req, this, this.useCache);
2274};
2275
2276Spry.Data.HTTPSourceDataSet.prototype.cancelLoadData = function()
2277{
2278	if (this.pendingRequest)
2279	{
2280		Spry.Data.HTTPSourceDataSet.LoadManager.cancelLoadData(this.pendingRequest.data, this);
2281		this.pendingRequest = null;
2282	}
2283};
2284
2285Spry.Data.HTTPSourceDataSet.prototype.getURL = function() { return this.url; };
2286Spry.Data.HTTPSourceDataSet.prototype.setURL = function(url, requestOptions)
2287{
2288	if (this.url == url)
2289	{
2290		// The urls match so we may not have to do anything, but
2291		// before we bail early, check to see if the method and
2292		// postData that was last used was the same. If there is a
2293		// difference, we need to process the new URL.
2294
2295		if (!requestOptions || (this.requestInfo.method == requestOptions.method && (requestOptions.method != "POST" || this.requestInfo.postData == requestOptions.postData)))
2296			return;
2297	}
2298
2299	this.url = url;
2300
2301	this.setRequestInfo(requestOptions);
2302
2303	this.cancelLoadData();
2304	this.recalculateDataSetDependencies();
2305	this.dataWasLoaded = false;
2306};
2307
2308Spry.Data.HTTPSourceDataSet.prototype.setDataFromDoc = function(rawDataDoc)
2309{
2310	this.pendingRequest = null;
2311
2312	this.loadDataIntoDataSet(rawDataDoc);
2313	this.applyColumnTypes();
2314
2315	this.disableNotifications();
2316	this.filterAndSortData();
2317	this.enableNotifications();
2318
2319	this.notifyObservers("onPostLoad");
2320	this.notifyObservers("onDataChanged");
2321};
2322
2323Spry.Data.HTTPSourceDataSet.prototype.loadDataIntoDataSet = function(rawDataDoc)
2324{
2325	// this method needs to be overwritten by the descendent classes;
2326	// internal data structures (data & dataHash) have to load data from the source document (ResponseText | ResponseDoc);
2327
2328	this.dataHash = new Object;
2329	this.data = new Array;
2330	this.dataWasLoaded = true;
2331};
2332
2333Spry.Data.HTTPSourceDataSet.prototype.xhRequestProcessor = function(xhRequest)
2334{
2335	// This method needs to be overwritten by the descendent classes if other objects (like responseXML)
2336	// are going to be used as a data source
2337	// This implementation returns the responseText from xhRequest
2338
2339	var resp = xhRequest.responseText;
2340
2341	if (xhRequest.status == 200 || xhRequest.status == 0)
2342		return resp;
2343	return null;
2344};
2345
2346Spry.Data.HTTPSourceDataSet.prototype.sessionExpiredChecker = function(req)
2347{
2348	if (req.xhRequest.responseText == 'session expired')
2349		return true;
2350	return false;
2351};
2352
2353Spry.Data.HTTPSourceDataSet.prototype.setSessionExpiredChecker = function(checker)
2354{
2355	this.sessionExpiredChecker = checker;
2356};
2357
2358
2359Spry.Data.HTTPSourceDataSet.prototype.onRequestResponse = function(cachedRequest, req)
2360{
2361	this.setDataFromDoc(cachedRequest.rawData);
2362};
2363
2364Spry.Data.HTTPSourceDataSet.prototype.onRequestError = function(cachedRequest, req)
2365{
2366	this.notifyObservers("onLoadError", req);
2367	// Spry.Debug.reportError("Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.loadDataCallback(" + req.xhRequest.status + ") failed to load: " + req.url + "\n");
2368};
2369
2370Spry.Data.HTTPSourceDataSet.prototype.onRequestSessionExpired = function(cachedRequest, req)
2371{
2372	this.notifyObservers("onSessionExpired", req);
2373	//Spry.Debug.reportError("Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.loadDataCallback(" + req.xhRequest.status + ") failed to load: " + req.url + "\n");
2374};
2375
2376
2377Spry.Data.HTTPSourceDataSet.LoadManager = {};
2378Spry.Data.HTTPSourceDataSet.LoadManager.cache = [];
2379
2380Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest = function(reqInfo, xhRequestProcessor, sessionExpiredChecker)
2381{
2382	Spry.Utils.Notifier.call(this);
2383
2384	this.reqInfo = reqInfo;
2385	this.rawData = null;
2386	this.timer = null;
2387	this.state = Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.NOT_LOADED;
2388	this.xhRequestProcessor = xhRequestProcessor;
2389	this.sessionExpiredChecker = sessionExpiredChecker;
2390};
2391
2392Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype = new Spry.Utils.Notifier();
2393Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype.constructor = Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest;
2394
2395Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.NOT_LOADED      = 1;
2396Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_REQUESTED  = 2;
2397Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_FAILED     = 3;
2398Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_SUCCESSFUL = 4;
2399
2400Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype.loadDataCallback = function(req)
2401{
2402	if (req.xhRequest.readyState != 4)
2403		return;
2404
2405	var rawData = null;
2406	if (this.xhRequestProcessor) rawData = this.xhRequestProcessor(req.xhRequest);
2407
2408	if (this.sessionExpiredChecker)
2409	{
2410		Spry.Utils.setOptions(req, {'rawData': rawData}, false);
2411		if (this.sessionExpiredChecker(req))
2412		{
2413			this.state = Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_FAILED;
2414			this.notifyObservers("onRequestSessionExpired", req);
2415			this.observers.length = 0;
2416			return;
2417		}
2418	}
2419
2420	if (!rawData)
2421	{
2422		this.state = Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_FAILED;
2423		this.notifyObservers("onRequestError", req);
2424		this.observers.length = 0; // Clear the observers list.
2425		return;
2426	}
2427
2428	this.rawData = rawData;
2429	this.state = Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_SUCCESSFUL;
2430
2431	// Notify all of the cached request's observers!
2432	this.notifyObservers("onRequestResponse", req);
2433
2434	// Clear the observers list.
2435	this.observers.length = 0;
2436};
2437
2438Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype.loadData = function()
2439{
2440	// IE will synchronously fire our loadDataCallback() during the call
2441	// to an async Spry.Utils.loadURL() if the data for the url is already
2442	// in the browser's local cache. This can wreak havoc with complicated master/detail
2443	// regions that use data sets that have master/detail relationships with other
2444	// data sets. Our data set logic already handles async data loading nicely so we
2445	// use a timer to fire off the async Spry.Utils.loadURL() call to insure that any
2446	// data loading happens asynchronously after this function is finished.
2447
2448	var self = this;
2449	this.cancelLoadData();
2450	this.rawData = null;
2451	this.state = Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_REQUESTED;
2452
2453	var reqInfo = this.reqInfo.clone();
2454	reqInfo.successCallback = function(req) { self.loadDataCallback(req); };
2455	reqInfo.errorCallback = reqInfo.successCallback;
2456
2457	this.timer = setTimeout(function()
2458	{
2459		self.timer = null;
2460		Spry.Utils.loadURL(reqInfo.method, reqInfo.url, reqInfo.async, reqInfo.successCallback, reqInfo);
2461	}, 0);
2462};
2463
2464Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.prototype.cancelLoadData = function()
2465{
2466	if (this.state == Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_REQUESTED)
2467	{
2468		if (this.timer)
2469		{
2470			this.timer.clearTimeout();
2471			this.timer = null;
2472		}
2473
2474		this.rawData = null;
2475		this.state = Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.NOT_LOADED;
2476	}
2477};
2478
2479Spry.Data.HTTPSourceDataSet.LoadManager.getCacheKey = function(reqInfo)
2480{
2481	return reqInfo.method + "::" + reqInfo.url + "::" + reqInfo.postData + "::" + reqInfo.username;
2482};
2483
2484Spry.Data.HTTPSourceDataSet.LoadManager.loadData = function(reqInfo, ds, useCache)
2485{
2486	if (!reqInfo)
2487		return null;
2488
2489	var cacheObj = null;
2490	var cacheKey = null;
2491
2492	if (useCache)
2493	{
2494		cacheKey = Spry.Data.HTTPSourceDataSet.LoadManager.getCacheKey(reqInfo);
2495		cacheObj = Spry.Data.HTTPSourceDataSet.LoadManager.cache[cacheKey];
2496	}
2497
2498	if (cacheObj)
2499	{
2500		if (cacheObj.state == Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_REQUESTED)
2501		{
2502			if (ds)
2503				cacheObj.addObserver(ds);
2504			return cacheObj;
2505		}
2506		else if (cacheObj.state == Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest.LOAD_SUCCESSFUL)
2507		{
2508			// Data is already cached so if we have a data set, trigger an async call
2509			// that tells it to load its data.
2510			if (ds)
2511				setTimeout(function() { ds.setDataFromDoc(cacheObj.rawData); }, 0);
2512			return cacheObj;
2513		}
2514	}
2515
2516	// We're either loading this url for the first time, or an error occurred when
2517	// we last tried to load it, or the caller requested a forced load.
2518
2519	if (!cacheObj)
2520	{
2521		cacheObj = new Spry.Data.HTTPSourceDataSet.LoadManager.CachedRequest(reqInfo, (ds ? ds.xhRequestProcessor : null), (ds ? ds.sessionExpiredChecker : null));
2522
2523		if (useCache)
2524		{
2525			Spry.Data.HTTPSourceDataSet.LoadManager.cache[cacheKey] = cacheObj;
2526
2527			// Add an observer that will remove the cacheObj from the cache
2528			// if there is a load request failure.
2529			cacheObj.addObserver({ onRequestError: function() { Spry.Data.HTTPSourceDataSet.LoadManager.cache[cacheKey] = undefined; }});
2530		}
2531	}
2532
2533	if (ds)
2534		cacheObj.addObserver(ds);
2535
2536	cacheObj.loadData();
2537
2538	return cacheObj;
2539};
2540
2541Spry.Data.HTTPSourceDataSet.LoadManager.cancelLoadData = function(cacheObj, ds)
2542{
2543	if (cacheObj)
2544	{
2545		if (ds)
2546			cacheObj.removeObserver(ds);
2547		else
2548			cacheObj.cancelLoadData();
2549	}
2550};
2551
2552//////////////////////////////////////////////////////////////////////
2553//
2554// Spry.Data.XMLDataSet
2555//
2556//////////////////////////////////////////////////////////////////////
2557
2558Spry.Data.XMLDataSet = function(dataSetURL, dataSetPath, dataSetOptions)
2559{
2560	// Call the constructor for our HTTPSourceDataSet base class so that
2561	// our base class properties get defined.
2562
2563	this.xpath = dataSetPath;
2564	this.doc = null;
2565	this.subPaths = [];
2566	this.entityEncodeStrings = true;
2567
2568	Spry.Data.HTTPSourceDataSet.call(this, dataSetURL, dataSetOptions);
2569
2570	// Callers are allowed to pass either a string, an object or an array of
2571	// strings and/or objects for the 'subPaths' option, so make sure we normalize
2572	// the subPaths value to be an array.
2573
2574	var jwType = typeof this.subPaths;
2575	if (jwType == "string" || (jwType == "object" && this.subPaths.constructor != Array))
2576		this.subPaths = [ this.subPaths ];
2577}; // End of Spry.Data.XMLDataSet() constructor.
2578
2579Spry.Data.XMLDataSet.prototype = new Spry.Data.HTTPSourceDataSet();
2580Spry.Data.XMLDataSet.prototype.constructor = Spry.Data.XMLDataSet;
2581
2582
2583Spry.Data.XMLDataSet.prototype.getDataRefStrings = function()
2584{
2585	var strArr = [];
2586	if (this.url) strArr.push(this.url);
2587	if (this.xpath) strArr.push(this.xpath);
2588	if (this.requestInfo && this.requestInfo.postData) strArr.push(this.requestInfo.postData);
2589	return strArr;
2590};
2591
2592Spry.Data.XMLDataSet.prototype.getDocument = function() { return this.doc; };
2593Spry.Data.XMLDataSet.prototype.getXPath = function() { return this.xpath; };
2594Spry.Data.XMLDataSet.prototype.setXPath = function(path)
2595{
2596	if (this.xpath != path)
2597	{
2598		this.xpath = path;
2599		if (this.dataWasLoaded && this.doc)
2600		{
2601			this.notifyObservers("onPreLoad");
2602			this.setDataFromDoc(this.doc);
2603		}
2604	}
2605};
2606
2607Spry.Data.XMLDataSet.nodeContainsElementNode = function(node)
2608{
2609	if (node)
2610	{
2611		node = node.firstChild;
2612
2613		while (node)
2614		{
2615			if (node.nodeType == 1 /* Node.ELEMENT_NODE */)
2616				return true;
2617
2618			node = node.nextSibling;
2619		}
2620	}
2621	return false;
2622};
2623
2624Spry.Data.XMLDataSet.getNodeText = function(node, encodeText, encodeCData)
2625{
2626	var txt = "";
2627
2628	if (!node)
2629		return;
2630
2631	try
2632	{
2633		var child = node.firstChild;
2634
2635		while (child)
2636		{
2637			try
2638			{
2639				if (child.nodeType == 3 /* TEXT_NODE */)
2640					txt += encodeText ? Spry.Utils.encodeEntities(child.data) : child.data;
2641				else if (child.nodeType == 4 /* CDATA_SECTION_NODE */)
2642					txt += encodeCData ? Spry.Utils.encodeEntities(child.data) : child.data;
2643			} catch (e) { Spry.Debug.reportError("Spry.Data.XMLDataSet.getNodeText() exception caught: " + e + "\n"); }
2644
2645			child = child.nextSibling;
2646		}
2647	}
2648	catch (e) { Spry.Debug.reportError("Spry.Data.XMLDataSet.getNodeText() exception caught: " + e + "\n"); }
2649
2650	return txt;
2651};
2652
2653Spry.Data.XMLDataSet.createObjectForNode = function(node, encodeText, encodeCData)
2654{
2655	if (!node)
2656		return null;
2657
2658	var obj = new Object();
2659	var i = 0;
2660	var attr = null;
2661
2662	try
2663	{
2664		for (i = 0; i < node.attributes.length; i++)
2665		{
2666			attr = node.attributes[i];
2667			if (attr && attr.nodeType == 2 /* Node.ATTRIBUTE_NODE */)
2668				obj["@" + attr.name] = attr.value;
2669		}
2670	}
2671	catch (e)
2672	{
2673		Spry.Debug.reportError("Spry.Data.XMLDataSet.createObjectForNode() caught exception while accessing attributes: " + e + "\n");
2674	}
2675
2676	var child = node.firstChild;
2677
2678	if (child && !child.nextSibling && child.nodeType != 1 /* Node.ELEMENT_NODE */)
2679	{
2680		// We have a single child and it's not an element. It must
2681		// be the text value for this node. Add it to the record set and
2682		// give it the column the same name as the node.
2683
2684		obj[node.nodeName] = Spry.Data.XMLDataSet.getNodeText(node, encodeText, encodeCData);
2685	}
2686
2687	while (child)
2688	{
2689		// Add the text value for each child element. Note that
2690		// We skip elements that have element children (sub-elements)
2691		// because we don't handle multi-level data sets right now.
2692
2693		if (child.nodeType == 1 /* Node.ELEMENT_NODE */)
2694		{
2695			if (!Spry.Data.XMLDataSet.nodeContainsElementNode(child))
2696			{
2697				obj[child.nodeName] = Spry.Data.XMLDataSet.getNodeText(child, encodeText, encodeCData);
2698
2699				// Now add properties for any attributes on the child. The property
2700				// name will be of the form "<child.nodeName>/@<attr.name>".
2701				try
2702				{
2703					var namePrefix = child.nodeName + "/@";
2704
2705					for (i = 0; i < child.attributes.length; i++)
2706					{
2707						attr = child.attributes[i];
2708						if (attr && attr.nodeType == 2 /* Node.ATTRIBUTE_NODE */)
2709							obj[namePrefix + attr.name] = attr.value;
2710					}
2711				}
2712				catch (e)
2713				{
2714					Spry.Debug.reportError("Spry.Data.XMLDataSet.createObjectForNode() caught exception while accessing attributes: " + e + "\n");
2715				}
2716			}
2717		}
2718
2719		child = child.nextSibling;
2720	}
2721
2722	return obj;
2723};
2724
2725Spry.Data.XMLDataSet.getRecordSetFromXMLDoc = function(xmlDoc, path, suppressColumns, entityEncodeStrings)
2726{
2727	if (!xmlDoc || !path)
2728		return null;
2729
2730	var recordSet = new Object();
2731	recordSet.xmlDoc = xmlDoc;
2732	recordSet.xmlPath = path;
2733	recordSet.dataHash = new Object;
2734	recordSet.data = new Array;
2735	recordSet.getData = function() { return this.data; };
2736
2737	// Use the XPath library to find the nodes that will
2738	// make up our data set. The result should be an array
2739	// of subtrees that we need to flatten.
2740
2741	var ctx = new ExprContext(xmlDoc);
2742	var pathExpr = xpathParse(path);
2743	var e = pathExpr.evaluate(ctx);
2744
2745	// XXX: Note that we should check the result type of the evaluation
2746	// just in case it's a boolean, string, or number value instead of
2747	// a node set.
2748
2749	var nodeArray = e.nodeSetValue();
2750
2751	var isDOMNodeArray = true;
2752
2753	if (nodeArray && nodeArray.length > 0)
2754		isDOMNodeArray = nodeArray[0].nodeType != 2 /* Node.ATTRIBUTE_NODE */;
2755
2756	var nextID = 0;
2757
2758	var encodeText = true;
2759	var encodeCData = false;
2760	if (typeof entityEncodeStrings == "boolean")
2761		encodeText = encodeCData = entityEncodeStrings;
2762
2763	// We now have the set of nodes that make up our data set
2764	// so process each one.
2765
2766	for (var i = 0; i < nodeArray.length; i++)
2767	{
2768		var rowObj = null;
2769
2770		if (suppressColumns)
2771			rowObj = new Object;
2772		else
2773		{
2774			if (isDOMNodeArray)
2775				rowObj = Spry.Data.XMLDataSet.createObjectForNode(nodeArray[i], encodeText, encodeCData);
2776			else // Must be a Node.ATTRIBUTE_NODE array.
2777			{
2778				rowObj = new Object;
2779				rowObj["@" + nodeArray[i].name] = nodeArray[i].value;
2780			}
2781		}
2782
2783		if (rowObj)
2784		{
2785			// We want to make sure that every row has a unique ID and since we
2786			// we don't know which column, if any, in this recordSet is a unique
2787			// identifier, we generate a unique ID ourselves and store it under
2788			// the ds_RowID column in the row object.
2789
2790			rowObj['ds_RowID'] = nextID++;
2791			rowObj['ds_XMLNode'] = nodeArray[i];
2792			recordSet.dataHash[rowObj['ds_RowID']] = rowObj;
2793			recordSet.data.push(rowObj);
2794		}
2795	}
2796
2797	return recordSet;
2798};
2799
2800Spry.Data.XMLDataSet.PathNode = function(path)
2801{
2802	this.path = path;
2803	this.subPaths = [];
2804	this.xpath = "";
2805};
2806
2807Spry.Data.XMLDataSet.PathNode.prototype.addSubPath = function(path)
2808{
2809	var node = this.findSubPath(path);
2810	if (!node)
2811	{
2812		node = new Spry.Data.XMLDataSet.PathNode(path);
2813		this.subPaths.push(node);
2814	}
2815	return node;
2816};
2817
2818Spry.Data.XMLDataSet.PathNode.prototype.findSubPath = function(path)
2819{
2820	var numSubPaths = this.subPaths.length;
2821	for (var i = 0; i < numSubPaths; i++)
2822	{
2823		var subPath = this.subPaths[i];
2824		if (path == subPath.path)
2825			return subPath;
2826	}
2827	return null;
2828};
2829
2830Spry.Data.XMLDataSet.PathNode.prototype.consolidate = function()
2831{
2832	// This method recursively runs through the path tree and
2833	// tries to flatten any nodes that have no XPath and one child.
2834	// The flattening involves merging the parent's path component
2835	// with its child path component.
2836
2837	var numSubPaths = this.subPaths.length;
2838	if (!this.xpath && numSubPaths == 1)
2839	{
2840		// Consolidate!
2841		var subPath = this.subPaths[0];
2842		this.path += ((subPath[0] != "/") ? "/" : "") + subPath.path;
2843		this.xpath = subPath.xpath;
2844		this.subPaths = subPath.subPaths;
2845		this.consolidate();
2846		return;
2847	}
2848
2849	for (var i = 0; i < numSubPaths; i++)
2850		this.subPaths[i].consolidate();
2851};
2852
2853/* This method is commented out so that it gets stripped when the file
2854   is minimized. Please do not remove this from the full version of the
2855   file! It is needed for debugging.
2856
2857Spry.Data.XMLDataSet.PathNode.prototype.dump = function(indentStr)
2858{
2859	var didPre = false;
2860	var result = "";
2861	if (!indentStr)
2862	{
2863		indentStr = "";
2864		didPre = true;
2865		result = "<pre>";
2866	}
2867	result += indentStr + "<strong>" + this.path + "</strong>" + (this.xpath ? " <em>-- xpath(" + Spry.Utils.encodeEntities(this.xpath) + ")</em>" : "") + "\n";
2868	var numSubPaths = this.subPaths.length;
2869	indentStr += "    ";
2870	for (var i = 0; i < numSubPaths; i++)
2871		result += this.subPaths[i].dump(indentStr);
2872	if (didPre)
2873		result += "</pre>";
2874	return result;
2875};
2876*/
2877
2878Spry.Data.XMLDataSet.prototype.convertXPathsToPathTree = function(xpathArray)
2879{
2880	var xpaLen = xpathArray.length;
2881	var root = new Spry.Data.XMLDataSet.PathNode("");
2882
2883	for (var i = 0; i < xpaLen; i++)
2884	{
2885		// Convert any "//" in the XPath to our placeholder value.
2886		// We need to do that so they don't get removed when we split the
2887		// path into components.
2888
2889		var xpath = xpathArray[i];
2890		var cleanXPath = xpath.replace(/\/\//g, "/__SPRYDS__");
2891		cleanXPath = cleanXPath.replace(/^\//, ""); // Strip any leading slash.
2892		var pathItems = cleanXPath.split(/\//);
2893		var pathItemsLen = pathItems.length;
2894
2895		// Now add each path component to our tree.
2896
2897		var node = root;
2898		for (var j = 0; j < pathItemsLen; j++)
2899		{
2900			// If this path component has a placeholder in it, convert it
2901			// back to a double slash.
2902
2903			var path = pathItems[j].replace(/__SPRYDS__/, "//");
2904			node = node.addSubPath(path);
2905		}
2906
2907		// Now add the full xpath to the node that represents the
2908		// last path component in our path.
2909
2910		node.xpath = xpath;
2911	}
2912
2913	// Now that we have a tree of nodes. Tell the root to consolidate
2914	// itself so we get a tree that is as flat as possible. This reduces
2915	// the number of XPaths we will have to flatten.
2916
2917	root.consolidate();
2918	return root;
2919};
2920
2921Spry.Data.XMLDataSet.prototype.flattenSubPaths = function(rs, subPaths)
2922{
2923	if (!rs || !subPaths)
2924		return;
2925
2926	var numSubPaths = subPaths.length;
2927	if (numSubPaths < 1)
2928		return;
2929
2930	var data = rs.data;
2931	var dataHash = {};
2932
2933	// Convert all of the templated subPaths to XPaths with real values.
2934	// We also need a "cleaned" version of the XPath which contains no
2935	// expressions in it, so that we can pre-pend it to the column names
2936	// of any nested data we find.
2937
2938	var xpathArray = [];
2939	var cleanedXPathArray = [];
2940
2941	for (var i = 0; i < numSubPaths; i++)
2942	{
2943		// The elements of the subPaths array can be XPath strings,
2944		// or objects that describe a path with nested sub-paths below
2945		// it, so make sure we properly extract out the XPath to use.
2946
2947		var subPath = subPaths[i];
2948		if (typeof subPath == "object")
2949			subPath = subPath.path;
2950		if (!subPath)
2951			subPath = "";
2952
2953		// Convert any data references in the XPath to real values!
2954
2955		xpathArray[i] = Spry.Data.Region.processDataRefString(null, subPath, this.dataSetsForDataRefStrings);
2956
2957		// Create a clean version of the XPath by stripping out any
2958		// expressions it may contain.
2959
2960		cleanedXPathArray[i] = xpathArray[i].replace(/\[.*\]/g, "");
2961	}
2962
2963	// For each row of the base record set passed in, generate a flattened
2964	// recordset from each subPath, and then join the results with the base
2965	// row. The row from the base data set will be duplicated to match the
2966	// number of rows matched by the subPath. The results are then merged.
2967
2968	var row;
2969	var numRows = data.length;
2970	var newData = [];
2971
2972	// Iterate over each row of the base record set.
2973
2974	for (var i = 0; i < numRows; i++)
2975	{
2976		row = data[i];
2977		var newRows = [ row ];
2978
2979		// Iterate over every subPath passed into this function.
2980
2981		for (var j = 0; j < numSubPaths; j++)
2982		{
2983			// Search for all nodes that match the given XPath underneath
2984			// the XML node for the base row and flatten the data into
2985			// a tabular recordset structure.
2986
2987			var newRS = Spry.Data.XMLDataSet.getRecordSetFromXMLDoc(row.ds_XMLNode, xpathArray[j], (subPaths[j].xpath ? false : true), this.entityEncodeStrings);
2988
2989			// If this subPath has additional subPaths beneath it,
2990			// flatten and join them with the recordset we just created.
2991
2992			if (newRS && newRS.data && newRS.data.length)
2993			{
2994				if (typeof subPaths[j] == "object" && subPaths[j].subPaths)
2995				{
2996					// The subPaths property can be either an XPath string,
2997					// an Object describing a subPath and paths beneath it,
2998					// or an Array of XPath strings or objects. We need to
2999					// normalize these variations into an array to simplify
3000					// our processing.
3001
3002					var sp = subPaths[j].subPaths;
3003					spType = typeof sp;
3004					if (spType == "string")
3005						sp = [ sp ];
3006					else if (spType == "object" && spType.constructor == Object)
3007						sp = [ sp ];
3008
3009					// Now that we have a normalized array of sub paths, flatten
3010					// them and join them to the recordSet we just calculated.
3011
3012					this.flattenSubPaths(newRS, sp);
3013				}
3014
3015				var newRSData = newRS.data;
3016				var numRSRows = newRSData.length;
3017
3018				var cleanedXPath = cleanedXPathArray[j] + "/";
3019
3020				var numNewRows = newRows.length;
3021				var joinedRows = [];
3022
3023				// Iterate over all rows in our newRows array. Note that the
3024				// contents of newRows changes after the execution of this
3025				// loop, allowing us to perform more joins when more than
3026				// one subPath is specified.
3027
3028				for (var k = 0; k < numNewRows; k++)
3029				{
3030					var newRow = newRows[k];
3031
3032					// Iterate over all rows in the record set generated
3033					// from the current subPath. We are going to create
3034					// m*n rows for the joined table, where m is the number
3035					// of rows in the newRows array, and n is the number of
3036					// rows in the current subPath recordset.
3037
3038					for (var l = 0; l < numRSRows; l++)
3039					{
3040						// Create a new row that will house the join result.
3041
3042						var newRowObj = new Object;
3043						var newRSRow = newRSData[l];
3044
3045						// Copy the columns from the newRow into our row
3046						// object.
3047
3048						for (prop in newRow)
3049							newRowObj[prop] = newRow[prop];
3050
3051						// Copy the data from the current row of the record set
3052						// into our new row object, but make sure to store the
3053						// data in columns that have the subPath prepended to
3054						// it so that it doesn't collide with any columns from
3055						// the newRows row data.
3056
3057						for (var prop in newRSRow)
3058						{
3059							// The new propery name will have the subPath used prepended to it.
3060							var newPropName = cleanedXPath + prop;
3061
3062							// We need to handle the case where the tag name of the node matched
3063							// by the XPath has a value. In that specific case, the name of the
3064							// property should be the cleanedXPath itself. For example:
3065							//
3066							//	<employees>
3067							//		<employee>Bob</employee>
3068							//		<employee>Joe</employee>
3069							//	</employees>
3070							//
3071							// XPath: /employees/employee
3072							//
3073							// The property name that contains "Bob" and "Joe" will be "employee".
3074							// So in our new row, we need to call this column "/employees/employee"
3075							// instead of "/employees/employee/employee" which would be incorrect.
3076
3077							if (cleanedXPath == (prop + "/") || cleanedXPath.search(new RegExp("\\/" + prop + "\\/$")) != -1)
3078								newPropName = cleanedXPathArray[j];
3079
3080							// Copy the props to the new object using the new property name.
3081
3082							newRowObj[newPropName] = newRSRow[prop];
3083						}
3084
3085						// Now add this row to the array that tracks all of the new
3086						// rows we've just created.
3087
3088						joinedRows.push(newRowObj);
3089					}
3090				}
3091
3092				// Set the newRows array equal to our joinedRows we just created,
3093				// so that when we flatten the data for the next subPath, it gets
3094				// joined with our new set of rows.
3095
3096				newRows = joinedRows;
3097			}
3098		}
3099
3100		newData = newData.concat(newRows);
3101	}
3102
3103	// Now that we have a new set of joined rows, we need to run through
3104	// all of the rows and make sure they all have a unique row ID and
3105	// rebuild our dataHash.
3106
3107	data = newData;
3108	numRows = data.length;
3109
3110	for (i = 0; i < numRows; i++)
3111	{
3112		row = data[i];
3113		row.ds_RowID = i;
3114		dataHash[row.ds_RowID] = row;
3115	}
3116
3117	// We're all done, so stuff the new data and dataHash
3118	// back into the base recordSet.
3119
3120	rs.data = data;
3121	rs.dataHash = dataHash;
3122};
3123
3124Spry.Data.XMLDataSet.prototype.loadDataIntoDataSet = function(rawDataDoc)
3125{
3126	var rs = null;
3127	var mainXPath = Spry.Data.Region.processDataRefString(null, this.xpath, this.dataSetsForDataRefStrings);
3128	var subPaths = this.subPaths;
3129	var suppressColumns = false;
3130
3131	if (this.subPaths && this.subPaths.length > 0)
3132	{
3133		// Some subPaths were specified. Convert any data references in each subPath
3134		// to real data. While we're at it, convert any subPaths that are relative
3135		// to our main XPath to absolute paths.
3136
3137		var processedSubPaths = [];
3138		var numSubPaths = subPaths.length;
3139		for (var i = 0; i < numSubPaths; i++)
3140		{
3141			var subPathStr = Spry.Data.Region.processDataRefString(null, subPaths[i], this.dataSetsForDataRefStrings);
3142			if (subPathStr.charAt(0) != '/')
3143				subPathStr = mainXPath + "/" + subPathStr;
3144			processedSubPaths.push(subPathStr);
3145		}
3146
3147		// We need to add our main XPath to the set of subPaths and generate a path
3148		// tree so we can find the XPath to the common parent of all the paths, just
3149		// in case the user specified a path that was outside of our main XPath.
3150
3151		processedSubPaths.unshift(mainXPath);
3152		var commonParent = this.convertXPathsToPathTree(processedSubPaths);
3153
3154		// The root node of the resulting path tree should contain the XPath
3155		// to the common parent. Make this the XPath we generate our initial
3156		// set of rows from so we can group the results of flattening the other
3157		// subPaths in predictable/expected manner.
3158
3159		mainXPath = commonParent.path;
3160		subPaths = commonParent.subPaths;
3161
3162		// If the XPath to the common parent we calculated isn't our main XPath
3163		// or any of the subPaths specified by the user, it is used purely for
3164		// grouping and joining the data we will flatten. We don't want to include
3165		// any of the columns for the rows created for the common parent XPath since
3166		// the user did not ask for it.
3167
3168		suppressColumns = commonParent.xpath ? false : true;
3169	}
3170
3171	rs = Spry.Data.XMLDataSet.getRecordSetFromXMLDoc(rawDataDoc, mainXPath, suppressColumns, this.entityEncodeStrings);
3172
3173	if (!rs)
3174	{
3175		Spry.Debug.reportError("Spry.Data.XMLDataSet.loadDataIntoDataSet() failed to create dataSet '" + this.name + "'for '" + this.xpath + "' - " + this.url + "\n");
3176		return;
3177	}
3178
3179	// Now that we have our base set of rows, flatten any additional subPaths
3180	// specified by the user.
3181
3182	this.flattenSubPaths(rs, subPaths);
3183
3184	this.doc = rs.xmlDoc;
3185	this.data = rs.data;
3186	this.dataHash = rs.dataHash;
3187	this.dataWasLoaded = (this.doc != null);
3188};
3189
3190Spry.Data.XMLDataSet.prototype.xhRequestProcessor = function(xhRequest)
3191{
3192	// XMLDataSet uses the responseXML from the xhRequest
3193
3194	var resp = xhRequest.responseXML;
3195	var manualParseRequired = false;
3196
3197	if (xhRequest.status != 200)
3198	{
3199		if (xhRequest.status == 0)
3200		{
3201			// The page that is attempting to load data was probably loaded with
3202			// a file:// url. Mozilla based browsers will actually provide the complete DOM
3203			// tree for the data, but IE provides an empty document node so try to parse
3204			// the xml text manually to create a dom tree we can use.
3205
3206			if (xhRequest.responseText && (!resp || !resp.firstChild))
3207				manualParseRequired = true;
3208		}
3209	}
3210	else if (!resp)
3211	{
3212		// The server said it sent us data, but for some reason we don't have
3213		// an XML DOM document. Some browsers won't auto-create an XML DOM
3214		// unless the server used a content-type of "text/xml" or "application/xml".
3215		// Try to manually parse the XML string, just in case the server
3216		// gave us an unexpected Content-Type.
3217
3218		manualParseRequired = true;
3219	}
3220
3221	if (manualParseRequired)
3222		resp = Spry.Utils.stringToXMLDoc(xhRequest.responseText);
3223
3224	if (!resp  || !resp.firstChild || resp.firstChild.nodeName == "parsererror")
3225		return null;
3226
3227	return resp;
3228};
3229
3230Spry.Data.XMLDataSet.prototype.sessionExpiredChecker = function(req)
3231{
3232	if (req.xhRequest.responseText == 'session expired')
3233		return true;
3234	else
3235	{
3236		if (req.rawData)
3237		{
3238			var firstChild = req.rawData.documentElement.firstChild;
3239			if (firstChild && firstChild.nodeValue == "session expired")
3240				return true;
3241		}
3242	}
3243	return false;
3244};
3245
3246//////////////////////////////////////////////////////////////////////
3247//
3248// Spry.Data.Region
3249//
3250//////////////////////////////////////////////////////////////////////
3251
3252Spry.Data.Region = function(regionNode, name, isDetailRegion, data, dataSets, regionStates, regionStateMap, hasBehaviorAttributes)
3253{
3254	this.regionNode = regionNode;
3255	this.name = name;
3256	this.isDetailRegion = isDetailRegion;
3257	this.data = data;
3258	this.dataSets = dataSets;
3259	this.hasBehaviorAttributes = hasBehaviorAttributes;
3260	this.tokens = null;
3261	this.currentState = null;
3262	this.states = { ready: true };
3263	this.stateMap = {};
3264
3265	Spry.Utils.setOptions(this.states, regionStates);
3266	Spry.Utils.setOptions(this.stateMap, regionStateMap);
3267
3268	// Add the region as an observer to the dataSet!
3269	for (var i = 0; i < this.dataSets.length; i++)
3270	{
3271		var ds = this.dataSets[i];
3272
3273		try
3274		{
3275			if (ds)
3276				ds.addObserver(this);
3277		}
3278		catch(e) { Spry.Debug.reportError("Failed to add '" + this.name + "' as a dataSet observer!\n"); }
3279	}
3280}; // End of Spry.Data.Region() constructor.
3281
3282Spry.Data.Region.hiddenRegionClassName = "SpryHiddenRegion";
3283Spry.Data.Region.evenRowClassName = "even";
3284Spry.Data.Region.oddRowClassName = "odd";
3285Spry.Data.Region.notifiers = {};
3286Spry.Data.Region.evalScripts = true;
3287
3288Spry.Data.Region.addObserver = function(regionID, observer)
3289{
3290	var n = Spry.Data.Region.notifiers[regionID];
3291	if (!n)
3292	{
3293		n = new Spry.Utils.Notifier();
3294		Spry.Data.Region.notifiers[regionID] = n;
3295	}
3296	n.addObserver(observer);
3297};
3298
3299Spry.Data.Region.removeObserver = function(regionID, observer)
3300{
3301	var n = Spry.Data.Region.notifiers[regionID];
3302	if (n)
3303		n.removeObserver(observer);
3304};
3305
3306Spry.Data.Region.notifyObservers = function(methodName, region, data)
3307{
3308	var n = Spry.Data.Region.notifiers[region.name];
3309	if (n)
3310	{
3311		var dataObj = {};
3312		if (data && typeof data == "object")
3313			dataObj = data;
3314		else
3315			dataObj.data = data;
3316
3317		dataObj.region = region;
3318		dataObj.regionID = region.name;
3319		dataObj.regionNode = region.regionNode;
3320
3321		n.notifyObservers(methodName, dataObj);
3322	}
3323};
3324
3325Spry.Data.Region.RS_Error = 0x01;
3326Spry.Data.Region.RS_LoadingData = 0x02;
3327Spry.Data.Region.RS_PreUpdate = 0x04;
3328Spry.Data.Region.RS_PostUpdate = 0x08;
3329
3330Spry.Data.Region.prototype.getState = function()
3331{
3332	return this.currentState;
3333};
3334
3335Spry.Data.Region.prototype.mapState = function(stateName, newStateName)
3336{
3337	this.stateMap[stateName] = newStateName;
3338};
3339
3340Spry.Data.Region.prototype.getMappedState = function(stateName)
3341{
3342	var mappedState = this.stateMap[stateName];
3343	return mappedState ? mappedState : stateName;
3344};
3345
3346Spry.Data.Region.prototype.setState = function(stateName, suppressNotfications)
3347{
3348	var stateObj = { state: stateName, mappedState: this.getMappedState(stateName) };
3349	if (!suppressNotfications)
3350		Spry.Data.Region.notifyObservers("onPreStateChange", this, stateObj);
3351
3352	this.currentState = stateObj.mappedState ? stateObj.mappedState : stateName;
3353
3354	// If the region has content that is specific to this
3355	// state, regenerate the region so that its markup is updated.
3356
3357	if (this.states[stateName])
3358	{
3359		var notificationData = { state: this.currentState };
3360		if (!suppressNotfications)
3361			Spry.Data.Region.notifyObservers("onPreUpdate", this, notificationData);
3362
3363		// Make the region transform the xml data. The result is
3364		// a string that we need to parse and insert into the document.
3365
3366		var str = this.transform();
3367
3368		// Clear out any previous transformed content.
3369		// this.clearContent();
3370
3371		if (Spry.Data.Region.debug)
3372			Spry.Debug.trace("<hr />Generated region markup for '" + this.name + "':<br /><br />" + Spry.Utils.encodeEntities(str));
3373
3374		// Now insert the new transformed content into the document.
3375		Spry.Utils.setInnerHTML(this.regionNode, str, !Spry.Data.Region.evalScripts);
3376
3377		// Now run through the content looking for attributes
3378		// that tell us what behaviors to attach to each element.
3379		if (this.hasBehaviorAttributes)
3380			this.attachBehaviors();
3381
3382		if (!suppressNotfications)
3383			Spry.Data.Region.notifyObservers("onPostUpdate", this, notificationData);
3384	}
3385
3386	if (!suppressNotfications)
3387		Spry.Data.Region.notifyObservers("onPostStateChange", this, stateObj);
3388};
3389
3390Spry.Data.Region.prototype.getDataSets = function()
3391{
3392	return this.dataSets;
3393};
3394
3395Spry.Data.Region.prototype.addDataSet = function(aDataSet)
3396{
3397	if (!aDataSet)
3398		return;
3399
3400	if (!this.dataSets)
3401		this.dataSets = new Array;
3402
3403	// Check to see if the data set is already in our list.
3404
3405	for (var i = 0; i < this.dataSets.length; i++)
3406	{
3407		if (this.dataSets[i] == aDataSet)
3408			return; // It's already in our list!
3409	}
3410
3411	this.dataSets.push(aDataSet);
3412	aDataSet.addObserver(this);
3413};
3414
3415Spry.Data.Region.prototype.removeDataSet = function(aDataSet)
3416{
3417	if (!aDataSet || this.dataSets)
3418		return;
3419
3420	for (var i = 0; i < this.dataSets.length; i++)
3421	{
3422		if (this.dataSets[i] == aDataSet)
3423		{
3424			this.dataSets.splice(i, 1);
3425			aDataSet.removeObserver(this);
3426			return;
3427		}
3428	}
3429};
3430
3431Spry.Data.Region.prototype.onPreLoad = function(dataSet)
3432{
3433	if (this.currentState != "loading")
3434		this.setState("loading");
3435};
3436
3437Spry.Data.Region.prototype.onLoadError = function(dataSet)
3438{
3439	if (this.currentState != "error")
3440		this.setState("error");
3441	Spry.Data.Region.notifyObservers("onError", this);
3442};
3443
3444Spry.Data.Region.prototype.onSessionExpired = function(dataSet)
3445{
3446	if (this.currentState != "expired")
3447		this.setState("expired");
3448	Spry.Data.Region.notifyObservers("onExpired", this);
3449};
3450
3451Spry.Data.Region.prototype.onCurrentRowChanged = function(dataSet, data)
3452{
3453	if (this.isDetailRegion)
3454		this.updateContent();
3455};
3456
3457Spry.Data.Region.prototype.onPostSort = function(dataSet, data)
3458{
3459	this.updateContent();
3460};
3461
3462Spry.Data.Region.prototype.onDataChanged = function(dataSet, data)
3463{
3464	this.updateContent();
3465};
3466
3467Spry.Data.Region.enableBehaviorAttributes = true;
3468Spry.Data.Region.behaviorAttrs = {};
3469
3470Spry.Data.Region.behaviorAttrs["spry:select"] =
3471{
3472	attach: function(rgn, node, value)
3473	{
3474		var selectGroupName = null;
3475		try { selectGroupName = node.attributes.getNamedItem("spry:selectgroup").value; } catch (e) {}
3476		if (!selectGroupName)
3477			selectGroupName = "default";
3478
3479		Spry.Utils.addEventListener(node, "click", function(event) { Spry.Utils.SelectionManager.select(selectGroupName, node, value); }, false);
3480
3481		if (node.attributes.getNamedItem("spry:selected"))
3482			Spry.Utils.SelectionManager.select(selectGroupName, node, value);
3483	}
3484};
3485
3486Spry.Data.Region.behaviorAttrs["spry:hover"] =
3487{
3488	attach: function(rgn, node, value)
3489	{
3490		Spry.Utils.addEventListener(node, "mouseover", function(event){ Spry.Utils.addClassName(node, value); }, false);
3491		Spry.Utils.addEventListener(node, "mouseout", function(event){ Spry.Utils.removeClassName(node, value); }, false);
3492	}
3493};
3494
3495Spry.Data.Region.setUpRowNumberForEvenOddAttr = function(node, attr, value, rowNumAttrName)
3496{
3497	// The format for the spry:even and spry:odd attributes are as follows:
3498	//
3499	// <div spry:even="dataSetName cssEvenClassName" spry:odd="dataSetName cssOddClassName">
3500	//
3501	// The dataSetName is optional, and if not specified, the first data set
3502	// listed for the region is used.
3503	//
3504	// cssEvenClassName and cssOddClassName are required and *must* be specified. They can be
3505	// any user defined CSS class name.
3506
3507	if (!value)
3508	{
3509		Spry.Debug.showError("The " + attr + " attribute requires a CSS class name as its value!");
3510		node.attributes.removeNamedItem(attr);
3511		return;
3512	}
3513
3514	var dsName = "";
3515	var valArr = value.split(/\s/);
3516	if (valArr.length > 1)
3517	{
3518		// Extract out the data set name and reset the attribute so
3519		// that it only contains the CSS class name to use.
3520
3521		dsName = valArr[0];
3522		node.setAttribute(attr, valArr[1]);
3523	}
3524
3525	// Tag the node with an attribute that will allow us to fetch the row
3526	// number used when it is written out during the re-generation process.
3527
3528	node.setAttribute(rowNumAttrName, "{" + (dsName ? (dsName + "::") : "") + "ds_RowNumber}");
3529};
3530
3531Spry.Data.Region.behaviorAttrs["spry:even"] =
3532{
3533	setup: function(node, value)
3534	{
3535		Spry.Data.Region.setUpRowNumberForEvenOddAttr(node, "spry:even", value, "spryevenrownumber");
3536	},
3537
3538	attach: function(rgn, node, value)
3539	{
3540		if (value)
3541		{
3542			rowNumAttr = node.attributes.getNamedItem("spryevenrownumber");
3543			if (rowNumAttr && rowNumAttr.value)
3544			{
3545				var rowNum = parseInt(rowNumAttr.value);
3546				if (rowNum % 2)
3547					Spry.Utils.addClassName(node, value);
3548			}
3549		}
3550		node.removeAttribute("spry:even");
3551		node.removeAttribute("spryevenrownumber");
3552	}
3553};
3554
3555Spry.Data.Region.behaviorAttrs["spry:odd"] =
3556{
3557	setup: function(node, value)
3558	{
3559		Spry.Data.Region.setUpRowNumberForEvenOddAttr(node, "spry:odd", value, "spryoddrownumber");
3560	},
3561
3562	attach: function(rgn, node, value)
3563	{
3564		if (value)
3565		{
3566			rowNumAttr = node.attributes.getNamedItem("spryoddrownumber");
3567			if (rowNumAttr && rowNumAttr.value)
3568			{
3569				var rowNum = parseInt(rowNumAttr.value);
3570				if (rowNum % 2 == 0)
3571					Spry.Utils.addClassName(node, value);
3572			}
3573		}
3574		node.removeAttribute("spry:odd");
3575		node.removeAttribute("spryoddrownumber");
3576	}
3577};
3578
3579Spry.Data.Region.setRowAttrClickHandler = function(node, dsName, rowAttr, funcName)
3580{
3581		if (dsName)
3582		{
3583			var ds = Spry.Data.getDataSetByName(dsName);
3584			if (ds)
3585			{
3586				rowIDAttr = node.attributes.getNamedItem(rowAttr);
3587				if (rowIDAttr)
3588				{
3589					var rowAttrVal = rowIDAttr.value;
3590					if (rowAttrVal)
3591						Spry.Utils.addEventListener(node, "click", function(event){ ds[funcName](rowAttrVal); }, false);
3592				}
3593			}
3594		}
3595};
3596
3597Spry.Data.Region.behaviorAttrs["spry:setrow"] =
3598{
3599	setup: function(node, value)
3600	{
3601		if (!value)
3602		{
3603			Spry.Debug.reportError("The spry:setrow attribute requires a data set name as its value!");
3604			node.removeAttribute("spry:setrow");
3605			return;
3606		}
3607
3608		// Tag the node with an attribute that will allow us to fetch the id of the
3609		// row used when it is written out during the re-generation process.
3610
3611		node.setAttribute("spryrowid", "{" + value + "::ds_RowID}");
3612	},
3613
3614	attach: function(rgn, node, value)
3615	{
3616		Spry.Data.Region.setRowAttrClickHandler(node, value, "spryrowid", "setCurrentRow");
3617		node.removeAttribute("spry:setrow");
3618		node.removeAttribute("spryrowid");
3619	}
3620};
3621
3622Spry.Data.Region.behaviorAttrs["spry:setrownumber"] =
3623{
3624	setup: function(node, value)
3625	{
3626		if (!value)
3627		{
3628			Spry.Debug.reportError("The spry:setrownumber attribute requires a data set name as its value!");
3629			node.removeAttribute("spry:setrownumber");
3630			return;
3631		}
3632
3633		// Tag the node with an attribute that will allow us to fetch the row number
3634		// of the row used when it is written out during the re-generation process.
3635
3636		node.setAttribute("spryrownumber", "{" + value + "::ds_RowID}");
3637	},
3638
3639	attach: function(rgn, node, value)
3640	{
3641		Spry.Data.Region.setRowAttrClickHandler(node, value, "spryrownumber", "setCurrentRowNumber");
3642		node.removeAttribute("spry:setrownumber");
3643		node.removeAttribute("spryrownumber");
3644	}
3645};
3646
3647Spry.Data.Region.behaviorAttrs["spry:sort"] =
3648{
3649	attach: function(rgn, node, value)
3650	{
3651		if (!value)
3652			return;
3653
3654		// The format of a spry:sort attribute is as follows:
3655		//
3656		// <div spry:sort="dataSetName column1Name column2Name ... sortOrderName">
3657		//
3658		// The dataSetName and sortOrderName are optional, but when specified, they
3659		// must appear in the order mentioned above. If the dataSetName is not specified,
3660		// the first data set listed for the region is used. If the sortOrderName is not
3661		// specified, the sort defaults to "toggle".
3662		//
3663		// The user *must* specify at least one column name.
3664
3665		var ds = rgn.getDataSets()[0];
3666		var sortOrder = "toggle";
3667
3668		var colArray = value.split(/\s/);
3669		if (colArray.length > 1)
3670		{
3671			// Check the first string in the attribute to see if a data set was
3672			// specified. If so, make sure we use it for the sort.
3673
3674			var specifiedDS = Spry.Data.getDataSetByName(colArray[0]);
3675			if (specifiedDS)
3676			{
3677				ds = specifiedDS;
3678				colArray.shift();
3679			}
3680
3681			// Check to see if the last string in the attribute is the name of
3682			// a sort order. If so, use that sort order during the sort.
3683
3684			if (colArray.length > 1)
3685			{
3686				var str = colArray[colArray.length - 1];
3687				if (str == "ascending" || str == "descending" || str == "toggle")
3688				{
3689					sortOrder = str;
3690					colArray.pop();
3691				}
3692			}
3693		}
3694
3695		// If we have a data set and some column names, add a non-destructive
3696		// onclick handler that will perform a toggle sort on the data set.
3697
3698		if (ds && colArray.length > 0)
3699			Spry.Utils.addEventListener(node, "click", function(event){ ds.sort(colArray, sortOrder); }, false);
3700
3701		node.removeAttribute("spry:sort");
3702	}
3703};
3704
3705Spry.Data.Region.prototype.attachBehaviors = function()
3706{
3707	var rgn = this;
3708	Spry.Utils.getNodesByFunc(this.regionNode, function(node)
3709	{
3710		if (!node || node.nodeType != 1 /* Node.ELEMENT_NODE */)
3711			return false;
3712		try
3713		{
3714			var bAttrs = Spry.Data.Region.behaviorAttrs;
3715			for (var bAttrName in bAttrs)
3716			{
3717				var attr = node.attributes.getNamedItem(bAttrName);
3718				if (attr)
3719				{
3720					var behavior = bAttrs[bAttrName];
3721					if (behavior && behavior.attach)
3722						behavior.attach(rgn, node, attr.value);
3723				}
3724			}
3725		} catch(e) {}
3726
3727		return false;
3728	});
3729};
3730
3731Spry.Data.Region.prototype.updateContent = function()
3732{
3733	var allDataSetsReady = true;
3734
3735	var dsArray = this.getDataSets();
3736
3737	if (!dsArray || dsArray.length < 1)
3738	{
3739		Spry.Debug.reportError("updateContent(): Region '" + this.name + "' has no data set!\n");
3740		return;
3741	}
3742
3743	for (var i = 0; i < dsArray.length; i++)
3744	{
3745		var ds = dsArray[i];
3746
3747		if (ds)
3748		{
3749			if (ds.getLoadDataRequestIsPending())
3750				allDataSetsReady = false;
3751			else if (!ds.getDataWasLoaded())
3752			{
3753				// Kick off the loading of the data if it hasn't happened yet.
3754				ds.loadData();
3755				allDataSetsReady = false;
3756			}
3757		}
3758	}
3759
3760	if (!allDataSetsReady)
3761	{
3762		Spry.Data.Region.notifyObservers("onLoadingData", this);
3763
3764		// Just return, this method will get called again automatically
3765		// as each data set load completes!
3766		return;
3767	}
3768
3769	this.setState("ready");
3770};
3771
3772Spry.Data.Region.prototype.clearContent = function()
3773{
3774	this.regionNode.innerHTML = "";
3775};
3776
3777Spry.Data.Region.processContentPI = function(inStr)
3778{
3779	var outStr = "";
3780	var regexp = /<!--\s*<\/?spry:content\s*[^>]*>\s*-->/mg;
3781	var searchStartIndex = 0;
3782	var processingContentTag = 0;
3783
3784	while (inStr.length)
3785	{
3786		var results = regexp.exec(inStr);
3787		if (!results || !results[0])
3788		{
3789			outStr += inStr.substr(searchStartIndex, inStr.length - searchStartIndex);
3790			break;
3791		}
3792
3793		if (!processingContentTag && results.index != searchStartIndex)
3794		{
3795			// We found a match but it's not at the start of the inStr.
3796			// Create a string token for everything that precedes the match.
3797			outStr += inStr.substr(searchStartIndex, results.index - searchStartIndex);
3798		}
3799
3800		if (results[0].search(/<\//) != -1)
3801		{
3802			--processingContentTag;
3803			if (processingContentTag)
3804				Spry.Debug.reportError("Nested spry:content regions are not allowed!\n");
3805		}
3806		else
3807		{
3808			++processingContentTag;
3809			var dataRefStr = results[0].replace(/.*\bdataref="/, "");
3810			outStr += dataRefStr.replace(/".*$/, "");
3811		}
3812
3813		searchStartIndex = regexp.lastIndex;
3814	}
3815
3816	return outStr;
3817};
3818
3819Spry.Data.Region.prototype.tokenizeData = function(dataStr)
3820{
3821	// If there is no data, there's nothing to do.
3822	if (!dataStr)
3823		return null;
3824
3825	var rootToken = new Spry.Data.Region.Token(Spry.Data.Region.Token.LIST_TOKEN, null, null, null);
3826	var tokenStack = new Array;
3827	var parseStr = Spry.Data.Region.processContentPI(dataStr);
3828
3829	tokenStack.push(rootToken);
3830
3831	// Create a regular expression that will match one of the following:
3832	//
3833	//   <spry:repeat select="regionName" test="true">
3834	//   </spry:repeat>
3835	//   {valueReference}
3836	var regexp = /((<!--\s*){0,1}<\/{0,1}spry:[^>]+>(\s*-->){0,1})|((\{|%7[bB])[^\}\s%]+(\}|%7[dD]))/mg;
3837	var searchStartIndex = 0;
3838
3839	while(parseStr.length)
3840	{
3841		var results = regexp.exec(parseStr);
3842		var token = null;
3843
3844		if (!results || !results[0])
3845		{
3846			// If we get here, the rest of the parseStr should be
3847			// just a plain string. Create a token for it and then
3848			// break out of the list.
3849			var str = parseStr.substr(searchStartIndex, parseStr.length - searchStartIndex);
3850			token = new Spry.Data.Region.Token(Spry.Data.Region.Token.STRING_TOKEN, null, str, str);
3851			tokenStack[tokenStack.length - 1].addChild(token);
3852			break;
3853		}
3854
3855		if (results.index != searchStartIndex)
3856		{
3857			// We found a match but it's not at the start of the parseStr.
3858			// Create a string token for everything that precedes the match.
3859			var str = parseStr.substr(searchStartIndex, results.index - searchStartIndex);
3860			token = new Spry.Data.Region.Token(Spry.Data.Region.Token.STRING_TOKEN, null, str, str);
3861			tokenStack[tokenStack.length - 1].addChild(token);
3862		}
3863
3864		// We found a string that needs to be turned into a token. Create a token
3865		// for it and then update parseStr for the next iteration.
3866		if (results[0].search(/^({|%7[bB])/) != -1 /* results[0].charAt(0) == '{' */)
3867		{
3868			var valueName = results[0];
3869			var regionStr = results[0];
3870
3871			// Strip off brace and url encode brace chars inside the valueName.
3872
3873			valueName = valueName.replace(/^({|%7[bB])/, "");
3874			valueName = valueName.replace(/(}|%7[dD])$/, "");
3875
3876			// Check to see if our value begins with the name of a data set.
3877			// For example: {dataSet:tokenValue}. If it is, we need to save
3878			// the data set name so we know which data set to use to get the
3879			// value for the token during the region transform.
3880
3881			var dataSetName = null;
3882			var splitArray = valueName.split(/::/);
3883
3884			if (splitArray.length > 1)
3885			{
3886				dataSetName = splitArray[0];
3887				valueName = splitArray[1];
3888			}
3889
3890			// Convert any url encoded braces to regular brace chars.
3891
3892			regionStr = regionStr.replace(/^%7[bB]/, "{");
3893			regionStr = regionStr.replace(/%7[dD]$/, "}");
3894
3895			// Now create a token for the placeholder.
3896
3897			token = new Spry.Data.Region.Token(Spry.Data.Region.Token.VALUE_TOKEN, dataSetName, valueName, new String(regionStr));
3898			tokenStack[tokenStack.length - 1].addChild(token);
3899		}
3900		else if (results[0].charAt(0) == '<')
3901		{
3902			// Extract out the name of the processing instruction.
3903			var piName = results[0].replace(/^(<!--\s*){0,1}<\/?/, "");
3904			piName = piName.replace(/>(\s*-->){0,1}|\s.*$/, "");
3905
3906			if (results[0].search(/<\//) != -1 /* results[0].charAt(1) == '/' */)
3907			{
3908				// We found a processing instruction close tag. Pop the top of the
3909				// token stack!
3910				//
3911				// XXX: We need to make sure that the close tag name matches the one
3912				//      on the top of the token stack!
3913				if (tokenStack[tokenStack.length - 1].tokenType != Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN)
3914				{
3915					Spry.Debug.reportError("Invalid processing instruction close tag: " + piName + " -- " + results[0] + "\n");
3916					return null;
3917				}
3918
3919				tokenStack.pop();
3920			}
3921			else
3922			{
3923				// Create the processing instruction token, add it as a child of the token
3924				// at the top of the token stack, and then push it on the stack so that it
3925				// becomes the parent of any tokens between it and its close tag.
3926
3927				var piDesc = Spry.Data.Region.PI.instructions[piName];
3928
3929				if (piDesc)
3930				{
3931					var dataSet = null;
3932
3933					var selectedDataSetName = "";
3934					if (results[0].search(/^.*\bselect=\"/) != -1)
3935					{
3936						selectedDataSetName = results[0].replace(/^.*\bselect=\"/, "");
3937						selectedDataSetName = selectedDataSetName.replace(/".*$/, "");
3938
3939						if (selectedDataSetName)
3940						{
3941							dataSet = Spry.Data.getDataSetByName(selectedDataSetName);
3942							if (!dataSet)
3943							{
3944								Spry.Debug.reportError("Failed to retrieve data set (" + selectedDataSetName + ") for " + piName + "\n");
3945								selectedDataSetName = "";
3946							}
3947						}
3948					}
3949
3950					// Check if the repeat has a test attribute.
3951					var jsExpr = null;
3952					if (results[0].search(/^.*\btest=\"/) != -1)
3953					{
3954						jsExpr = results[0].replace(/^.*\btest=\"/, "");
3955						jsExpr = jsExpr.replace(/".*$/, "");
3956						jsExpr = Spry.Utils.decodeEntities(jsExpr);
3957					}
3958
3959					// Check if the instruction has a state name specified.
3960					var regionState = null;
3961					if (results[0].search(/^.*\bname=\"/) != -1)
3962					{
3963						regionState = results[0].replace(/^.*\bname=\"/, "");
3964						regionState = regionState.replace(/".*$/, "");
3965						regionState = Spry.Utils.decodeEntities(regionState);
3966					}
3967
3968					var piData = new Spry.Data.Region.Token.PIData(piName, selectedDataSetName, jsExpr, regionState);
3969
3970					token = new Spry.Data.Region.Token(Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN, dataSet, piData, new String(results[0]));
3971
3972					tokenStack[tokenStack.length - 1].addChild(token);
3973					tokenStack.push(token);
3974				}
3975				else
3976				{
3977					Spry.Debug.reportError("Unsupported region processing instruction: " + results[0] + "\n");
3978					return null;
3979				}
3980			}
3981		}
3982		else
3983		{
3984			Spry.Debug.reportError("Invalid region token: " + results[0] + "\n");
3985			return null;
3986		}
3987
3988		searchStartIndex = regexp.lastIndex;
3989	}
3990
3991	return rootToken;
3992};
3993
3994Spry.Data.Region.prototype.callScriptFunction = function(funcName, processContext)
3995{
3996	var result = undefined;
3997
3998	funcName = funcName.replace(/^\s*\{?\s*function::\s*|\s*\}?\s*$/g, "");
3999	var func = Spry.Utils.getObjectByName(funcName);
4000	if (func)
4001		result = func(this.name, function() { return processContext.getValueFromDataSet.apply(processContext, arguments); });
4002
4003	return result;
4004};
4005
4006Spry.Data.Region.prototype.evaluateExpression = function(exprStr, processContext)
4007{
4008	var result = undefined;
4009
4010	try
4011	{
4012		if (exprStr.search(/^\s*function::/) != -1)
4013			result = this.callScriptFunction(exprStr, processContext);
4014		else
4015			result = Spry.Utils.eval(Spry.Data.Region.processDataRefString(processContext, exprStr, null, true));
4016	}
4017	catch(e)
4018	{
4019		Spry.Debug.trace("Caught exception in Spry.Data.Region.prototype.evaluateExpression() while evaluating: " + Spry.Utils.encodeEntities(exprStr) + "\n    Exception:" + e + "\n");
4020	}
4021
4022	return result;
4023};
4024
4025Spry.Data.Region.prototype.processTokenChildren = function(outputArr, token, processContext)
4026{
4027	var children = token.children;
4028	var len = children.length;
4029
4030	for (var i = 0; i < len; i++)
4031		this.processTokens(outputArr, children[i], processContext);
4032};
4033
4034Spry.Data.Region.prototype.processTokens = function(outputArr, token, processContext)
4035{
4036	var i = 0;
4037
4038	switch(token.tokenType)
4039	{
4040		case Spry.Data.Region.Token.LIST_TOKEN:
4041			this.processTokenChildren(outputArr, token, processContext);
4042			break;
4043		case Spry.Data.Region.Token.STRING_TOKEN:
4044			outputArr.push(token.data);
4045			break;
4046		case Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN:
4047			if (token.data.name == "spry:repeat")
4048			{
4049				var dataSet = null;
4050
4051				if (token.dataSet)
4052					dataSet = token.dataSet;
4053				else
4054					dataSet = this.dataSets[0];
4055
4056				if (dataSet)
4057				{
4058					var dsContext = processContext.getDataSetContext(dataSet);
4059					if (!dsContext)
4060					{
4061						Spry.Debug.reportError("processTokens() failed to get a data set context!\n");
4062						break;
4063					}
4064
4065					dsContext.pushState();
4066
4067					var dataSetRows = dsContext.getData();
4068					var numRows = dataSetRows.length;
4069					for (i = 0; i < numRows; i++)
4070					{
4071						dsContext.setRowIndex(i);
4072						var testVal = true;
4073						if (token.data.jsExpr)
4074							testVal = this.evaluateExpression(token.data.jsExpr, processContext);
4075
4076						if (testVal)
4077							this.processTokenChildren(outputArr, token, processContext);
4078					}
4079					dsContext.popState();
4080				}
4081			}
4082			else if (token.data.name == "spry:if")
4083			{
4084				var testVal = true;
4085
4086				if (token.data.jsExpr)
4087					testVal = this.evaluateExpression(token.data.jsExpr, processContext);
4088
4089				if (testVal)
4090					this.processTokenChildren(outputArr, token, processContext);
4091			}
4092			else if (token.data.name == "spry:choose")
4093			{
4094				var defaultChild = null;
4095				var childToProcess = null;
4096				var testVal = false;
4097				var j = 0;
4098
4099				// All of the children of the spry:choose token should be of the type spry:when or spry:default.
4100				// Run through all of the spry:when children and see if any of their test expressions return true.
4101				// If one does, then process its children tokens. If none of the test expressions return true,
4102				// process the spry:default token's children, if it exists.
4103
4104				for (j = 0; j < token.children.length; j++)
4105				{
4106					var child = token.children[j];
4107					if (child.tokenType == Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN)
4108					{
4109						if (child.data.name == "spry:when")
4110						{
4111							if (child.data.jsExpr)
4112							{
4113								testVal = this.evaluateExpression(child.data.jsExpr, processContext);
4114
4115								if (testVal)
4116								{
4117									childToProcess = child;
4118									break;
4119								}
4120							}
4121						}
4122						else if (child.data.name == "spry:default")
4123							defaultChild = child;
4124					}
4125				}
4126
4127				// If we didn't find a match, use the token for the default case.
4128
4129				if (!childToProcess && defaultChild)
4130					childToProcess = defaultChild;
4131
4132				if (childToProcess)
4133					this.processTokenChildren(outputArr, childToProcess, processContext);
4134			}
4135			else if (token.data.name == "spry:state")
4136			{
4137				var testVal = true;
4138
4139				if (!token.data.regionState || token.data.regionState == this.currentState)
4140					this.processTokenChildren(outputArr, token, processContext);
4141			}
4142			else
4143			{
4144				Spry.Debug.reportError("processTokens(): Unknown processing instruction: " + token.data.name + "\n");
4145				return "";
4146			}
4147			break;
4148		case Spry.Data.Region.Token.VALUE_TOKEN:
4149
4150			var dataSet = token.dataSet;
4151			var val = undefined;
4152
4153			if (dataSet && dataSet == "function")
4154			{
4155				// This value token doesn't contain a data set data reference, it
4156				// contains a function call, so call it.
4157
4158				val = this.callScriptFunction(token.data, processContext);
4159			}
4160			else
4161			{
4162				if (!dataSet && this.dataSets && this.dataSets.length > 0 && this.dataSets[0])
4163				{
4164					// No dataSet was specified by the token, so use whatever the first
4165					// data set specified in the region.
4166	
4167					dataSet = this.dataSets[0];
4168				}
4169				if (!dataSet)
4170				{
4171					Spry.Debug.reportError("processTokens(): Value reference has no data set specified: " + token.regionStr + "\n");
4172					return "";
4173				}
4174	
4175				val = processContext.getValueFromDataSet(dataSet, token.data);
4176			}
4177
4178			if (typeof val != "undefined")
4179				outputArr.push(val + "");
4180
4181			break;
4182		default:
4183			Spry.Debug.reportError("processTokens(): Invalid token type: " + token.regionStr + "\n");
4184			break;
4185	}
4186};
4187
4188Spry.Data.Region.prototype.transform = function()
4189{
4190	if (this.data && !this.tokens)
4191		this.tokens = this.tokenizeData(this.data);
4192
4193	if (!this.tokens)
4194		return "";
4195
4196	processContext = new Spry.Data.Region.ProcessingContext(this);
4197	if (!processContext)
4198		return "";
4199
4200	// Now call processTokens to transform our tokens into real data strings.
4201	// We use an array to gather the strings during processing as a performance
4202	// enhancement for IE to avoid n-square problems of adding to an existing
4203	// string. For example:
4204	//
4205	//     for (var i = 0; i < token.children.length; i++)
4206	//       outputStr += this.processTokens(token.children[i], processContext);
4207	//
4208	// Using an array with a final join reduced one of our test cases  from over
4209	// a minute to about 15 seconds.
4210
4211	var outputArr = [ "" ];
4212	this.processTokens(outputArr, this.tokens, processContext);
4213	return outputArr.join("");
4214};
4215
4216Spry.Data.Region.PI = {};
4217Spry.Data.Region.PI.instructions = {};
4218
4219Spry.Data.Region.PI.buildOpenTagForValueAttr = function(ele, piName, attrName)
4220{
4221	if (!ele || !piName)
4222		return "";
4223
4224	var jsExpr = "";
4225
4226	try
4227	{
4228		var testAttr = ele.attributes.getNamedItem(piName);
4229		if (testAttr && testAttr.value)
4230			jsExpr = Spry.Utils.encodeEntities(testAttr.value);
4231	}
4232	catch (e) { jsExpr = ""; }
4233
4234	if (!jsExpr)
4235	{
4236		Spry.Debug.reportError(piName + " attribute requires a JavaScript expression that returns true or false!\n");
4237		return "";
4238	}
4239
4240	return "<" + Spry.Data.Region.PI.instructions[piName].tagName + " " + attrName +"=\"" + jsExpr + "\">";
4241};
4242
4243Spry.Data.Region.PI.buildOpenTagForTest = function(ele, piName)
4244{
4245	return Spry.Data.Region.PI.buildOpenTagForValueAttr(ele, piName, "test");
4246};
4247
4248Spry.Data.Region.PI.buildOpenTagForState = function(ele, piName)
4249{
4250	return Spry.Data.Region.PI.buildOpenTagForValueAttr(ele, piName, "name");
4251};
4252
4253Spry.Data.Region.PI.buildOpenTagForRepeat = function(ele, piName)
4254{
4255	if (!ele || !piName)
4256		return "";
4257
4258	var selectAttrStr = "";
4259
4260	try
4261	{
4262		var selectAttr = ele.attributes.getNamedItem(piName);
4263		if (selectAttr && selectAttr.value)
4264		{
4265			selectAttrStr = selectAttr.value;
4266			selectAttrStr = selectAttrStr.replace(/\s/g, "");
4267		}
4268	}
4269	catch (e) { selectAttrStr = ""; }
4270
4271	if (!selectAttrStr)
4272	{
4273		Spry.Debug.reportError(piName + " attribute requires a data set name!\n");
4274		return "";
4275	}
4276
4277	var testAttrStr = "";
4278
4279	try
4280	{
4281		var testAttr = ele.attributes.getNamedItem("spry:test");
4282		if (testAttr)
4283		{
4284			if (testAttr.value)
4285				testAttrStr = " test=\"" + Spry.Utils.encodeEntities(testAttr.value) + "\"";
4286			ele.attributes.removeNamedItem(testAttr.nodeName);
4287		}
4288	}
4289	catch (e) { testAttrStr = ""; }
4290
4291	return "<" + Spry.Data.Region.PI.instructions[piName].tagName + " select=\"" + selectAttrStr + "\"" + testAttrStr + ">";
4292};
4293
4294Spry.Data.Region.PI.buildOpenTagForContent = function(ele, piName)
4295{
4296	if (!ele || !piName)
4297		return "";
4298
4299	var dataRefStr = "";
4300
4301	try
4302	{
4303		var contentAttr = ele.attributes.getNamedItem(piName);
4304		if (contentAttr && contentAttr.value)
4305			dataRefStr = Spry.Utils.encodeEntities(contentAttr.value);
4306	}
4307	catch (e) { dataRefStr = ""; }
4308
4309	if (!dataRefStr)
4310	{
4311		Spry.Debug.reportError(piName + " attribute requires a data reference!\n");
4312		return "";
4313	}
4314
4315	return "<" + Spry.Data.Region.PI.instructions[piName].tagName + " dataref=\"" + dataRefStr + "\">";
4316};
4317
4318Spry.Data.Region.PI.buildOpenTag = function(ele, piName)
4319{
4320	return "<" + Spry.Data.Region.PI.instructions[piName].tagName + ">";
4321};
4322
4323Spry.Data.Region.PI.buildCloseTag = function(ele, piName)
4324{
4325	return "</" + Spry.Data.Region.PI.instructions[piName].tagName + ">";
4326};
4327
4328Spry.Data.Region.PI.instructions["spry:state"] = { tagName: "spry:state", childrenOnly: false, getOpenTag: 
4328Spry.Data.Region.PI.buildOpenTagForState, getCloseTag: Spry.Data.Region.PI.buildCloseTag };
4329Spry.Data.Region.PI.instructions["spry:if"] = { tagName: "spry:if", childrenOnly: false, getOpenTag: Spry.Data.Region.PI.buildOpenTagForTest, getCloseTag: Spry.Data.Region.PI.buildCloseTag };
4330Spry.Data.Region.PI.instructions["spry:repeat"] = { tagName: "spry:repeat", childrenOnly: false, getOpenTag: Spry.Data.Region.PI.buildOpenTagForRepeat, getCloseTag: Spry.Data.Region.PI.buildCloseTag };
4331Spry.Data.Region.PI.instructions["spry:repeatchildren"] = { tagName: "spry:repeat", childrenOnly: true, getOpenTag: Spry.Data.Region.PI.buildOpenTagForRepeat, getCloseTag: Spry.Data.Region.PI.buildCloseTag };
4332Spry.Data.Region.PI.instructions["spry:choose"] = { tagName: "spry:choose", childrenOnly: true, getOpenTag: Spry.Data.Region.PI.buildOpenTag, getCloseTag: Spry.Data.Region.PI.buildCloseTag };
4333Spry.Data.Region.PI.instructions["spry:when"] = { tagName: "spry:when", childrenOnly: false, getOpenTag: Spry.Data.Region.PI.buildOpenTagForTest, getCloseTag: Spry.Data.Region.PI.buildCloseTag };
4334Spry.Data.Region.PI.instructions["spry:default"] = { tagName: "spry:default", childrenOnly: false, getOpenTag: Spry.Data.Region.PI.buildOpenTag, getCloseTag: Spry.Data.Region.PI.buildCloseTag };
4335Spry.Data.Region.PI.instructions["spry:content"] = { tagName: "spry:content", childrenOnly: true, getOpenTag: Spry.Data.Region.PI.buildOpenTagForContent, getCloseTag: Spry.Data.Region.PI.buildCloseTag };
4336
4337Spry.Data.Region.PI.orderedInstructions = [ "spry:state", "spry:if", "spry:repeat", "spry:repeatchildren", "spry:choose", "spry:when", "spry:default", "spry:content" ];
4338
4339Spry.Data.Region.getTokensFromStr = function(str)
4340{
4341	// XXX: This will need to be modified if we support
4342	// tokens that use javascript between the braces!
4343	if (!str)
4344		return null;
4345	return str.match(/{[^}]+}/g);
4346};
4347
4348Spry.Data.Region.processDataRefString = function(processingContext, regionStr, dataSetsToUse, isJSExpr)
4349{
4350	if (!regionStr)
4351		return "";
4352
4353	if (!processingContext && !dataSetsToUse)
4354		return regionStr;
4355
4356	var resultStr = "";
4357	var re = new RegExp("\\{([^\\}:]+::)?[^\\}]+\\}", "g");
4358	var startSearchIndex = 0;
4359
4360	while (startSearchIndex < regionStr.length)
4361	{
4362		var reArray = re.exec(regionStr);
4363		if (!reArray || !reArray[0])
4364		{
4365			resultStr += regionStr.substr(startSearchIndex, regionStr.length - startSearchIndex);
4366			return resultStr;
4367		}
4368
4369		if (reArray.index != startSearchIndex)
4370			resultStr += regionStr.substr(startSearchIndex, reArray.index - startSearchIndex);
4371
4372		var dsName = "";
4373		if (reArray[0].search(/^\{[^}:]+::/) != -1)
4374			dsName = reArray[0].replace(/^\{|::.*/g, "");
4375
4376		var fieldName = reArray[0].replace(/^\{|.*::|\}/g, "");
4377		var row = null;
4378
4379		var val = "";
4380
4381		if (processingContext)
4382			val = processingContext.getValueFromDataSet(dsName, fieldName);
4383		else
4384		{
4385			var ds = dsName ? dataSetsToUse[dsName] : dataSetsToUse[0];
4386			if (ds)
4387				val = ds.getValue(fieldName);
4388		}
4389
4390		if (typeof val != "undefined")
4391		{
4392			val += ""; // Make sure val is converted to a string.
4393			resultStr += isJSExpr ? Spry.Utils.escapeQuotesAndLineBreaks(val) : val;
4394		}
4395
4396		if (startSearchIndex == re.lastIndex)
4397		{
4398			// On IE if there was a match near the end of the string, it sometimes
4399			// leaves re.lastIndex pointing to the value it had before the last time
4400			// we called re.exec. We check for this case to prevent an infinite loop!
4401			// We need to write out any text in regionStr that comes after the last
4402			// match.
4403
4404			var leftOverIndex = reArray.index + reArray[0].length;
4405			if (leftOverIndex < regionStr.length)
4406				resultStr += regionStr.substr(leftOverIndex);
4407
4408			break;
4409		}
4410
4411		startSearchIndex = re.lastIndex;
4412	}
4413
4414	return resultStr;
4415};
4416
4417Spry.Data.Region.strToDataSetsArray = function(str, returnRegionNames)
4418{
4419	var dataSetsArr = new Array;
4420	var foundHash = {};
4421
4422	if (!str)
4423		return dataSetsArr;
4424
4425	str = str.replace(/\s+/g, " ");
4426	str = str.replace(/^\s|\s$/g, "");
4427	var arr = str.split(/ /);
4428
4429
4430	for (var i = 0; i < arr.length; i++)
4431	{
4432		if (arr[i] && !Spry.Data.Region.PI.instructions[arr[i]])
4433		{
4434			try {
4435				var dataSet = Spry.Data.getDataSetByName(arr[i]);
4436
4437				if (!foundHash[arr[i]])
4438				{
4439					if (returnRegionNames)
4440						dataSetsArr.push(arr[i]);
4441					else
4442						dataSetsArr.push(dataSet);
4443					foundHash[arr[i]] = true;
4444				}
4445			}
4446			catch (e) { /* Spry.Debug.trace("Caught exception: " + e + "\n"); */ }
4447		}
4448	}
4449
4450	return dataSetsArr;
4451};
4452
4453Spry.Data.Region.DSContext = function(dataSet, processingContext)
4454{
4455	var m_dataSet = dataSet;
4456	var m_processingContext = processingContext;
4457	var m_curRowIndexArray = [ { rowIndex: -1 } ]; // -1 means return whatever the current row is inside the data set.
4458	var m_parent = null;
4459	var m_children = [];
4460
4461	// Private Methods:
4462
4463	var getInternalRowIndex = function() { return m_curRowIndexArray[m_curRowIndexArray.length - 1].rowIndex; };
4464
4465	// Public Methods:
4466	this.resetAll = function() { m_curRowIndexArray = [ { rowIndex: m_dataSet.getCurrentRow() } ] };
4467	this.getDataSet = function() { return m_dataSet; };
4468	this.getNumRows = function(unfiltered)
4469	{
4470		var data = this.getCurrentState().data;
4471		return data ? data.length : m_dataSet.getRowCount(unfiltered);
4472	};
4473	this.getData = function()
4474	{
4475		var data = this.getCurrentState().data;
4476		return data ? data : m_dataSet.getData();
4477	};
4478	this.setData = function(data)
4479	{
4480		this.getCurrentState().data = data;
4481	};
4482	this.getValue = function(valueName, rowContext)
4483	{
4484		var result = "";
4485		var curState = this.getCurrentState();
4486		var ds = curState.nestedDS ? curState.nestedDS : this.getDataSet();
4487		if (ds)
4488			result = ds.getValue(valueName, rowContext);
4489		return result;
4490	};
4491	this.getCurrentRow = function()
4492	{
4493		if (m_curRowIndexArray.length < 2 || getInternalRowIndex() < 0)
4494			return m_dataSet.getCurrentRow();
4495
4496		var data = this.getData();
4497		var curRowIndex = getInternalRowIndex();
4498
4499		if (curRowIndex < 0 || curRowIndex > data.length)
4500		{
4501			Spry.Debug.reportError("Invalid index used in Spry.Data.Region.DSContext.getCurrentRow()!\n");
4502			return null;
4503		}
4504
4505		return data[curRowIndex];
4506	};
4507	this.getRowIndex = function()
4508	{
4509		var curRowIndex = getInternalRowIndex();
4510		if (curRowIndex >= 0)
4511			return curRowIndex;
4512
4513		return m_dataSet.getRowNumber(m_dataSet.getCurrentRow());
4514	};
4515	this.setRowIndex = function(rowIndex)
4516	{
4517		this.getCurrentState().rowIndex = rowIndex;
4518
4519		var data = this.getData();
4520		var numChildren = m_children.length;
4521		for (var i = 0; i < numChildren; i++)
4522			m_children[i].syncDataWithParentRow(this, rowIndex, data);
4523	};
4524	this.syncDataWithParentRow = function(parentDSContext, rowIndex, parentData)
4525	{
4526		var row = parentData[rowIndex];
4527		if (row)
4528		{
4529			nestedDS = m_dataSet.getNestedDataSetForParentRow(row);
4530			if (nestedDS)
4531			{
4532				var currentState = this.getCurrentState();
4533				currentState.nestedDS = nestedDS;
4534				currentState.data = nestedDS.getData();
4535				currentState.rowIndex = nestedDS.getCurrentRowNumber();
4536
4537				// getCurrentRowNumber() will return a -1 if the nestedDS has
4538				// no data in it. If the rowIndex is -1, we need to reset it back to
4539				// zero so the dsContext doesn't attempt to use the *real* current
4540				// row of the data set.
4541
4542				currentState.rowIndex = currentState.rowIndex < 0 ? 0 : currentState.rowIndex;
4543
4544				var numChildren = m_children.length;
4545				for (var i = 0; i < numChildren; i++)
4546					m_children[i].syncDataWithParentRow(this, currentState.rowIndex, currentState.data);
4547			}
4548		}
4549	};
4550	this.pushState = function()
4551	{
4552		var curState = this.getCurrentState();
4553		var newState = new Object;
4554		newState.rowIndex = curState.rowIndex;
4555		newState.data = curState.data;
4556		newState.nestedDS = curState.nestedDS;
4557
4558		m_curRowIndexArray.push(newState);
4559
4560		var numChildren = m_children.length;
4561		for (var i = 0; i < numChildren; i++)
4562			m_children[i].pushState();
4563	};
4564	this.popState = function()
4565	{
4566		if (m_curRowIndexArray.length < 2)
4567		{
4568			// Our array should always have at least one element in it!
4569			Spry.Debug.reportError("Stack underflow in Spry.Data.Region.DSContext.popState()!\n");
4570			return;
4571		}
4572
4573		var numChildren = m_children.length;
4574		for (var i = 0; i < numChildren; i++)
4575			m_children[i].popState();
4576
4577		m_curRowIndexArray.pop();
4578	};
4579	this.getCurrentState = function()
4580	{
4581		return m_curRowIndexArray[m_curRowIndexArray.length - 1];
4582	};
4583	this.addChild = function(childDSContext)
4584	{
4585		var numChildren = m_children.length;
4586		for (var i = 0; i < numChildren; i++)
4587		{
4588			if (m_children[i] == childDSContext)
4589				return;
4590		}
4591		m_children.push(childDSContext);
4592	};
4593};
4594
4595Spry.Data.Region.ProcessingContext = function(region)
4596{
4597	this.region = region;
4598	this.dataSetContexts = [];
4599
4600	if (region && region.dataSets)
4601	{
4602		// Run through each data set in the list and check to see if we need
4603		// to add its parent to the list of data sets we track.
4604		var dsArray = region.dataSets.slice(0);
4605		var dsArrayLen = dsArray.length;
4606		for (var i = 0; i < dsArrayLen; i++)
4607		{
4608			var ds = region.dataSets[i];
4609			while (ds && ds.getParentDataSet)
4610			{
4611				var doesExist = false;
4612				ds = ds.getParentDataSet();
4613				if (ds && this.indexOf(dsArray, ds) == -1)
4614					dsArray.push(ds);
4615			}
4616		}
4617
4618		// Create a data set context for every data set in our list.
4619
4620		for (i = 0; i < dsArray.length; i++)
4621			this.dataSetContexts.push(new Spry.Data.Region.DSContext(dsArray[i], this));
4622
4623		// Now run through the list of data set contexts and wire up the parent/child
4624		// relationships so that notifications get dispatched as expected.
4625
4626		var dsContexts = this.dataSetContexts;
4627		var numDSContexts = dsContexts.length;
4628
4629		for (i = 0; i < numDSContexts; i++)
4630		{
4631			var dsc = dsContexts[i];
4632			var ds = dsc.getDataSet();
4633			if (ds.getParentDataSet)
4634			{
4635				var parentDS = ds.getParentDataSet();
4636				if (parentDS)
4637				{
4638					var pdsc = this.getDataSetContext(parentDS);
4639					if (pdsc) pdsc.addChild(dsc);
4640				}
4641			}
4642		}
4643	}
4644};
4645
4646Spry.Data.Region.ProcessingContext.prototype.indexOf = function(arr, item)
4647{
4648	// Given an array, return the index of item in that array
4649	// or -1 if it doesn't exist.
4650
4651	if (arr)
4652	{
4653		var arrLen = arr.length;
4654		for (var i = 0; i < arrLen; i++)
4655			if (arr[i] == item)
4656				return i;
4657	}
4658	return -1;
4659};
4660
4661Spry.Data.Region.ProcessingContext.prototype.getDataSetContext = function(dataSet)
4662{
4663	if (!dataSet)
4664	{
4665		// We were called without a specified data set or
4666		// data set name. Assume the caller wants the first
4667		// data set in the processing context.
4668
4669		if (this.dataSetContexts.length > 0)
4670			return this.dataSetContexts[0];
4671		return null;
4672	}
4673
4674	if (typeof dataSet == 'string')
4675	{
4676		dataSet = Spry.Data.getDataSetByName(dataSet);
4677		if (!dataSet)
4678			return null;
4679	}
4680
4681	for (var i = 0; i < this.dataSetContexts.length; i++)
4682	{
4683		var dsc = this.dataSetContexts[i];
4684		if (dsc.getDataSet() == dataSet)
4685			return dsc;
4686	}
4687
4688	return null;
4689};
4690
4691Spry.Data.Region.ProcessingContext.prototype.getValueFromDataSet = function()
4692{
4693	var dsName = "";
4694	var columnName = "";
4695
4696	if (arguments.length > 1)
4697	{
4698		// The caller is passing in the data set name and the
4699		// name of the data reference separately.
4700
4701		dsName = arguments[0];
4702		columnName = arguments[1];
4703	}
4704	else
4705	{
4706		// The caller is passing a single string which can be in one
4707		// of the following forms:
4708		//
4709		//    "columnName"
4710		//    "dsName::columnName"
4711		//    "{columnName}"
4712		//    "{dsName::columnName}"
4713
4714		var dataRef = arguments[0].replace(/\s*{\s*|\s*}\s*/g, "");
4715		if (dataRef.search("::") != -1)
4716		{
4717			dsName = dataRef.replace(/::.*/, "");
4718			columnName = dataRef.replace(/.*::/, "");
4719		}
4720		else
4721			columnName = dataRef;
4722	}
4723
4724	var result = "";
4725	var dsContext = this.getDataSetContext(dsName);
4726	if (dsContext)
4727		result = dsContext.getValue(columnName, dsContext.getCurrentRow());
4728	else
4729		Spry.Debug.reportError("getValueFromDataSet: Failed to get " + dsName + " context for the " + this.region.regionNode.id + " region.\n");
4730
4731	return result;
4732};
4733
4734// Define a short-hand name for developers.
4735Spry.Data.Region.ProcessingContext.prototype.$v = Spry.Data.Region.ProcessingContext.prototype.getValueFromDataSet;
4736
4737Spry.Data.Region.ProcessingContext.prototype.getCurrentRowForDataSet = function(dataSet)
4738{
4739	var dsc = this.getDataSetContext(dataSet);
4740	if (dsc)
4741		return dsc.getCurrentRow();
4742	return null;
4743};
4744
4745Spry.Data.Region.Token = function(tokenType, dataSet, data, regionStr)
4746{
4747	var self = this;
4748	this.tokenType = tokenType;
4749	this.dataSet = dataSet;
4750	this.data = data;
4751	this.regionStr = regionStr;
4752	this.parent = null;
4753	this.children = null;
4754};
4755
4756Spry.Data.Region.Token.prototype.addChild = function(child)
4757{
4758	if (!child)
4759		return;
4760
4761	if (!this.children)
4762		this.children = new Array;
4763
4764	this.children.push(child);
4765	child.parent = this;
4766};
4767
4768Spry.Data.Region.Token.LIST_TOKEN                   = 0;
4769Spry.Data.Region.Token.STRING_TOKEN                 = 1;
4770Spry.Data.Region.Token.PROCESSING_INSTRUCTION_TOKEN = 2;
4771Spry.Data.Region.Token.VALUE_TOKEN                  = 3;
4772
4773Spry.Data.Region.Token.PIData = function(piName, data, jsExpr, regionState)
4774{
4775	var self = this;
4776	this.name = piName;
4777	this.data = data;
4778	this.jsExpr = jsExpr;
4779	this.regionState = regionState;
4780};
4781
4782Spry.Utils.addLoadListener(function() { setTimeout(function() { if (Spry.Data.initRegionsOnLoad) Spry.Data.initRegions(); }, 0); });

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.