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