ADS.js
上传用户:pzy1211
上传日期:2022-04-09
资源大小:23k
文件大小:53k
源码类别:

其他智力游戏

开发平台:

JavaScript

  1. /**
  2.  * ADS Library from Advanced DOM Scripting
  3.  * http://advanceddomscripting.com
  4.  *
  5.  * This library is not compressed and is not recommended for production use in
  6.  * its current state. The code is excessively verbose and heavily commented
  7.  * as it was written as a teaching tool. It is recommended you edit the code for
  8.  * better performance and smaller file size.
  9.  * @projectDescription ADS library from the book "AdvancED DOM Scripting" http://advanceddomscripting.com/
  10.  * @author Jeffrey Sambells jeff@advanceddomscripting.com
  11.  * @copyright Jeffrey Sambells 2007 unless otherwise noted
  12.  * @version $Id: ADS-final-verbose.js 183 2007-07-17 20:23:30Z jsambells $
  13.  * @see http://advanceddomscripting.com/source/documentation
  14.  * @namespace ADS
  15.  */
  16. /**
  17. * Missing getElementById.
  18. * Example of creating a DOM replacement this isn't necessary because the
  19. * library assume browsers that support it.
  20. */
  21. if(document.all && !document.getElementById) {
  22.     document.getElementById = function(id) {
  23.          return document.all[id];
  24.     }
  25. }
  26. /**
  27. * Repeat a string 
  28. * from Chapter 3
  29. * Using the prototype to modify core objects
  30. */
  31. if (!String.repeat) {
  32.     String.prototype.repeat = function(l){
  33.         return new Array(l+1).join(this);
  34.     }
  35. }
  36. /** 
  37. * Remove trailing and leading whitespace 
  38. * from Chapter 3
  39. */
  40. if (!String.trim) {
  41.     String.prototype.trim = function() {
  42.         return this.replace(/^s+|s+$/g,'');
  43.     }
  44. }
  45. /**
  46.  * ADS Namespace
  47.  * This anonymous function acts as a namespace wrapper for the rest
  48.  * of the methods. Methods are then assigned to the window object
  49.  * using: window['ADS']['methodName'] = methodReference;
  50.  * @alias ADS
  51.  */
  52. (function(){
  53. /**
  54.  * The primary namespace object
  55.  * @type {Object}
  56.  * @alias ADS
  57.  */
  58. if(!window['ADS']) {
  59.     window['ADS'] = {};
  60. }
  61. /********************************
  62. * Chapter 1
  63. *********************************/
  64. /**
  65.  * Checks to see if the current browser is compatible with the entire library
  66.  */
  67. function isCompatible(other) {
  68.     // Use capability detection to check requirements
  69.     if( other===false 
  70.         || !Array.prototype.push
  71.         || !Object.hasOwnProperty
  72.         || !document.createElement
  73.         || !document.getElementsByTagName
  74.         ) {
  75.         alert('TR- if you see this message isCompatible is failing incorrectly.');
  76.         return false;
  77.     }
  78.     return true;
  79. }
  80. window['ADS']['isCompatible'] = isCompatible;
  81. /**
  82.  * document.getElementById(); replacement.
  83.  */
  84. function $() {
  85.     var elements = new Array();
  86.     
  87.     // Find all the elements supplied as arguments
  88.     for (var i = 0; i < arguments.length; i++) {
  89.         var element = arguments[i];
  90.         
  91.         // If the argument is a string assume it's an id
  92.         if (typeof element == 'string') {
  93.             element = document.getElementById(element);
  94.         }
  95.         
  96.         // If only one argument was supplied, return the element immediately
  97.         if (arguments.length == 1) {
  98.             return element;
  99.         }
  100.         
  101.         // Otherwise add it to the array
  102.         elements.push(element);
  103.     }
  104.     
  105.     // Return the array of multiple requested elements
  106.     return elements;
  107. };
  108. window['ADS']['$'] = $;
  109. /**
  110.  * Register an event listener on an element
  111.  */
  112. function addEvent( node, type, listener ) {
  113.     // Check compatibility using the earlier method
  114.     // to ensure graceful degradation
  115.     if(!isCompatible()) { return false }
  116.     if(!(node = $(node))) return false;
  117.     
  118.     if (node.addEventListener) {
  119.         // W3C method
  120.         node.addEventListener( type, listener, false );
  121.         return true;
  122.     } else if(node.attachEvent) {
  123.         // MSIE method
  124.         node['e'+type+listener] = listener;
  125.         node[type+listener] = function(){node['e'+type+listener]( window.event );}
  126.         node.attachEvent( 'on'+type, node[type+listener] );
  127.         return true;
  128.     }
  129.     
  130.     // Didn't have either so return false
  131.     return false;
  132. };
  133. window['ADS']['addEvent'] = addEvent;
  134. /**
  135.  * Unregister an event listener on an element
  136.  */
  137. function removeEvent(node, type, listener ) {
  138.     if(!(node = $(node))) return false;
  139.     if (node.removeEventListener) {
  140.         node.removeEventListener( type, listener, false );
  141.         return true;
  142.     } else if (node.detachEvent) {
  143.         // MSIE method
  144.         node.detachEvent( 'on'+type, node[type+listener] );
  145.         node[type+listener] = null;
  146.         return true;
  147.     }
  148.     // Didn't have either so return false
  149.     return false;
  150. };
  151. window['ADS']['removeEvent'] = removeEvent;
  152. /**
  153.  * Retrieve an array of element base on a class name
  154.  */
  155. function getElementsByClassName(className, tag, parent){
  156.     parent = parent || document;
  157.     if(!(parent = $(parent))) return false;
  158.     
  159.     // Locate all the matching tags
  160.     var allTags = (tag == "*" && parent.all) ? parent.all : parent.getElementsByTagName(tag);
  161.     var matchingElements = new Array();
  162.     
  163.     // Create a regular expression to determine if the className is correct
  164.     className = className.replace(/-/g, "\-");
  165.     var regex = new RegExp("(^|\s)" + className + "(\s|$)");
  166.     
  167.     var element;
  168.     // Check each element
  169.     for(var i=0; i<allTags.length; i++){
  170.         element = allTags[i];
  171.         if(regex.test(element.className)){
  172.             matchingElements.push(element);
  173.         }
  174.     }
  175.     
  176.     // Return any matching elements
  177.     return matchingElements;
  178. };
  179. window['ADS']['getElementsByClassName'] = getElementsByClassName;
  180. /**
  181.  * Toggle the style display value between none and the default
  182.  */
  183. function toggleDisplay(node, value) {
  184.     if(!(node = $(node))) return false;
  185.     if ( node.style.display != 'none' ) {
  186.         node.style.display = 'none';
  187.     } else {
  188.         node.style.display = value || '';
  189.     }
  190.     return true;
  191. }
  192. window['ADS']['toggleDisplay'] = toggleDisplay;
  193. /**
  194.  * Insert a DOM node after another DOM node
  195.  */
  196. function insertAfter(node, referenceNode) {
  197.     if(!(node = $(node))) return false;
  198.     if(!(referenceNode = $(referenceNode))) return false;
  199.     
  200.     return referenceNode.parentNode.insertBefore(node, referenceNode.nextSibling);
  201. };
  202. window['ADS']['insertAfter'] = insertAfter;
  203. /**
  204.  * Remove all teh child nodes from an element
  205.  */
  206. function removeChildren(parent) {
  207.     if(!(parent = $(parent))) return false;
  208.     
  209.     // While there is a child remove it
  210.     while (parent.firstChild) {
  211.          parent.firstChild.parentNode.removeChild(parent.firstChild);
  212.     }
  213.     // Return the parent again so you can stack the methods
  214.     return parent;
  215. };
  216. window['ADS']['removeChildren'] = removeChildren;
  217. /**
  218.  * Insert a new node as the first child.
  219.  */
  220. function prependChild(parent,newChild) {
  221.     if(!(parent = $(parent))) return false;
  222.     if(!(newChild = $(newChild))) return false;
  223.     if(parent.firstChild) {
  224.         // There is already a child so insert before the first one
  225.         parent.insertBefore(newChild,parent.firstChild);    
  226.     } else {
  227.         // No children so just append
  228.         parent.appendChild(newChild);
  229.     }
  230.     // Return the parent again so you can stack the methods
  231.     return parent;
  232. window['ADS']['prependChild'] = prependChild;
  233. /********************************
  234. * Chapter 2
  235. *********************************/
  236. /**
  237.  * Put the given object in teh context of the given method.
  238.  */
  239. function bindFunction(obj, func) {
  240.     return function() {
  241.         func.apply(obj,arguments);    
  242.     };
  243. };
  244. window['ADS']['bindFunction'] = bindFunction;
  245. /**
  246.  * Retrieve the size of the browser window.
  247.  */
  248. function getBrowserWindowSize() {
  249.     var de = document.documentElement;
  250.     
  251.     // window.innerWidth for most browsers
  252.     // document.documentElement.clientWidth for MSIE in strict mode
  253.     // document.body.clientWidth for MSIE in quirks mode
  254.     
  255.     return {
  256.         'width':(
  257.             window.innerWidth 
  258.             || (de && de.clientWidth ) 
  259.             || document.body.clientWidth),
  260.         'height':(
  261.             window.innerHeight 
  262.             || (de && de.clientHeight ) 
  263.             || document.body.clientHeight)
  264.     }
  265. };
  266. window['ADS']['getBrowserWindowSize'] = getBrowserWindowSize;
  267. /********************************
  268. * Chapter 3
  269. *********************************/
  270. /**
  271.  * Constants for note type comparison
  272.  */
  273. window['ADS']['node'] = {
  274.     ELEMENT_NODE                : 1,
  275.     ATTRIBUTE_NODE              : 2,
  276.     TEXT_NODE                   : 3,
  277.     CDATA_SECTION_NODE          : 4,
  278.     ENTITY_REFERENCE_NODE       : 5,
  279.     ENTITY_NODE                 : 6,
  280.     PROCESSING_INSTRUCTION_NODE : 7,
  281.     COMMENT_NODE                : 8,
  282.     DOCUMENT_NODE               : 9,
  283.     DOCUMENT_TYPE_NODE          : 10,
  284.     DOCUMENT_FRAGMENT_NODE      : 11,
  285.     NOTATION_NODE               : 12
  286. };
  287. /**
  288.  * Walk the nodes in the DOM tree without maintaining parent/child relationships.
  289.  */
  290. function walkElementsLinear(func,node) {
  291.     var root = node || window.document;
  292.     var nodes = root.getElementsByTagName("*");
  293.     for(var i = 0 ; i < nodes.length ; i++) {
  294.         func.call(nodes[i]);
  295.     }
  296. };
  297. window['ADS']['walkElementsLinear'] = walkElementsLinear;
  298. /**
  299.  * Walk the nodes in the DOM tree maintaining parent/child relationships.
  300.  */
  301. function walkTheDOMRecursive(func,node,depth,returnedFromParent) {
  302.     var root = node || window.document;
  303.     returnedFromParent = func.call(root,depth++,returnedFromParent);
  304.     node = root.firstChild;
  305.     while(node) {
  306.         walkTheDOMRecursive(func,node,depth,returnedFromParent);
  307.         node = node.nextSibling;
  308.     }
  309. };
  310. window['ADS']['walkTheDOMRecursive'] = walkTheDOMRecursive;
  311. /**
  312.  * Walk the nodes in the DOM tree maintaining parent/child relationships and include the node attributes as well.
  313.  */
  314. function walkTheDOMWithAttributes(node,func,depth,returnedFromParent) {
  315.     var root = node || window.document;
  316.     returnedFromParent = func(root,depth++,returnedFromParent);
  317.     if (root.attributes) {
  318.         for(var i=0; i < root.attributes.length; i++) {
  319.             walkTheDOMWithAttributes(root.attributes[i],func,depth-1,returnedFromParent);
  320.         }
  321.     }
  322.     if(root.nodeType != ADS.node.ATTRIBUTE_NODE) {
  323.         node = root.firstChild;
  324.         while(node) {
  325.             walkTheDOMWithAttributes(node,func,depth,returnedFromParent);
  326.             node = node.nextSibling;
  327.         }
  328.     }
  329. };
  330. window['ADS']['walkTheDOMWithAttributes'] = walkTheDOMWithAttributes;
  331. /**
  332.  * Walk the DOM recursively using a callback function
  333.  */
  334. function walkTheDOM(node, func) {
  335.     func(node);
  336.     node = node.firstChild;
  337.     while (node) {
  338.          walkTheDOM(node, func);
  339.          node = node.nextSibling;
  340.     }
  341. }
  342. window['ADS']['walkTheDOM'] = walkTheDOM;
  343. /**
  344.  * Convert hyphenated word-word strings to camel case wordWord strings.
  345.  */
  346. function camelize(s) {
  347.     return s.replace(/-(w)/g, function (strMatch, p1){
  348.         return p1.toUpperCase();
  349.     });
  350. }
  351. window['ADS']['camelize'] = camelize;
  352. /********************************
  353. * Chapter 4
  354. *********************************/
  355. /**
  356.  * Convert camel case wordWord strings to hyphenated word-word strings.
  357.  */
  358. function uncamelize(s, sep) {
  359.     sep = sep || '-';
  360.     return s.replace(/([a-z])([A-Z])/g, function (strMatch, p1, p2){
  361.         return p1 + sep + p2.toLowerCase();
  362.     });
  363. }
  364. window['ADS']['uncamelize'] = uncamelize;
  365. /**
  366.  * Add a load event that will run when the document finishes loading - excluding images.
  367.  */
  368. function addLoadEvent(loadEvent,waitForImages) {
  369.     if(!isCompatible()) return false;
  370.     
  371.     // If the wait flag is true use the regular add event method
  372.     if(waitForImages) {
  373.         return addEvent(window, 'load', loadEvent);
  374.     }
  375.     
  376.     // Otherwise use a number of different methods
  377.     
  378.     // Wrap the loadEvent method to assign the correct content for the
  379.     // this keyword and ensure that the event doesn't execute twice
  380.     var init = function() {
  381.         if (arguments.callee.done) return;
  382.         // Return if this function has already been called
  383.         // Mark this function so you can verify if it was already run
  384.         arguments.callee.done = true;
  385.         // Run the load event in the context of the document
  386.         loadEvent.apply(document,arguments);
  387.     };
  388.     
  389.     // Register the event using the DOMContentLoaded event
  390.     if (document.addEventListener) {
  391.         document.addEventListener("DOMContentLoaded", init, false);
  392.     }
  393.     
  394.     // For Safari, use a setInterval() to see if the document has loaded 
  395.     if (/WebKit/i.test(navigator.userAgent)) {
  396.         var _timer = setInterval(function() {
  397.             if (/loaded|complete/.test(document.readyState)) {
  398.                 clearInterval(_timer);
  399.                 init();
  400.             }
  401.         },10);
  402.     }
  403.     // For Internet Explorer (using conditional comments) attach a script 
  404.     // that is deferred to the end of the load process and then check to see
  405.     // if it has loaded
  406.     /*@cc_on @*/
  407.     /*@if (@_win32)
  408.     document.write("<script id=__ie_onload defer src=javascript:void(0)></script>");
  409.     var script = document.getElementById("__ie_onload");
  410.     script.onreadystatechange = function() {
  411.         if (this.readyState == "complete") {
  412.             init();
  413.         }
  414.     };
  415.     /*@end @*/
  416.     return true;
  417. }
  418. window['ADS']['addLoadEvent'] = addLoadEvent;
  419. /**
  420.  * Stop the propagation of an event
  421.  */
  422. function stopPropagation(eventObject) {
  423.     eventObject = eventObject || getEventObject(eventObject);
  424.     if(eventObject.stopPropagation) {
  425.         eventObject.stopPropagation();
  426.     } else {
  427.         eventObject.cancelBubble = true;
  428.     }
  429. }
  430. window['ADS']['stopPropagation'] = stopPropagation;
  431. /**
  432.  * Prevents the default event in the event flow (such as following the href in an anchor).
  433.  */
  434. function preventDefault(eventObject) {
  435.     eventObject = eventObject || getEventObject(eventObject);
  436.     if(eventObject.preventDefault) {
  437.         eventObject.preventDefault();
  438.     } else {
  439.         eventObject.returnValue = false;
  440.     }
  441. }
  442. window['ADS']['preventDefault'] = preventDefault;
  443. /**
  444.  * Retrieves the event object in a cross-browser way.
  445.  */
  446. function getEventObject(W3CEvent) {
  447.     return W3CEvent || window.event;
  448. }
  449. window['ADS']['getEventObject'] = getEventObject;
  450. /**
  451.  * Retrieves the element targeted by the event.
  452.  */
  453. function getTarget(eventObject) {
  454.     eventObject = eventObject || getEventObject(eventObject);
  455.     // Check if the target is W3C or MSIE
  456.     var target = eventObject.target || eventObject.srcElement;
  457.     // Reassign the target to the parent
  458.     // if it is a text node like in Safari
  459.     if(target.nodeType == ADS.node.TEXT_NODE) {
  460.         target = node.parentNode;
  461.     }
  462.     return target;
  463. }
  464. window['ADS']['getTarget'] = getTarget;
  465. /**
  466.  * Determine which mouse button was pressed 
  467.  */
  468. function getMouseButton(eventObject) {
  469.     eventObject = eventObject || getEventObject(eventObject);
  470.     // Initialize an object wit the appropriate properties
  471.     var buttons = {
  472.         'left':false,
  473.         'middle':false,
  474.         'right':false
  475.     };
  476.     // Check the toString value of the eventObject
  477.     // W3C Dom object have a toString method and in this case it
  478.     // should be MouseEvent
  479.     if(eventObject.toString && eventObject.toString().indexOf('MouseEvent') != -1) {
  480.         // W3C Method
  481.         switch(eventObject.button) {
  482.             case 0: buttons.left = true; break;
  483.             case 1: buttons.middle = true; break;
  484.             case 2: buttons.right = true; break;
  485.             default: break;
  486.         }
  487.     } else if(eventObject.button) {
  488.         // MSIE method
  489.         switch(eventObject.button) {
  490.             case 1: buttons.left = true; break;
  491.             case 2: buttons.right = true; break;
  492.             case 3:
  493.                 buttons.left = true;
  494.                 buttons.right = true;
  495.             break;
  496.             case 4: buttons.middle = true; break;
  497.             case 5:
  498.                 buttons.left = true;
  499.                 buttons.middle = true;
  500.             break;
  501.             case 6:
  502.                 buttons.middle = true;
  503.                 buttons.right = true;
  504.             break;
  505.             case 7:
  506.                 buttons.left = true;
  507.                 buttons.middle = true;
  508.                 buttons.right = true;
  509.             break;
  510.             default: break;
  511.         }
  512.     } else {
  513.         return false;
  514.     }
  515.     return buttons;
  516. }
  517. window['ADS']['getMouseButton'] = getMouseButton;
  518. /**
  519.  * Get the position of the pointer in the document. 
  520.  */
  521. function getPointerPositionInDocument(eventObject) {
  522.     eventObject = eventObject || getEventObject(eventObject);
  523.     var x = eventObject.pageX || (eventObject.clientX +
  524.         (document.documentElement.scrollLeft || document.body.scrollLeft));
  525.     var y= eventObject.pageY || (eventObject.clientY +
  526.         (document.documentElement.scrollTop || document.body.scrollTop));
  527.     //x and y now contain the coordinates of the mouse relative to the document origin
  528.     return {'x':x,'y':y};
  529. }
  530. window['ADS']['getPointerPositionInDocument'] = getPointerPositionInDocument;
  531. /**
  532.  * Get the key pressed from the event object 
  533.  */
  534. function getKeyPressed(eventObject) {
  535.     eventObject = eventObject || getEventObject(eventObject);
  536.     var code = eventObject.keyCode;
  537.     var value = String.fromCharCode(code);
  538.     return {'code':code,'value':value};
  539. }
  540. window['ADS']['getKeyPressed'] = getKeyPressed;
  541. /********************************
  542. * Chapter 5
  543. *********************************/
  544. /**
  545.  * Changes the style of a single element by id 
  546.  */
  547. function setStyleById(element, styles) {
  548.     // Retrieve an object reference
  549.     if(!(element = $(element))) return false;
  550.     // Loop through  the styles object an apply each property
  551.     for (property in styles) {
  552.         if(!styles.hasOwnProperty(property)) continue;
  553.     
  554.         if(element.style.setProperty) {
  555.             //DOM2 Style method
  556.             element.style.setProperty(
  557.             uncamelize(property,'-'),styles[property],null);
  558.         } else {
  559.             //Alternative method
  560.             element.style[camelize(property)] = styles[property];
  561.         }
  562.     }
  563.     return true;
  564. }
  565. window['ADS']['setStyle'] = setStyleById;
  566. window['ADS']['setStyleById'] = setStyleById;
  567. /**
  568.  * Changes the style of multiple elements by class name 
  569.  */
  570. function setStylesByClassName(parent, tag, className, styles) {
  571.     if(!(parent = $(parent))) return false;
  572.     var elements = getElementsByClassName(className, tag, parent);
  573.     for (var e = 0 ; e < elements.length ; e++) {
  574.         setStyleById(elements[e], styles);
  575.     }
  576.     return true;
  577. }
  578. window['ADS']['setStylesByClassName'] = setStylesByClassName;
  579. /**
  580.  * Changes the style of multiple elements by tag name 
  581.  */
  582. function setStylesByTagName(tagname, styles, parent) {
  583.     parent = $(parent) || document;
  584.     var elements = parent.getElementsByTagName(tagname);
  585.     for (var e = 0 ; e < elements.length ; e++) {
  586.         setStyleById(elements[e], styles);
  587.     }
  588. }
  589. window['ADS']['setStylesByTagName'] = setStylesByTagName;
  590. /**
  591.  * Retrieves the classes as an array
  592.  */
  593. function getClassNames(element) {
  594.     if(!(element = $(element))) return false;
  595.     // Replace multiple spaces with one space and then
  596.     // split the classname on spaces
  597.     return element.className.replace(/s+/,' ').split(' ');
  598. };
  599. window['ADS']['getClassNames'] = getClassNames;
  600. /**
  601.  * Check if a class exists on an element 
  602.  */
  603. function hasClassName(element, className) {
  604.     if(!(element = $(element))) return false;
  605.     var classes = getClassNames(element);
  606.     for (var i = 0; i < classes.length; i++) {
  607.         // Check if the className matches and return true if it does
  608.         if (classes[i] === className) { return true; }
  609.     }
  610.     return false;
  611. };
  612. window['ADS']['hasClassName'] = hasClassName;
  613. /**
  614.  * Add a class to an element 
  615.  */
  616. function addClassName(element, className) {
  617.     if(!(element = $(element))) return false;
  618.     // Append the classname to the end of the current className
  619.     // If there is no className, don't include the space
  620.     element.className += (element.className ? ' ' : '') + className;
  621.     return true;
  622. };
  623. window['ADS']['addClassName'] = addClassName;
  624. /**
  625.  * remove a class from an element 
  626.  */
  627. function removeClassName(element, className) {
  628.     if(!(element = $(element))) return false;
  629.     var classes = getClassNames(element);
  630.     var length = classes.length
  631.     //loop through the array in reverse, deleting matching items
  632.     // You loop in reverse as you're deleting items from 
  633.     // the array which will shorten it.
  634.     for (var i = length-1; i >= 0; i--) {
  635.         if (classes[i] === className) { delete(classes[i]); }
  636.     }
  637.     element.className = classes.join(' ');
  638.     return (length == classes.length ? false : true);
  639. };
  640. window['ADS']['removeClassName'] = removeClassName;
  641. /**
  642. * Add a new stylesheet 
  643. */
  644. function addStyleSheet(url,media) {
  645.     media = media || 'screen';
  646.     var link = document.createElement('LINK');
  647.     link.setAttribute('rel','stylesheet');
  648.     link.setAttribute('type','text/css');
  649.     link.setAttribute('href',url);
  650.     link.setAttribute('media',media);
  651.     document.getElementsByTagName('head')[0].appendChild(link);
  652. }
  653. window['ADS']['addStyleSheet'] = addStyleSheet;
  654. /** 
  655.  * Remove a stylesheet 
  656.  */
  657. function removeStyleSheet(url,media) {
  658.     var styles = getStyleSheets(url,media);
  659.     for(var i = 0 ; i < styles.length ; i++) {
  660.         var node = styles[i].ownerNode || styles[i].owningElement;
  661.         // Disable the stylesheet
  662.         styles[i].disabled = true;
  663.         // Remove the node
  664.         node.parentNode.removeChild(node);
  665.     }
  666. }
  667. window['ADS']['removeStyleSheet'] = removeStyleSheet;
  668. /**
  669.  * Retrieve an array of all the stylesheets by URL 
  670.  */
  671. function getStyleSheets(url,media) {
  672.     var sheets = [];
  673.     for(var i = 0 ; i < document.styleSheets.length ; i++) {
  674.         if (url &&  document.styleSheets[i].href.indexOf(url) == -1) { continue; }
  675.         if(media) {
  676.             // Normaizle the media strings
  677.             media = media.replace(/,s*/,',');
  678.             var sheetMedia;
  679.                 
  680.             if(document.styleSheets[i].media.mediaText) {
  681.                 // DOM mehtod
  682.                 sheetMedia = document.styleSheets[i].media.mediaText.replace(/,s*/,',');
  683.                 // Safari adds an extra comma and space
  684.                 sheetMedia = sheetMedia.replace(/,s*$/,'');
  685.             } else {
  686.                 // MSIE
  687.                 sheetMedia = document.styleSheets[i].media.replace(/,s*/,',');
  688.             }
  689.             // Skip it if the media don't match
  690.             if (media != sheetMedia) { continue; }
  691.         }
  692.         sheets.push(document.styleSheets[i]);
  693.     }
  694.     return sheets;
  695. }
  696. window['ADS']['getStyleSheets'] = getStyleSheets;
  697. /**
  698.  * Edit a CSS rule 
  699.  */
  700. function editCSSRule(selector,styles,url,media) {
  701.     var styleSheets = (typeof url == 'array' ? url : getStyleSheets(url,media));
  702.     for ( i = 0; i < styleSheets.length; i++ ) {
  703.         // Retrieve the list of rules
  704.         // The DOM2 Style method is styleSheets[i].cssRules
  705.         // The MSIE method is styleSheets[i].rules
  706.         var rules = styleSheets[i].cssRules || styleSheets[i].rules;
  707.         if (!rules) { continue; }
  708.                
  709.         // Convert to uppercase as MSIIE defaults to UPPERCASE tags.
  710.         // this could cause conflicts if you're using case sensetive ids
  711.         selector = selector.toUpperCase();
  712.         
  713.         for(var j = 0; j < rules.length; j++) {
  714.             // Check if it matches
  715.             if(rules[j].selectorText.toUpperCase() == selector) {
  716.                 for (property in styles) {
  717.                     if(!styles.hasOwnProperty(property)) { continue; }
  718.                     // Set the new style property
  719.                     rules[j].style[camelize(property)] = styles[property];
  720.                 }
  721.             }
  722.         }
  723.     }
  724. }
  725. window['ADS']['editCSSRule'] = editCSSRule;
  726. /**
  727.  * Add a CSS rule 
  728.  */
  729. function addCSSRule(selector, styles, index, url, media) {
  730.     var declaration = '';
  731.     // Build the declaration string from the style object
  732.     for (property in styles) {
  733.         if(!styles.hasOwnProperty(property)) { continue; }
  734.         declaration += property + ':' + styles[property] + '; ';
  735.     }
  736.     var styleSheets = (typeof url == 'array' ? url : getStyleSheets(url,media));
  737.     var newIndex;
  738.     for(var i = 0 ; i < styleSheets.length ; i++) {
  739.         // Add the rule        
  740.         if(styleSheets[i].insertRule) {
  741.             // The DOM2 Style method
  742.             // index = length is the end of the list
  743.             newIndex = (index >= 0 ? index : styleSheets[i].cssRules.length);
  744.             styleSheets[i].insertRule(selector + ' { ' + declaration + ' } ', 
  745.                 newIndex);
  746.         } else if(styleSheets[i].addRule) {
  747.             // The Microsoft method
  748.             // index = -1 is the end of the list 
  749.             newIndex = (index >= 0 ? index : -1);
  750.             styleSheets[i].addRule(selector, declaration, newIndex);
  751.         }
  752.     }
  753. }
  754. window['ADS']['addCSSRule'] = addCSSRule;
  755. /**
  756.  * retrieve the computed style of an element 
  757.  */
  758. function getStyle(element,property) {
  759.     if(!(element = $(element)) || !property) return false;
  760.     // Check for the value in the element's style property
  761.     var value = element.style[camelize(property)];
  762.     if (!value) {
  763.         // Retrieve the computed style value
  764.         if (document.defaultView && document.defaultView.getComputedStyle) {
  765.             // The DOM method
  766.             var css = document.defaultView.getComputedStyle(element, null);
  767.             value = css ? css.getPropertyValue(property) : null;
  768.         } else if (element.currentStyle) {
  769.             // The MSIE method
  770.             value = element.currentStyle[camelize(property)];
  771.         }
  772.     }
  773.     // Return an empty string rather than auto so that you don't
  774.     // have to check for auto values 
  775.     return value == 'auto' ? '' : value;
  776. }
  777. window['ADS']['getStyle'] = getStyle;
  778. window['ADS']['getStyleById'] = getStyle;
  779. /********************************
  780. * Chapter 7: Case Study
  781. *********************************/
  782. // no code added in Chapter 6
  783. /********************************
  784. * Chapter 7
  785. *********************************/
  786. /*
  787. parseJSON(string,filter)
  788. A slightly modified version of the public domain method 
  789. at http://www.json.org/json.js This method parses a JSON text 
  790. to produce an object or array. It can throw a 
  791. SyntaxError exception.
  792. The optional filter parameter is a function which can filter and
  793. transform the results. It receives each of the keys and values, and
  794. its return value is used instead of the original value. If it
  795. returns what it received, then structure is not modified. If it
  796. returns undefined then the member is deleted.
  797. Example:
  798. // Parse the text. If a key contains the string 'date' then
  799. // convert the value to a date.
  800. myData = parseJSON(string,function (key, value) {
  801.     return key.indexOf('date') >= 0 ? new Date(value) : value;
  802. });
  803. */
  804. function parseJSON(s,filter) {
  805.     var j;
  806.     function walk(k, v) {
  807.         var i;
  808.         if (v && typeof v === 'object') {
  809.             for (i in v) {
  810.                 if (v.hasOwnProperty(i)) {
  811.                     v[i] = walk(i, v[i]);
  812.                 }
  813.             }
  814.         }
  815.         return filter(k, v);
  816.     }
  817. // Parsing happens in three stages. In the first stage, we run the 
  818. // text against a regular expression which looks for non-JSON 
  819. // characters. We are especially concerned with '()' and 'new' 
  820. // because they can cause invocation, and '=' because it can cause 
  821. // mutation. But just to be safe, we will reject all unexpected 
  822. // characters.
  823.  if (/^("(\.|[^"\nr])*?"|[,:{}[]0-9.-+Eaeflnr-u nrt])+?$/.
  824.             test(s)) {
  825. // In the second stage we use the eval function to compile the text 
  826. // into a JavaScript structure. The '{' operator is subject to a 
  827. // syntactic ambiguity in JavaScript: it can begin a block or an 
  828. // object literal. We wrap the text in parens to eliminate 
  829. // the ambiguity.
  830.         try {
  831.             j = eval('(' + s + ')');
  832.         } catch (e) {
  833.             throw new SyntaxError("parseJSON");
  834.         }
  835.     } else {
  836.         throw new SyntaxError("parseJSON");
  837.     }
  838. // In the optional third stage, we recursively walk the new structure, 
  839. // passing each name/value pair to a filter function for possible 
  840. // transformation.
  841.     if (typeof filter === 'function') {
  842.         j = walk('', j);
  843.     }
  844.     return j;
  845. };
  846.     
  847.         
  848. /**
  849.  * Setup the various parts of an XMLHttpRequest Object 
  850.  */
  851. function getRequestObject(url,options) {
  852.     
  853.     // Initialize the request object
  854.     var req = false;
  855.     if(window.XMLHttpRequest) {
  856.         var req = new window.XMLHttpRequest();
  857.     } else if (window.ActiveXObject) {
  858.         var req = new window.ActiveXObject('Microsoft.XMLHTTP');
  859.     }
  860.     if(!req) return false;
  861.     
  862.     // Define the default options
  863.     options = options || {};
  864.     options.method = options.method || 'GET';
  865.     options.send = options.send || null;
  866.     // Define the various listeners for each state of the request
  867.     req.onreadystatechange = function() {
  868.         switch (req.readyState) {
  869.             case 1:
  870.                 // Loading
  871.                 if(options.loadListener) {
  872.                     options.loadListener.apply(req,arguments);
  873.                 }
  874.                 break;
  875.             case 2:
  876.                 // Loaded
  877.                 if(options.loadedListener) {
  878.                     options.loadedListener.apply(req,arguments);
  879.                 }
  880.                 break;
  881.             case 3:
  882.                 // Interactive
  883.                 if(options.ineractiveListener) {
  884.                     options.ineractiveListener.apply(req,arguments);
  885.                 }
  886.                 break;
  887.             case 4:
  888.                 // Complete
  889.                 // if aborted FF throws errors
  890.                 try { 
  891.                 if (req.status && req.status == 200) {
  892.                     
  893.                     // Specific listeners for content-type
  894.                     // The Content-Type header can include the charset:
  895.                     // Content-Type: text/html; charset=ISO-8859-4
  896.                     // So we'll use a match to extract the part we need.
  897.                     var contentType = req.getResponseHeader('Content-Type');
  898.                     var mimeType = contentType.match(/s*([^;]+)s*(;|$)/i)[1];
  899.                                         
  900.                     switch(mimeType) {
  901.                         case 'text/javascript':
  902.                         case 'application/javascript':
  903.                             // The response is JavaScript so use the 
  904.                             // req.responseText as the argument to the callback
  905.                             if(options.jsResponseListener) {
  906.                                 options.jsResponseListener.call(
  907.                                     req,
  908.                                     req.responseText
  909.                                 );
  910.                             }
  911.                             break;
  912.                         case 'application/json':
  913.                             // The response is json so parse   
  914.                             // req.responseText using the an anonymous functions
  915.                             // which simply returns the JSON object for the
  916.                             // argument to the callback
  917.                             if(options.jsonResponseListener) {
  918.                                 try {
  919.                                     var json = parseJSON(
  920.                                         req.responseText
  921.                                     );
  922.                                 } catch(e) {
  923.                                     var json = false;
  924.                                 }
  925.                                 options.jsonResponseListener.call(
  926.                                     req,
  927.                                     json
  928.                                 );
  929.                             }
  930.                             break;
  931.                         case 'text/xml':
  932.                         case 'application/xml':
  933.                         case 'application/xhtml+xml':
  934.                             // The response is XML so use the 
  935.                             // req.responseXML as the argument to the callback
  936.                             // This will be a Document object
  937.                             if(options.xmlResponseListener) {
  938.                                 options.xmlResponseListener.call(
  939.                                     req,
  940.                                     req.responseXML
  941.                                 );
  942.                             }
  943.                             break;
  944.                         case 'text/html':
  945.                             // The response is HTML so use the 
  946.                             // req.responseText as the argument to the callback
  947.                             if(options.htmlResponseListener) {
  948.                                 options.htmlResponseListener.call(
  949.                                     req,
  950.                                     req.responseText
  951.                                 );
  952.                             }
  953.                             break;
  954.                     }
  955.                 
  956.                     // A complete listener
  957.                     if(options.completeListener) {
  958.                         options.completeListener.apply(req,arguments);
  959.                     }
  960.                 } else {
  961.                     // Response completed but there was an error
  962.                     if(options.errorListener) {
  963.                         options.errorListener.apply(req,arguments);
  964.                     }
  965.                 }
  966.                 
  967.                 } catch(e) {
  968.                     //ignore errors
  969.                     //alert('Response Error: ' + e);
  970.                 }
  971.                 break;
  972.         }
  973.     };
  974.     // Open the request
  975.     req.open(options.method, url, true);
  976.     // Add a special header to identify the requests
  977.     req.setRequestHeader('X-ADS-Ajax-Request','AjaxRequest');
  978.     return req;
  979. }
  980. window['ADS']['getRequestObject'] = getRequestObject;
  981. /**
  982.  * send an XMLHttpRequest using a quick wrapper around the
  983.  * getRequestObject and the send method. 
  984.  */
  985. function ajaxRequest(url,options) {
  986.     var req = getRequestObject(url,options);
  987.     return req.send(options.send);
  988. }
  989. window['ADS']['ajaxRequest'] = ajaxRequest;
  990. /**
  991.  * A counter for the XssHttpRequest objects
  992.  */
  993. var XssHttpRequestCount=0;
  994. /**
  995.  * An cross-site <script> tag implementation of the XMLHttpReqest object 
  996.  */
  997. var XssHttpRequest = function(){
  998.     this.requestID = 'XSS_HTTP_REQUEST_' + (++XssHttpRequestCount);
  999. }
  1000. XssHttpRequest.prototype = {
  1001.     url:null,
  1002.     scriptObject:null,
  1003.     responseJSON:null,
  1004.     status:0,
  1005.     readyState:0,
  1006.     timeout:30000,
  1007.     onreadystatechange:function() { },
  1008.     
  1009.     setReadyState: function(newReadyState) {
  1010.         // Only update the ready state if it's newer than the current state
  1011.         if(this.readyState < newReadyState || newReadyState==0) {
  1012.             this.readyState = newReadyState;
  1013.             this.onreadystatechange();
  1014.         }
  1015.     },
  1016.     
  1017.     open: function(url,timeout){
  1018.         this.timeout = timeout || 30000;
  1019.         // Append a special variable to the URL called XSS_HTTP_REQUEST_CALLBACK
  1020.         // that contains the name of the callback function for this request
  1021.         this.url = url 
  1022.             + ((url.indexOf('?')!=-1) ? '&' : '?' ) 
  1023.             + 'XSS_HTTP_REQUEST_CALLBACK=' 
  1024.             + this.requestID 
  1025.             + '_CALLBACK';    
  1026.         this.setReadyState(0);        
  1027.     },
  1028.     
  1029.     send: function(){
  1030.         var requestObject = this;
  1031.         
  1032.         // Create a new script object to load the external data
  1033.         this.scriptObject = document.createElement('script');
  1034.         this.scriptObject.setAttribute('id',this.requestID);
  1035.         this.scriptObject.setAttribute('type','text/javascript');
  1036.         // Don't set the src or append to the document yet
  1037.         
  1038.         
  1039.         // Create a setTimeout() method that will trigger after a given 
  1040.         // number of milliseconds. If the script hasn't loaded by the given
  1041.         // time it will be cancelled
  1042.         var timeoutWatcher = setTimeout(function() {
  1043.             // Re-populate the window method with an empty method incase the 
  1044.             // script loads later on after we've assumed it stalled
  1045.             window[requestObject.requestID + '_CALLBACK'] = function() { };
  1046.             
  1047.             // Remove the script to prevent it from loading further
  1048.             requestObject.scriptObject.parentNode.removeChild(
  1049.                 requestObject.scriptObject
  1050.             );
  1051.             // Set the status to error
  1052.             requestObject.status = 2;
  1053.             requestObject.statusText = 'Timeout after ' 
  1054.                 + requestObject.timeout 
  1055.                 + ' milliseconds.'            
  1056.             
  1057.             // Update the state
  1058.             requestObject.setReadyState(2);
  1059.             requestObject.setReadyState(3);
  1060.             requestObject.setReadyState(4);
  1061.                     
  1062.         },this.timeout);
  1063.         
  1064.         
  1065.         // Create a method in the window object that matches the callback
  1066.         // in the request. When called it will processing the rest of 
  1067.         // the request
  1068.         window[this.requestID + '_CALLBACK'] = function(JSON) {
  1069.             // When the script loads this method will execute, passing in
  1070.             // the desired JSON object.
  1071.         
  1072.             // Clear the timeoutWatcher method as the request 
  1073.             // loaded successfully
  1074.             clearTimeout(timeoutWatcher);
  1075.             // Update the state
  1076.             requestObject.setReadyState(2);
  1077.             requestObject.setReadyState(3);
  1078.             
  1079.             // Set the status to success 
  1080.             requestObject.responseJSON = JSON; 
  1081.             requestObject.status=1;
  1082.             requestObject.statusText = 'Loaded.' 
  1083.         
  1084.             // Update the state
  1085.             requestObject.setReadyState(4);
  1086.         }
  1087.         // Set the initial state
  1088.         this.setReadyState(1);
  1089.         
  1090.         // Now set the src property and append to the document's 
  1091.         // head. This will load the script
  1092.         this.scriptObject.setAttribute('src',this.url);                    
  1093.         var head = document.getElementsByTagName('head')[0];
  1094.         head.appendChild(this.scriptObject);
  1095.         
  1096.     }
  1097. }
  1098. window['ADS']['XssHttpRequest'] = XssHttpRequest;
  1099. /**
  1100.  * Setup the various parts of the new XssHttpRequest Object 
  1101.  */
  1102. function getXssRequestObject(url,options) {
  1103.     var req = new  XssHttpRequest();
  1104.  
  1105.     options = options || {};
  1106.     // Default timeout of 30 sec
  1107.     options.timeout = options.timeout || 30000;
  1108.     req.onreadystatechange = function() {
  1109.         switch (req.readyState) {
  1110.             case 1:
  1111.                 // Loading
  1112.                 if(options.loadListener) {
  1113.                     options.loadListener.apply(req,arguments);
  1114.                 }
  1115.                 break;
  1116.             case 2:
  1117.                 // Loaded
  1118.                 if(options.loadedListener) {
  1119.                     options.loadedListener.apply(req,arguments);
  1120.                 }
  1121.                 break;
  1122.             case 3:
  1123.                 // Interactive
  1124.                 if(options.ineractiveListener) {
  1125.                     options.ineractiveListener.apply(req,arguments);
  1126.                 }
  1127.                 break;
  1128.             case 4:
  1129.                 // Complete
  1130.                 if (req.status == 1) {
  1131.                     // The request was successful
  1132.                     if(options.completeListener) {
  1133.                         options.completeListener.apply(req,arguments);
  1134.                     }
  1135.                 } else {
  1136.                     // There was an error
  1137.                     if(options.errorListener) {
  1138.                         options.errorListener.apply(req,arguments);
  1139.                     }
  1140.                 }
  1141.                 break;
  1142.         }
  1143.     };
  1144.     req.open(url,options.timeout);
  1145.     
  1146.     return req;
  1147. }
  1148. window['ADS']['getXssRequestObject'] = getXssRequestObject;
  1149. /**
  1150.  * send an XssHttpRequest 
  1151.  */
  1152. function xssRequest(url,options) {
  1153.     var req = getXssRequestObject(url,options);
  1154.     return req.send(null);
  1155. }
  1156. window['ADS']['xssRequest'] = xssRequest;
  1157. /**
  1158.  * a helper method to make callbacks 
  1159.  */
  1160. function makeCallback(method, target) {
  1161.     return function() { method.apply(target,arguments); }
  1162. }
  1163. /**
  1164.  * A URL hash listener used to trigger 
  1165.  * registered methods based on hashes 
  1166.  */
  1167. var actionPager =  {
  1168.     // The previous hash
  1169.     lastHash : '',
  1170.     // A list of the methods registered for the hash patterns
  1171.     callbacks: [],
  1172.     // The safari history list
  1173.     safariHistory : false,
  1174.     // A reference to the iframe for Internet Explorer
  1175.     msieHistory: false,
  1176.     // The class name of the links that should be converted
  1177.     ajaxifyClassName: '',
  1178.     // The root URL of the application. This will be stripped off the URLS
  1179.     // when creating the hashes
  1180.     ajaxifyRoot: '',
  1181.     
  1182.     
  1183.     init: function(ajaxifyClass,ajaxifyRoot,startingHash) {
  1184.         this.ajaxifyClassName = ajaxifyClass || 'ADSActionLink';
  1185.         this.ajaxifyRoot = ajaxifyRoot || '';
  1186.         if (/Safari/i.test(navigator.userAgent)) {
  1187.             this.safariHistory = [];
  1188.         } else if (/MSIE/i.test(navigator.userAgent)) {
  1189.             // In the case of MSIE, add a iframe to track override the back button
  1190.             this.msieHistory = document.createElement("iframe");
  1191.             this.msieHistory.setAttribute("id", "msieHistory");
  1192.             this.msieHistory.setAttribute("name", "msieHistory");
  1193.             setStyleById(this.msieHistory,{
  1194.                 'width':'100px',
  1195.                 'height':'100px',
  1196.                 'border':'1px solid black',
  1197.                 'visibility':'visible',
  1198.                 'zIndex':'-1'
  1199.             });
  1200.             document.body.appendChild(this.msieHistory);
  1201.             this.msieHistory = frames['msieHistory'];
  1202.             
  1203.         }
  1204.         // Convert the links to AJAX links
  1205.         this.ajaxifyLinks();
  1206.         // Get the current location
  1207.         var location = this.getLocation();
  1208.         // Check if the location has a hash (from a bookmark)
  1209.         // or if a hash has bee provided
  1210.         if(!location.hash && !startingHash) { startingHash = 'start'; }
  1211.         // Store the hash as necessary
  1212.         ajaxHash = this.getHashFromURL(location.hash) || startingHash;
  1213.         this.addBackButtonHash(ajaxHash);
  1214.         // Add a watching event to look for changes in the location bar
  1215.         var watcherCallback = makeCallback(this.watchLocationForChange,this);
  1216.         window.setInterval(watcherCallback,200);
  1217.     },
  1218.     ajaxifyLinks: function() {
  1219.         // Convert the links to anchors for ajax handling
  1220.         links = getElementsByClassName(this.ajaxifyClassName, 'a', document);
  1221.         for(var i=0 ; i < links.length ; i++) {
  1222.             if(hasClassName(links[i],'ADSActionPagerModified')) { continue; }
  1223.         
  1224.             // Convert the herf attribute to #value
  1225.             links[i].setAttribute(
  1226.                 'href',
  1227.                 this.convertURLToHash(links[i].getAttribute('href'))
  1228.             );
  1229.             addClassName(links[i],'ADSActionPagerModified');
  1230.             // Attach a click event to add history as necessary
  1231.             addEvent(links[i],'click',function() {
  1232.                  if (this.href && this.href.indexOf('#') > -1) {
  1233.                      actionPager.addBackButtonHash(
  1234.                         actionPager.getHashFromURL(this.href)
  1235.                     );
  1236.                  }
  1237.             });
  1238.         }
  1239.     },
  1240.     addBackButtonHash: function(ajaxHash) {
  1241.         // Store the hash
  1242.         if (!ajaxHash) return false;
  1243.         if (this.safariHistory !== false) {
  1244.             // Using a special array for Safari
  1245.             if (this.safariHistory.length == 0) {
  1246.                 this.safariHistory[window.history.length] = ajaxHash;
  1247.             } else {
  1248.                 this.safariHistory[window.history.length+1] = ajaxHash;
  1249.             }
  1250.             return true;
  1251.         } else if (this.msieHistory !== false) {
  1252.             // By navigating the iframe in MSIE
  1253.             this.msieHistory.document.execCommand('Stop');
  1254.             this.msieHistory.location.href = '/fakepage?hash='
  1255.                 + ajaxHash
  1256.                 + '&title='+document.title;
  1257.             return true;
  1258.         } else {
  1259.             // By changing the location value
  1260.             // The function is wrapped using makeCallback so that this 
  1261.             // will refer to the actionPager from within the timeout method
  1262.             var timeoutCallback = makeCallback(function() {
  1263.                 if (this.getHashFromURL(window.location.href) != ajaxHash) {
  1264.                     window.location.replace(location.href+'#'+ajaxHash);
  1265.                 }
  1266.             },this);
  1267.             setTimeout(timeoutCallback, 200);
  1268.             return true;
  1269.         }
  1270.         return false;
  1271.     },
  1272.     watchLocationForChange: function() {
  1273.         
  1274.         var newHash;
  1275.         // Retrieve the value for the new hash
  1276.         if (this.safariHistory !== false) {
  1277.             // From the history array for safari
  1278.             if (this.safariHistory[history.length]) {
  1279.                 newHash = this.safariHistory[history.length];
  1280.             }
  1281.         } else if (this.msieHistory !== false) {
  1282.             // From the location of the iframe in MSIE
  1283.             newHash = this.msieHistory.location.href.split('&')[0].split('=')[1];
  1284.         } else if (location.hash != '') {
  1285.             // From the window.location otherwise
  1286.             newHash = this.getHashFromURL(window.location.href);
  1287.         }
  1288.         // Update the page if the new hash doesn't equal the last hash
  1289.         if (newHash && this.lastHash != newHash) {
  1290.             if (this.msieHistory !== false 
  1291.             && this.getHashFromURL(window.location.href) != newHash) {
  1292.                 // Fix the location bar in MSIE so it bookmarks properly
  1293.                 location.hash = newHash;
  1294.             }
  1295.             
  1296.             // Try executing any registered listeners
  1297.             // using try/catch incase of an exception
  1298.             try {
  1299.                 this.executeListeners(newHash);
  1300.                 // Update the links again incase any new
  1301.                 // ones were added with the handler
  1302.                 this.ajaxifyLinks();
  1303.             } catch(e) {
  1304.                 // This will catch any bad JS in the callbacks.
  1305.                 alert(e);
  1306.             }
  1307.             
  1308.             // Save this as the last hash
  1309.             this.lastHash = newHash;
  1310.         }
  1311.     },
  1312.     register: function(regex,method,context){
  1313.         var obj = {'regex':regex};
  1314.         if(context) {
  1315.             // A context has been specified
  1316.             obj.callback = function(matches) { method.apply(context,matches); };
  1317.         } else {
  1318.             // Use the window as the context
  1319.             obj.callback = function(matches) { method.apply(window,matches); };
  1320.         }
  1321.         
  1322.         // Add listeners to the callback array
  1323.         this.callbacks.push(obj)
  1324.     },
  1325.     convertURLToHash: function(url) {
  1326.         if (!url) {
  1327.             // No url so return a pound
  1328.             return '#';
  1329.         } else if(url.indexOf("#") != -1) {
  1330.             // Has a hash so return it
  1331.             return url.split("#")[1];
  1332.         } else {
  1333.             // If the URL includes the domain name (MSIE) strip it off.
  1334.             if(url.indexOf("://") != -1) {
  1335.                 url = url.match(/://[^/]+(.*)/)[1];
  1336.             }
  1337.             // Strip off the root as specified in init()
  1338.             return '#' + url.substr(this.ajaxifyRoot.length)
  1339.         }
  1340.     },
  1341.     getHashFromURL: function(url) {
  1342.         if (!url || url.indexOf("#") == -1) { return ''; }
  1343.         return url.split("#")[1];
  1344.     },
  1345.     getLocation: function() {
  1346.         // Check for a hash
  1347.         if(!window.location.hash) {
  1348.             // Not one so make it
  1349.             var url = {host:null,hash:null}
  1350.             if (window.location.href.indexOf("#") > -1) {
  1351.                 parts = window.location.href.split("#")[1];
  1352.                 url.domain = parts[0];
  1353.                 url.hash = parts[1];
  1354.             } else {
  1355.                 url.domain = window.location;
  1356.             }
  1357.             return url;
  1358.         }
  1359.         return window.location;
  1360.     },
  1361.     executeListeners: function(hash){
  1362.         // Execute any listeners that match the hash
  1363.         for(var i in this.callbacks) {
  1364.             if((matches = hash.match(this.callbacks[i].regex))) {
  1365.                 this.callbacks[i].callback(matches);
  1366.             }
  1367.         }
  1368.     }
  1369. }
  1370. window['ADS']['actionPager'] = actionPager;
  1371. /**
  1372.  * a helper method to clone a JavaScript object 
  1373.  */
  1374. function clone(myObj) {
  1375.     if(typeof(myObj) != 'object') return myObj;
  1376.     if(myObj == null) return myObj;
  1377.     var myNewObj = new Object();
  1378.     for(var i in myObj) {
  1379.         myNewObj[i] = clone(myObj[i]);
  1380.     }
  1381.     return myNewObj;
  1382. }
  1383. /**
  1384.  * An array to hold the queues 
  1385.  */
  1386. var requestQueue = [];
  1387. /**
  1388.  * Wrapper for the ADS.ajaxRequest method the enables a queue 
  1389.  */
  1390. function ajaxRequestQueue(url,options,queue) {
  1391.     queue = queue || 'default';
  1392.     
  1393.     // This object will wrap the option listeners in another function
  1394.     // so the option object needs to be unique. If a shared options object 
  1395.     // is used when the method is call it will get into a recursive mess.
  1396.     options = clone(options) || {};
  1397.     if(!requestQueue[queue]) requestQueue[queue] = [];
  1398.      
  1399.     // The queue needs to invoke the next request using the completeListener
  1400.     // when the previous request is complete. If the complete listener is 
  1401.     // already defined then you need to invoke it first.
  1402.     
  1403.     // Grab the old listener
  1404.     var userCompleteListener = options.completeListener;
  1405.     // Add a new listener
  1406.     options.completeListener = function() {
  1407.         
  1408.         // If there was an old one invoke it first
  1409.         if(userCompleteListener) {
  1410.             // this will refer to the request object
  1411.             userCompleteListener.apply(this,arguments);        
  1412.         };
  1413.         // Remove this request from the queue
  1414.         requestQueue[queue].shift();
  1415.         
  1416.         // Invoke the next item in the queue
  1417.         if(requestQueue[queue][0]) {
  1418.             // The request is in the req property but you alos need to include
  1419.             // the send option incase it's a POST request
  1420.             var q = requestQueue[queue][0].req.send(
  1421.                 requestQueue[queue][0].send
  1422.             );
  1423.         }
  1424.     }
  1425.     
  1426.     // If there's an error the rest of the queue should be cancelled
  1427.     // by calling their error methods
  1428.     
  1429.     // Grab the old listener
  1430.     var userErrorListener = options.errorListener;
  1431.     // Add a new listener
  1432.     options.errorListener = function() {
  1433.     
  1434.         if(userErrorListener) {
  1435.             userErrorListener.apply(this,arguments);        
  1436.         };
  1437.         
  1438.         // Remove this request from the queue as the error 
  1439.         // was already invoked
  1440.         requestQueue[queue].shift();
  1441.         
  1442.         // Kill the rest of the queue as there was an error but call the
  1443.         // errorListener on each first. By invoking the error listener on
  1444.         // the next item in the queue it will clear all queued requests as
  1445.         // as each will invoke the next in a chain
  1446.         
  1447.         // Check if there is still anything in the queue
  1448.         if(requestQueue[queue].length) {
  1449.             
  1450.             // Grab the next one
  1451.             var q = requestQueue[queue].shift();
  1452.             // Abort the request.
  1453.             q.req.abort();
  1454.             
  1455.             // Fake a request object so that the errorListener thinks it
  1456.             // completed and runs accordingly
  1457.             var fakeRequest = new Object();
  1458.             
  1459.             // Set the status to 0 and readyState to 4 (as if 
  1460.             // the request completed but failed
  1461.             fakeRequest.status = 0;
  1462.             fakeRequest.readyState = 4
  1463.             fakeRequest.responseText = null;
  1464.             fakeRequest.responseXML = null;
  1465.             // Set an error so you can show a message if you wish.
  1466.             fakeRequest.statusText = 'A request in the queue received an error';
  1467.             // Invoke the state change. If readyState is 4 and 
  1468.             // status is not 200 then errorListener will be invoked.
  1469.             q.error.apply(fakeRequest);
  1470.         }
  1471.        
  1472.     }
  1473.     
  1474.     // Add this requests to the queue
  1475.     requestQueue[queue].push({
  1476.         req:getRequestObject(url,options),
  1477.         send:options.send,
  1478.         error:options.errorListener
  1479.     });
  1480.     // If the length of the queue is only one 
  1481.     // item (the first) invoke the request    
  1482.     if(requestQueue[queue].length == 1) {
  1483.         ajaxRequest(url,options);
  1484.     }
  1485. }
  1486. window['ADS']['ajaxRequestQueue'] = ajaxRequestQueue;
  1487. /********************************
  1488. * Chapter 7 thru 12
  1489. *********************************/
  1490. //nothing added to the library
  1491. })();