root/trunk/website/soundings/includes/SpryData.js @ 45

Revision 5, 125.6 kB (checked in by DanWilson, 17 years ago)

Initial Commit Of ModelGlue? Website (upgrade to blogcfc 511)

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