Guest User

Untitled

a guest
Jul 22nd, 2013
632
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Only include the contents of this file once - for example, if this is included in a lightbox we don't want to re-run
  3.  * all of this - just use the loaded version.  (i.e. rerunning would clear page.bundle which would remove all the
  4.  * language strings for the current page)
  5.  */
  6. if (!window.page)
  7. {
  8. var page = {};
  9.  
  10. page.isLoaded = false;
  11.  
  12. /**
  13.  * Utility for adding and using localized messages on the page.
  14.  */
  15. page.bundle = {};
  16. page.bundle.messages = {};
  17. page.bundle.addKey = function( key, value )
  18. {
  19.   page.bundle.messages[key] = value;
  20. };
  21.  
  22. page.bundle.getString = function( key /*, arg1, arg2, ..., argN */ )
  23. {
  24.   var result = page.bundle.messages[key];
  25.   if ( !result )
  26.   {
  27.      return "!!!!" + key + "!!!!!";
  28.   }
  29.   else
  30.   {
  31.     if ( arguments.length > 1 )
  32.     {
  33.       for ( var i = 1; i < arguments.length; i++ )
  34.       {
  35.         result = result.replace( new RegExp("\\{"+(i-1)+"\\}","g"), arguments[i] );
  36.       }
  37.     }
  38.     return result;
  39.   }
  40. };
  41.  
  42. /**
  43.  * Provides support for lazy initialization of javascript behavior when a certain
  44.  * event happens to a certain item.
  45.  */
  46. page.LazyInit = function( event, eventTypes, initCode )
  47. {
  48.   var e = event || window.event;
  49.   var target = Event.element( event );
  50.   // This is because events bubble and we want a reference
  51.   // to the element we registered the handlers on.
  52.   target = page.util.upToClass(target, "jsInit");
  53.   for (var i = 0; i < eventTypes.length; i++ )
  54.   {
  55.     target['on'+eventTypes[i]] = null;
  56.   }
  57.   eval( initCode ); //initCode can reference "target"
  58. };
  59.  
  60. /**
  61.  * Evaluates any <script> tags in the provided string in the global scope.
  62.  * Useful for evaluating scripts that come back in text from an Ajax call.
  63.  * If signalObject is passed then signalObject.evaluatingScripts will be set to false when done.
  64.  */
  65. page.globalEvalScripts = function(str, evalExternalScripts, signalObject)
  66. {
  67.   //Get any external scripts
  68.   var waitForVars = [];
  69.   var scriptVars = [
  70.                     { script: 'bb_htmlarea', variable: ['HTMLArea'] },
  71.                     { script: 'w_editor', variable: ['WebeqEditors'] },
  72.                     { script: 'wysiwyg.js', variable: ['vtbe_attchfiles'] },
  73.                     { script: 'gradebook_utils.js', variable: ['gradebook_utils'] },
  74.                     { script: 'rubric.js', variable: ['rubricModule'] },
  75.                     { script: 'gridmgmt.js', variable: ['gridMgmt'] },
  76.                     { script: 'calendar-time.js', variable: ['calendar'] },
  77.                     { script: 'widget.js', variable: ['widget'] },
  78.                     { script: 'vtbeTinymce.js', variable: ['tinyMceWrapper'] },
  79.                     { script: 'WhatsNewView.js', variable: ['WhatsNewView'] },
  80.                     { script: 'tiny_mce.js', variable: ['tinymce','tinyMCE'] },
  81.                     { script: 'slider.js', variable: ['Control.Slider'] }
  82.                    ];
  83.   if (evalExternalScripts)
  84.   {
  85.     var externalScriptRE = '<script[^>]*src=["\']([^>"\']*)["\'][^>]*>([\\S\\s]*?)<\/script>';
  86.     var scriptMatches = str.match(new RegExp(externalScriptRE, 'img'));
  87.     if (scriptMatches && scriptMatches.length > 0)
  88.     {
  89.       $A(scriptMatches).each(function(scriptTag)
  90.       {
  91.         var matches = scriptTag.match(new RegExp(externalScriptRE, 'im'));
  92.         if (matches && matches.length > 0 && matches[1] != '')
  93.         {
  94.           var scriptSrc = matches[1];
  95.           if (scriptSrc.indexOf('/dwr_open/') != -1)
  96.           {
  97.             // dwr_open calls will ONLY work if the current page's webapp == the caller's webapp,
  98.             // otherwise we'll get a session error.  THis will happen if a lightbox is loaded with
  99.             // dynamic content from a different webapp (say /webapps/blackboard) while the main page
  100.             // is loaded from /webapps/discussionboard.  To avoid this, rewrite the url to use the
  101.             // webapp associated with the current page.
  102.             var newparts = scriptSrc.split('/');
  103.             var oldparts = window.location.pathname.split('/');
  104.             newparts[1] = oldparts[1];
  105.             newparts[2] = oldparts[2];
  106.             scriptSrc = newparts.join('/');
  107.           }
  108.           var scriptElem = new Element('script', {
  109.             type: 'text/javascript',
  110.             src: scriptSrc
  111.           });
  112.           var head = $$('head')[0];
  113.           head.appendChild(scriptElem);
  114.  
  115.           for ( var i = 0; i < scriptVars.length; i++ )
  116.           {
  117.             if ( scriptSrc.indexOf( scriptVars[i].script ) != -1 )
  118.             {
  119.                  scriptVars[ i ].variable.each( function( s )
  120.                 {
  121.                   waitForVars.push( s );
  122.                 } );
  123.                 break;
  124.             }
  125.           }
  126.         }
  127.       });
  128.     }
  129.   }
  130. //Finding Comments in HTML Source Code Using Regular Expressions and replaces with empty value
  131. //Example: <!-- <script>alert("welcome");</script>--> = ''
  132. //So,that extractScripts won't find commented scripts to extract
  133. //str =str.replace(new RegExp('\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>', 'img'), '');
  134.   page.delayAddExtractedScripts(str.extractScripts(), waitForVars, signalObject);
  135. };
  136.  
  137. // Evaluate any inline script - delay a bit to give the scripts above time to load
  138. // NOTE that this is not guaranteed to work - if there are delays loading and initializing
  139. // the scripts required then code in these scripts might fail to find the required variables
  140. // If it is for our code then updating waitForVars appropriately per script will work
  141. page.delayAddExtractedScripts = function (scripts, waitForVars, signalObject)
  142. {
  143.   var count = 0;
  144.   if (waitForVars.length === 0)
  145.   {
  146.     page.actuallyAddExtractedScripts(scripts, signalObject);
  147.   }
  148.   else
  149.   {
  150.   new PeriodicalExecuter( function( pe )
  151.   {
  152.     if ( count < 100 )
  153.     {
  154.       count++;
  155.       if ( page.allVariablesDefined(waitForVars) )
  156.       {
  157.         page.actuallyAddExtractedScripts(scripts, signalObject);
  158.         pe.stop();
  159.       }
  160.     }
  161.     else // give up if it takes longer than 5s to load
  162.     {
  163.       page.actuallyAddExtractedScripts(scripts, signalObject);
  164.       pe.stop();
  165.     }
  166.   }.bind(this), 0.05 );
  167.   }
  168. };
  169.  
  170. page.variableDefined = function (avar)
  171. {
  172.  
  173.   if ( !window[avar] )
  174.   {
  175.     if (avar.indexOf('.') > 0)
  176.     {
  177.       var parts = avar.split('.');
  178.       var obj = window[parts[0]];
  179.       for (var partNum = 1; obj && partNum < parts.length; partNum++)
  180.       {
  181.         obj = obj[parts[partNum]];
  182.       }
  183.       if (obj)
  184.       {
  185.         return true;
  186.       }
  187.     }
  188.     return false;
  189.   }
  190.   return true;
  191. };
  192. page.allVariablesDefined = function(vars)
  193. {
  194.   var result = true;
  195.   for ( var i = 0; i < vars.length; i++ )
  196.   {
  197.     if ( !page.variableDefined(vars[i]) )
  198.     {
  199.       result = false;
  200.       break;
  201.     }
  202.   }
  203.   return result;
  204. };
  205.  
  206. page.actuallyAddExtractedScripts = function (scripts, signalObject)
  207. {
  208.   var scriptExecutionDelay = 0;
  209.   if( signalObject )
  210.   {
  211.     scriptExecutionDelay = signalObject.delayScriptExecution;
  212.   }
  213.   scripts.each(function(script)
  214.     {
  215.       if ( script != '' )
  216.       {
  217.         if ( Prototype.Browser.IE && window.execScript )
  218.         {
  219.           ( function()
  220.             {
  221.               window.execScript( script );
  222.             }.delay( scriptExecutionDelay ) );
  223.         }
  224.         else
  225.         {
  226.           ( function()
  227.             {
  228.               var scriptElem = new Element( 'script',
  229.               {
  230.                 type : 'text/javascript'
  231.               } );
  232.               var head = $$( 'head' )[ 0 ];
  233.               script = document.createTextNode( script );
  234.               scriptElem.appendChild( script );
  235.               head.appendChild( scriptElem );
  236.               head.removeChild( scriptElem );
  237.            }.delay( scriptExecutionDelay ) );
  238.         }
  239.       }
  240.     }
  241.   );
  242.   if (signalObject)
  243.   {
  244.     signalObject.evaluatingScripts = false;
  245.   }
  246. };
  247.  
  248. page.setIframeHeightAndWidth = function ()
  249. {
  250.   page.setIframeHeight();
  251.   page.setIframeWidth();
  252. };
  253.  
  254. page.setIframeHeight = function ()
  255. {
  256.   try
  257.   {
  258.     var iframeElements = $$('iframe.cleanSlate');
  259.     var i = 0;
  260.     for( i = 0; i < iframeElements.length; i++ )
  261.     {
  262.       var iframeElement = iframeElements[i];
  263.       if ( iframeElement.contentWindow && iframeElement.contentWindow.document && iframeElement.contentWindow.document.body )
  264.       {
  265.         var frameHeight = page.util.getMaxContentHeight( iframeElement );
  266.         iframeElement.style.height =iframeElement.contentWindow.document.body.scrollHeight + frameHeight + 300 +'px';
  267.       }
  268.     }
  269.   }
  270.   catch( e ){}
  271. };
  272.  
  273. page.setIframeWidth = function ()
  274. {
  275.   try
  276.   {
  277.     var iframeElements = $$('iframe.cleanSlate');
  278.     var i = 0;
  279.     for( i = 0; i < iframeElements.length; i++ )
  280.     {
  281.       var iframeElement = iframeElements[i];
  282.       if ( iframeElement.contentWindow && iframeElement.contentWindow.document && iframeElement.contentWindow.document.body )
  283.       {
  284.         var frameWidth = page.util.getMaxContentWidth( iframeElement );
  285.         iframeElement.style.width = '100%';
  286.       }
  287.     }
  288.   }
  289.   catch( e ){}
  290. };
  291.  
  292. page.onResizeChannelIframe = function( channelExtRef )
  293. {
  294.   var frameId = 'iframe' + channelExtRef;
  295.   var listId = 'list_channel' + channelExtRef;
  296.   var f = $( frameId );
  297.   var fli = f.contentWindow.document.getElementById( listId );
  298.   if (fli)
  299.   {
  300.     f.style.height = fli.scrollHeight + 15 + "px";
  301.   }
  302. };
  303.  
  304. /**
  305.  * Contains page-wide utility methods
  306.  */
  307. page.util = {};
  308.  
  309. /**
  310.  * Returns whether the specific element has the specified class name.
  311.  * Same as prototype's Element.hasClassName, except it doesn't extend the element (which is faster in IE).
  312.  */
  313. page.util.hasClassName = function ( element, className )
  314. {
  315.   var elementClassName = element.className;
  316.   if ((typeof elementClassName == "undefined") || elementClassName.length === 0)
  317.   {
  318.     return false;
  319.   }
  320.   if (elementClassName == className ||
  321.       elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
  322.   {
  323.     return true;
  324.   }
  325.  
  326.   return false;
  327. };
  328.  
  329. page.util.fireClick = function ( elem )
  330. {
  331.   if (Prototype.Browser.IE)
  332.   {
  333.     elem.fireEvent("onclick");
  334.   }
  335.   else
  336.   {
  337.     var evt = document.createEvent("HTMLEvents");
  338.     evt.initEvent("click", true, true);
  339.     elem.dispatchEvent(evt);
  340.   }
  341. };
  342.  
  343. page.util.useARIA = function ()
  344. {
  345.   if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)){ //test for Firefox/x.x or Firefox x.x (ignoring remaining digits);
  346.     var ffversion= parseFloat( RegExp.$1 ); // capture x.x portion and store as a number
  347.     if (ffversion >= 1.9)
  348.     {
  349.       return true;
  350.     }
  351.   }
  352.   else if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)){ //test for MSIE x.x;
  353.     var ieversion= parseFloat( RegExp.$1 ); // capture x.x portion and store as a number
  354.     if (ieversion>=8)
  355.     {
  356.       return true;
  357.     }
  358.   }
  359.   return false;
  360. };
  361.  
  362. // Find an element with the given className, starting with the element passed in
  363. page.util.upToClass = function ( element, className )
  364. {
  365.   while (element && !page.util.hasClassName(element, className))
  366.   {
  367.     element = element.parentNode;
  368.   }
  369.   return $(element);
  370. };
  371.  
  372. page.util.isRTL = function ()
  373. {
  374.   var els = document.getElementsByTagName("html");
  375.   var is_rtl = (typeof(els) != 'undefined' &&
  376.           els && els.length == 1 && els[0].dir == 'rtl' );
  377.   return is_rtl ;
  378. };
  379.  
  380. page.util.allImagesLoaded = function (imgList)
  381. {
  382.   var allDone = true;
  383.   if (imgList)
  384.   {
  385.     for ( var i = 0, c = imgList.length; i < c; i++ )
  386.     {
  387.       var animg = imgList[i];
  388.       // TODO - this doesn't appear to work on IE.
  389.       if ( !animg.complete )
  390.       {
  391.         allDone = false;
  392.         break;
  393.       }
  394.     }
  395.   }
  396.   return allDone;
  397. };
  398.  
  399. // Exposes (display but keep invisible) an invisible element for measurement
  400. // recursively traverses up the DOM looking for
  401. // a parent node of element whose display == 'none'
  402. // If found, sets its style to: display:block, position:absolute, and visibility:hidden
  403. // and saves it as element.hiddenNode so it can be easily unexposed
  404. page.util.exposeElementForMeasurement = function ( element )
  405. {
  406.   element = $(element);
  407.   var e = element;
  408.   var hiddenNode;
  409.   // find parent node that is hidden
  410.   while ( !hiddenNode && e && e.parentNode)
  411.   {
  412.     if ( $(e).getStyle('display') === 'none')
  413.     {
  414.       hiddenNode = $(e);
  415.     }
  416.     e = $(e.parentNode);
  417.   }
  418.   if ( hiddenNode )
  419.   {
  420.     // save original style attributes: visibility, position, & display
  421.     element.hiddenNode = hiddenNode;
  422.     var style = hiddenNode.style;
  423.     var originalStyles = {
  424.                           visibility: style.visibility,
  425.                           position:   style.position,
  426.                           display:    style.display
  427.                         };
  428.     var newStyles = {
  429.                      visibility: 'hidden',
  430.                      display:    'block'
  431.                    };
  432.  
  433.      if (originalStyles.position !== 'fixed')
  434.      {
  435.        newStyles.position = 'absolute';
  436.      }
  437.      hiddenNode.originalStyles = originalStyles;
  438.      // set new style for: visibility, position, & display
  439.      hiddenNode.setStyle( newStyles );
  440.   }
  441.  
  442. };
  443.  
  444. // undo previous call to exposeElementForMeasurement
  445. page.util.unExposeElementForMeasurement = function ( element )
  446. {
  447.   element = $(element);
  448.   if ( element && element.hiddenNode && element.hiddenNode.originalStyles )
  449.   {
  450.     Element.setStyle( element.hiddenNode, element.hiddenNode.originalStyles );
  451.     element.hiddenNode.originalStyles = null;
  452.     element.hiddenNode = null;
  453.   }
  454.  
  455. };
  456.  
  457.  
  458. /**
  459.  * Returns whether any part of the two elements overlap each other.
  460.  */
  461. page.util.elementsOverlap = function ( e1, e2 )
  462. {
  463.   var pos1 = $(e1).cumulativeOffset();
  464.   var a = { x1: pos1.left, y1: pos1.top, x2: pos1.left + e1.getWidth(), y2: pos1.top + e1.getHeight() };
  465.   var pos2 = $(e2).cumulativeOffset();
  466.   var b = { x1: pos2.left, y1: pos2.top, x2: pos2.left + e2.getWidth(), y2: pos2.top + e2.getHeight() };
  467.  
  468.   return a.x1 < b.x2 && a.x2 > b.x1 && a.y1 < b.y2 && a.y2 > b.y1;
  469. };
  470. /**
  471.  *  To handle the case where the focus is visible but too close to the
  472.     bottom of the page, scroll the page up a bit.
  473.     Note: when using scrollbar.js, use scrollBar.scrollTo() rather than focusAndScroll
  474. */
  475.  
  476. page.util.focusAndScroll= function(elem)
  477. {
  478.   elem.focus();
  479.  
  480.   return page.util.ensureVisible(elem);
  481. };
  482.  
  483. page.util.ensureVisible= function(elem)
  484. {
  485.   var scrolltop = document.viewport.getScrollOffsets().top;
  486.   var mytop = elem.cumulativeOffset()[1];
  487.   var height = document.viewport.getDimensions().height;
  488.   var realtop = mytop - scrolltop;
  489.   var thirty = height * 0.3;
  490.   if (realtop > (height-thirty))
  491.   {
  492.     var scrollDistance = realtop - thirty;
  493.     window.scrollBy(0,scrollDistance);
  494.   }
  495.   return false;
  496. };
  497.  
  498. page.util.processJSProtoString = function (string, checkToken) {
  499.   // This value must match the value passed as the 2nd parameter to this string.
  500.   // The goal is to pass a known value, as a constant, through the javascript: pseudo-protocol
  501.   // handler. We can then examine the result to determine the decoding method used by the current
  502.   // browser.
  503.   var sniffToken = '%C3%A9';
  504.  
  505.   // There are three known decoding cases, non-translated, UTF8, and unescape
  506.   if (checkToken === unescape(sniffToken)) {
  507.     // Unescape decoded
  508.     return decodeURIComponent(escape(string));
  509.   } else if (checkToken === sniffToken) {
  510.     // Non-translated
  511.     return decodeURIComponent(string);
  512.   } else {
  513.     // UTF8 Decoded/Unknown
  514.     return string;
  515.   }
  516. };
  517.  
  518. /**
  519.  * Find the first action bar that precedes sourceElement
  520.  * Returns the action bar div element if found, null otherwise
  521.  *
  522.  * @param sourceElement
  523.  */
  524. page.util.findPrecedingActionBar = function( sourceElement )
  525. {
  526.   var actionBar = null;
  527.   // Loop through each ancestor of sourceElement,
  528.   // starting with parent, until an action bar is found
  529.   sourceElement.ancestors().each( function( item )
  530.   {
  531.     actionBar = item.previous('div.tabActionBar') ||
  532.                 item.previous('div.actionBarMicro') ||
  533.                 item.previous('div.actionBar');
  534.     if (actionBar)
  535.     {
  536.       throw $break;
  537.     }
  538.   });
  539.   return actionBar;
  540. };
  541.  
  542. page.util.getLargestDimensions = function (element)
  543. {
  544.   var width = 0;
  545.   var height = 0;
  546.   var dim;
  547.   while (element != document)
  548.   {
  549.     dim = $(element).getDimensions();
  550.     if (dim.width > width)
  551.     {
  552.       width = dim.width;
  553.     }
  554.     if (dim.height > height)
  555.     {
  556.       height = dim.height;
  557.     }
  558.     element = element.up();
  559.   }
  560.     return { width: width, height: height };
  561. };
  562.  
  563. /*
  564.  * Resize the current window so that it will fit the largest dimensions found for the given element.
  565.  * Will also reposition on the screen if required to fit.  Will not size larger than the screen.
  566.  * NOTE that this will only work for popup windows - main windows are typically in a tabset in
  567.  * the browser and they don't allow resizing like this.  That's OK because the main use case
  568.  * for this method is to make sure popup windows are resized appropriately for their content.
  569.  */
  570. page.util.resizeToContent = function (startElement)
  571. {
  572.     var dim = page.util.getLargestDimensions(startElement);
  573.     var newWidth = dim.width;
  574.     newWidth += 25; // TODO: Haven't figured out why I need this extra space yet...
  575.     if (window.innerWidth > newWidth)
  576.     {
  577.       newWidth = window.innerWidth;
  578.     }
  579.     if (newWidth > screen.width)
  580.     {
  581.       newWidth = screen.width;
  582.     }
  583.  
  584.     var newHeight = dim.height;
  585.     newHeight += 100; // TODO: Haven't figured out why I need this extra space yet
  586.     if (window.innerHeight > newHeight)
  587.     {
  588.       newHeight = window.innerHeight;
  589.     }
  590.     if (newHeight > screen.height)
  591.     {
  592.       newHeight = screen.height;
  593.     }
  594.  
  595.     var left = 0;
  596.     var top = 0;
  597.     if ( window.screenLeft )
  598.     {
  599.       left = window.screenLeft;
  600.       top = window.screenTop;
  601.     }
  602.     else if ( window.screenX )
  603.     {
  604.       left = window.screenX;
  605.       top = window.screenY;
  606.     }
  607.     if (left + newWidth > screen.width)
  608.     {
  609.       left = screen.width - newWidth;
  610.       if (left < 0)
  611.       {
  612.         left = 0;
  613.       }
  614.     }
  615.     if (top + newHeight > screen.height)
  616.     {
  617.       top = screen.height - newHeight;
  618.       if (top < 0)
  619.       {
  620.         top = 0;
  621.       }
  622.     }
  623.     window.moveTo(left,top);
  624.     window.resizeTo(newWidth,newHeight);
  625. };
  626.  
  627. /**
  628.  * Sets the css position of all li elements that are contained in action bars on the page.
  629.  * Since z-index only works on positioned elements, this function can be used to ensure that
  630.  * divs with a higher z-index will always appear on top of any action bars on the page.
  631.  *
  632.  * @param cssPosition
  633.  */
  634. page.util.setActionBarPosition = function( cssPosition )
  635. {
  636.   $$( 'div.actionBar',
  637.       'div.tabActionBar',
  638.       'div.actionBarMicro' ).each( function( actionbar )
  639.   {
  640.     actionbar.select( 'li' ).each( function( li )
  641.     {
  642.       li.setStyle( {position: cssPosition} );
  643.     });
  644.   });
  645. };
  646.  
  647. /**
  648.  * Class for controlling the course menu-collapser.  Also ensures the menu is
  649.  * the right height
  650.  */
  651. page.PageMenuToggler = Class.create();
  652. page.PageMenuToggler.prototype =
  653. {
  654.   /**
  655.    * initialize
  656.    */
  657.   initialize: function( isMenuOpen,key,temporaryScope )
  658.   {
  659.     page.PageMenuToggler.toggler = this;
  660.     this.key = key;
  661.     if (temporaryScope)
  662.     {
  663.       this.temporaryScope = temporaryScope;
  664.     }
  665.     else
  666.     {
  667.       this.temporaryScope = false;
  668.     }
  669.     this.isMenuOpen = isMenuOpen;
  670.     this.puller = $('puller');
  671.     this.menuPullerLink = $(this.puller.getElementsByTagName('a')[0]);
  672.     this.menuContainerDiv = $('menuWrap');
  673.     this.navigationPane = $('navigationPane');
  674.     this.contentPane = $('contentPanel') || $('contentPane');
  675.     this.navigationPane = $('navigationPane');
  676.     this.locationPane = $(this.navigationPane.parentNode);
  677.     this.breadcrumbBar = $('breadcrumbs');
  678.  
  679.     this.menu_pTop = parseInt(this.menuContainerDiv.getStyle('paddingTop'), 10);
  680.     this.menu_pBottom = parseInt(this.menuContainerDiv.getStyle('paddingBottom'), 10);
  681.     this.loc_pTop = parseInt(this.locationPane.getStyle('paddingTop'), 10);
  682.  
  683.     if ( this.breadcrumbBar )
  684.     {
  685.       this.bc_pTop = parseInt(this.breadcrumbBar.getStyle('paddingTop'), 10);
  686.       this.bc_pBottom = parseInt(this.breadcrumbBar.getStyle('paddingBottom'), 10);
  687.     }
  688.     else
  689.     {
  690.       this.bc_pTop = 0;
  691.       this.bc_pBottom = 0;
  692.     }
  693.  
  694.     this.toggleListeners = [];
  695.     this.onResize( null );  // fix the menu size
  696.  
  697.     // Doesn't work in IE or Safari..
  698.     //Event.observe( window, 'resize', this.onResize.bindAsEventListener( this ) );
  699.     Event.observe( this.menuPullerLink, 'click', this.onToggleClick.bindAsEventListener( this ) );
  700.   },
  701.  
  702.   /**
  703.    * Adds a listener for course menu toggle events
  704.    */
  705.   addToggleListener: function( listener )
  706.   {
  707.     this.toggleListeners.push( listener );
  708.   },
  709.  
  710.   /**
  711.    * Notifies all registered toggle event listeners that a toggle has occurred.
  712.    */
  713.   _notifyToggleListeners: function( isOpen )
  714.   {
  715.     this.toggleListeners.each( function( listener )
  716.     {
  717.       listener( isOpen );
  718.     });
  719.   },
  720.  
  721.   notifyToggleListeners: function( isOpen )
  722.   {
  723.     // we call once the toggle is complete and the DOM in its new state. 2012 themes add transition, which seems
  724.     // to collide with the logic to get dimensions of dom element, so the delay is a 1 sec to let time for those
  725.     // transitions to be done.
  726.     this._notifyToggleListeners.bind( this, isOpen ).delay( 1 );
  727.   },
  728.   /**
  729.    * getAvailableResponse
  730.    */
  731.   getAvailableResponse : function ( req  )
  732.   {
  733.     var originalMenuOpen = this.isMenuOpen ;
  734.     if ( req.responseText.length > 0 )
  735.     {
  736.       if ( req.responseText == 'true' )
  737.       {
  738.         this.isMenuOpen = true;
  739.       }
  740.       else
  741.       {
  742.         this.isMenuOpen = false;
  743.     }
  744.     }
  745.  
  746.     if ( originalMenuOpen != this.isMenuOpen )
  747.     {
  748.       this.notifyToggleListeners( this.isMenuOpen );
  749.       this.menuContainerDiv.toggle();
  750.       this.puller.toggleClassName("pullcollapsed");
  751.       this.contentPane.toggleClassName("contcollapsed");
  752.       this.navigationPane.toggleClassName("navcollapsed");
  753.     }
  754.   },
  755.  
  756.  
  757.  
  758.   /**
  759.    * Expands the menu.  This can be used instead of toggling to explicitly
  760.    * change the visibility of the menu.
  761.    */
  762.   expand : function ()
  763.   {
  764.     this.menuContainerDiv.show();
  765.     this.puller.removeClassName("pullcollapsed");
  766.     this.contentPane.removeClassName("contcollapsed");
  767.     this.navigationPane.removeClassName("navcollapsed");
  768.  
  769.     this.isMenuOpen = true;
  770.  
  771.     var msg = page.bundle.messages[ "coursemenu.hide" ];
  772.     this.menuPullerLink.title = msg;
  773.     $('expander').alt = msg;
  774.  
  775.     this.notifyToggleListeners( true );
  776.     if (this.temporaryScope)
  777.     {
  778.       UserDataDWRFacade.setStringTempScope( this.key, true );
  779.     }
  780.     else
  781.     {
  782.       UserDataDWRFacade.setStringPermScope( this.key, true );
  783.     }
  784.   },
  785.  
  786.   /**
  787.    * Collapses the menu.  This can be used instead of toggling to explicitly
  788.    * change the visibility of the menu.
  789.    */
  790.   collapse : function ()
  791.   {
  792.     this.menuContainerDiv.hide();
  793.     this.puller.addClassName("pullcollapsed");
  794.     this.contentPane.addClassName("contcollapsed");
  795.     this.navigationPane.addClassName("navcollapsed");
  796.  
  797.     this.isMenuOpen = false;
  798.  
  799.     var msg = page.bundle.messages[ "coursemenu.show" ];
  800.     this.menuPullerLink.title = msg;
  801.     $('expander').alt = msg;
  802.  
  803.     this.notifyToggleListeners( false );
  804.     if (this.temporaryScope)
  805.     {
  806.       UserDataDWRFacade.setStringTempScope( this.key, false );
  807.     }
  808.     else
  809.     {
  810.       UserDataDWRFacade.setStringPermScope( this.key, false );
  811.     }
  812.   },
  813.  
  814.   /**
  815.    * Event triggered when the puller toggle control is clicked.  Changes the
  816.    * menu from open to closed or closed to open depending on existing state.
  817.    */
  818.   onToggleClick: function( event )
  819.   {
  820.     if ( this.isMenuOpen )
  821.     {
  822.       this.collapse();
  823.     }
  824.     else
  825.     {
  826.       this.expand();
  827.     }
  828.     Event.stop( event );
  829.   },
  830.  
  831.   /**
  832.    * onResize
  833.    */
  834.   onResize: function( event )
  835.   {
  836.       var menuHeight = this.menuContainerDiv.getHeight();
  837.       var contentHeight = this.contentPane.getHeight();
  838.       var maxHeight = ( menuHeight > contentHeight ) ? menuHeight : contentHeight;
  839.       this.contentPane.setStyle({height: maxHeight + 'px'});
  840.       this.navigationPane.setStyle({height: maxHeight + 'px'});
  841.   }
  842. };
  843. page.PageMenuToggler.toggler = null;
  844.  
  845. /**
  846.  *  Class for controlling the page help toggler in the view toggle area
  847.  */
  848. page.PageHelpToggler = Class.create();
  849. page.PageHelpToggler.prototype =
  850. {
  851.   initialize: function( isHelpEnabled, showHelpText, hideHelpText, assumeThereIsHelp )
  852.   {
  853.     page.PageHelpToggler.toggler = this;
  854.     this.toggleListeners = [];
  855.     this.isHelpEnabled = isHelpEnabled;
  856.     this.showText = showHelpText;
  857.     this.hideText = hideHelpText;
  858.     this.contentPanel = $('contentPanel') || $('contentPane');
  859.     var helperList = [];
  860.     if ( this.contentPanel && !assumeThereIsHelp)
  861.     {
  862.       var allElems = [];
  863.       allElems = allElems.concat( $A(this.contentPanel.getElementsByTagName('p') ) );
  864.       allElems = allElems.concat( $A(this.contentPanel.getElementsByTagName('div') ) );
  865.       allElems = allElems.concat( $A(this.contentPanel.getElementsByTagName('li') ) );
  866.       allElems = allElems.concat( $A(this.contentPanel.getElementsByTagName('span') ) );
  867.       for ( var i = 0; i < allElems.length; i++ )
  868.       {
  869.         var el = allElems[i];
  870.         if ( page.util.hasClassName( el, 'helphelp' ) ||
  871.              page.util.hasClassName( el, 'stepHelp' ) ||
  872.              page.util.hasClassName( el, 'taskbuttonhelp' ) ||
  873.              page.util.hasClassName( el, 'pageinstructions' ) )
  874.         {
  875.           helperList.push( $(el) );
  876.         }
  877.       }
  878.     }
  879.  
  880.     var helpTextToggleLink = $('helpTextToggleLink');
  881.     if ( ( !helperList || helperList.length === 0) && !assumeThereIsHelp )
  882.     {
  883.       if ( helpTextToggleLink )
  884.       {
  885.         helpTextToggleLink.remove();
  886.       }
  887.     }
  888.     else
  889.     {
  890.       if ( !isHelpEnabled )
  891.       {
  892.         helperList.invoke( "toggle" );
  893.       }
  894.  
  895.       if ( !this.showText )
  896.       {
  897.         this.showText = page.bundle.getString("viewtoggle.editmode.showHelp");
  898.       }
  899.  
  900.       if ( !this.hideText )
  901.       {
  902.         this.hideText = page.bundle.getString("viewtoggle.editmode.hideHelp");
  903.       }
  904.  
  905.       helpTextToggleLink.style.display = 'inline-block';
  906.       this.toggleLink = helpTextToggleLink;
  907.       this.toggleImage = $(this.toggleLink.getElementsByTagName('img')[0]);
  908.       Event.observe( this.toggleLink, "click", this.onToggleClick.bindAsEventListener( this ) );
  909.       $(this.toggleLink.parentNode).removeClassName('hidden');
  910.       this.updateUI();
  911.     }
  912.   },
  913.  
  914.   addToggleListener: function( listener )
  915.   {
  916.     this.toggleListeners.push( listener );
  917.   },
  918.  
  919.   _notifyToggleListeners: function()
  920.   {
  921.     this.toggleListeners.each( function( listener )
  922.     {
  923.       listener( this.isHelpEnabled );
  924.     });
  925.   },
  926.  
  927.   notifyToggleListeners: function()
  928.   {
  929.     // we notify once the whole menu collapse/expand is done, so the DOM is in final state
  930.     this._notifyToggleListeners.bind( this ).delay( );
  931.   },
  932.  
  933.  
  934.   updateUI: function( )
  935.   {
  936.     if ( this.isHelpEnabled )
  937.     {
  938.       $("showHelperSetting").value = 'true';
  939.       this.toggleImage.src = "/images/ci/ng/small_help_on2.gif";
  940.       this.toggleLink.setAttribute( "title", this.showText );
  941.       this.toggleImage.setAttribute( "alt", this.showText );
  942.     }
  943.     else
  944.     {
  945.       $("showHelperSetting").value = 'false';
  946.       this.toggleImage.src = "/images/ci/ng/small_help_off2.gif";
  947.       this.toggleLink.setAttribute( "title", this.hideText );
  948.       this.toggleImage.setAttribute( "alt", this.hideText );
  949.     }
  950.   },
  951.  
  952.   onToggleClick: function( event )
  953.   {
  954.     // Toggle all elements that have the css class "helphelp"
  955.     var helperList = [];
  956.     if ( this.contentPanel )
  957.     {
  958.       var allElems = [];
  959.       allElems = allElems.concat( $A(this.contentPanel.getElementsByTagName('p') ) );
  960.       allElems = allElems.concat( $A(this.contentPanel.getElementsByTagName('div') ) );
  961.       allElems = allElems.concat( $A(this.contentPanel.getElementsByTagName('li') ) );
  962.       allElems = allElems.concat( $A(this.contentPanel.getElementsByTagName('span') ) );
  963.  
  964.       for ( var i = 0; i < allElems.length; i++ )
  965.       {
  966.         var el = allElems[i];
  967.         if ( page.util.hasClassName( el, 'helphelp' ) ||
  968.              page.util.hasClassName( el, 'stepHelp' ) ||
  969.              page.util.hasClassName( el, 'taskbuttonhelp' ) ||
  970.              page.util.hasClassName( el, 'pageinstructions' ) )
  971.         {
  972.           $(el).toggle();
  973.         }
  974.       }
  975.     }
  976.  
  977.     if ( this.isHelpEnabled )
  978.     {
  979.       this.isHelpEnabled = false;
  980.       UserPageInstructionsSettingDWRFacade.setShowPageInstructions( "false" );
  981.     }
  982.     else
  983.     {
  984.       this.isHelpEnabled = true;
  985.       UserPageInstructionsSettingDWRFacade.setShowPageInstructions( "true" );
  986.     }
  987.  
  988.     this.updateUI();
  989.     this.notifyToggleListeners();
  990.     Event.stop( event );
  991.   }
  992. };
  993.  
  994. /**
  995.  * Class for controlling the display of a context menu.
  996.  */
  997. page.ContextMenu = Class.create();
  998. page.ContextMenu.prototype =
  999. {
  1000.   initialize: function( contextMenuContainer, divId, forceMenuRefresh )
  1001.   {
  1002.     this.displayContextMenuLink = contextMenuContainer.down("a");
  1003.     this.contextMenuContainer = contextMenuContainer;
  1004.     this.forceMenuRefresh = forceMenuRefresh;
  1005.     this.uniqueId = this.displayContextMenuLink.id.split('_')[1];
  1006.     this.contextMenuDiv = this.displayContextMenuLink.savedDiv;
  1007.     if ( !this.contextMenuDiv )
  1008.     {
  1009.       this.contextMenuDiv = contextMenuContainer.down("div");//$('cmdiv_' + this.uniqueId);
  1010.       this.displayContextMenuLink.savedDiv = this.contextMenuDiv;
  1011.       page.ContextMenu.hiddenDivs.set(divId,this.contextMenuDiv);
  1012.     }
  1013.  
  1014.     this.originalContextMenuDiv = this.contextMenuDiv.cloneNode(true);
  1015.     $(this.contextMenuDiv).setStyle({zIndex: 200});
  1016.     this.displayContextMenuLink.appendChild( this.contextMenuDiv ); // Temporarily add the menu back where it started
  1017.     this.closeContextMenuLink = contextMenuContainer.down(".contextmenubar_top").down(0);
  1018.     this.contextParameters = contextMenuContainer.readAttribute("bb:contextParameters");
  1019.     this.menuGeneratorURL = contextMenuContainer.readAttribute("bb:menuGeneratorURL");
  1020.     this.nav = contextMenuContainer.readAttribute("bb:navItem");
  1021.     this.enclosingTableCell = contextMenuContainer.up("td");
  1022.     this.menuOrder = contextMenuContainer.readAttribute("bb:menuOrder");
  1023.     this.overwriteNavItems = contextMenuContainer.readAttribute("bb:overwriteNavItems");
  1024.     this.beforeShowFunc = contextMenuContainer.readAttribute("bb:beforeShowFunc");
  1025.     if (this.beforeShowFunc)
  1026.     {
  1027.       this.beforeShowFunc = eval(this.beforeShowFunc);
  1028.     }
  1029.  
  1030.     if ( this.menuOrder )
  1031.     {
  1032.       this.menuOrder = this.menuOrder.split(',');
  1033.     }
  1034.  
  1035.     if ( !this.contextParameters )
  1036.     {
  1037.       this.contextParameters = "";
  1038.     }
  1039.  
  1040.     if ( !this.menuGeneratorURL )
  1041.     {
  1042.       this.menuGeneratorURL = "";
  1043.     }
  1044.  
  1045.     if ( !this.nav )
  1046.     {
  1047.       this.nav = "";
  1048.     }
  1049.  
  1050.     this.dynamicMenu = false;
  1051.  
  1052.     if ( this.menuGeneratorURL )
  1053.     {
  1054.       this.dynamicMenu = true;
  1055.     }
  1056.  
  1057.     if (this.dynamicMenu)
  1058.     {
  1059.       Event.observe( this.displayContextMenuLink, "click", this.generateDynamicMenu.bindAsEventListener( this ) );
  1060.     }
  1061.     else
  1062.     {
  1063.       Event.observe( this.displayContextMenuLink, "click", this.onDisplayLinkClick.bindAsEventListener( this ) );
  1064.     }
  1065.  
  1066.     Event.observe( this.closeContextMenuLink, "click", this.onCloseLinkClick.bindAsEventListener( this ) );
  1067.     Event.observe( this.contextMenuDiv, "keydown", this.onKeyPress.bindAsEventListener( this ) );
  1068.  
  1069.     // adding nowrap to table cell containing context menu
  1070.     // If no enclosing td is found, try th
  1071.     if ( !this.enclosingTableCell )
  1072.     {
  1073.       this.enclosingTableCell = contextMenuContainer.up("th");
  1074.     }
  1075.  
  1076.     if ( this.enclosingTableCell )
  1077.     {
  1078.       if ( !this.enclosingTableCell.hasClassName("nowrapCell") )
  1079.       {
  1080.         this.enclosingTableCell.addClassName("nowrapCell");
  1081.       }
  1082.  
  1083.       // if label tag is an immediate parent of context menu span tag, it needs nowrap as well
  1084.       if ( this.enclosingTableCell.down("label") && !this.enclosingTableCell.down("label").hasClassName("nowrapLabel"))
  1085.       {
  1086.         this.enclosingTableCell.down("label").addClassName("nowrapLabel");
  1087.       }
  1088.     }
  1089.  
  1090.     if ( !this.dynamicMenu )
  1091.     {
  1092.       var contexMenuItems = contextMenuContainer.getElementsBySelector("li > a").each( function (link )
  1093.       {
  1094.         if ( !link.up('li').hasClassName("contextmenubar_top") )
  1095.         {
  1096.           Event.observe( link, 'focus', this.onAnchorFocus.bindAsEventListener( this ) );
  1097.           Event.observe( link, 'blur', this.onAnchorBlur.bindAsEventListener( this ) );
  1098.         }
  1099.       }.bind( this ) );
  1100.     }
  1101.  
  1102.     this.useARIA = page.util.useARIA();
  1103.  
  1104.     // remove the context menu div from the page for performance reasons - add it back when we need to show it
  1105.     Element.remove( this.contextMenuDiv );
  1106.   },
  1107.  
  1108.   onKeyPress: function( event )
  1109.   {
  1110.     var elem, children, index;
  1111.     var key = event.keyCode || event.which;
  1112.     if ( key == Event.KEY_UP )
  1113.     {
  1114.       elem = Event.element ( event );
  1115.       children = this.contextMenuDiv.getElementsBySelector("li > a");
  1116.       index = children.indexOf( elem );
  1117.       if ( index > 0 )
  1118.       {
  1119.         children[index - 1].focus();
  1120.       }
  1121.       Event.stop( event );
  1122.     }
  1123.     else if ( key == Event.KEY_DOWN )
  1124.     {
  1125.       elem = Event.element ( event );
  1126.       children = this.contextMenuDiv.getElementsBySelector("li > a");
  1127.       index = children.indexOf( elem );
  1128.       if ( index < ( children.length - 1 ) )
  1129.       {
  1130.         children[index + 1].focus();
  1131.       }
  1132.       Event.stop( event );
  1133.     }
  1134.     else if ( key == Event.KEY_ESC )
  1135.     {
  1136.       this.close();
  1137.       this.displayContextMenuLink.focus();
  1138.       Event.stop( event );
  1139.     }
  1140.     else if ( key == Event.KEY_TAB )
  1141.     {
  1142.       elem = Event.element ( event );
  1143.       children = this.contextMenuDiv.getElementsBySelector("li > a");
  1144.       index = children.indexOf( elem );
  1145.       if ( (!event.shiftKey && index == children.length - 1) || (event.shiftKey && index === 0))
  1146.       {
  1147.         this.close();
  1148.         this.displayContextMenuLink.focus();
  1149.         Event.stop( event );
  1150.       }
  1151.     }
  1152.     else if ( key == Event.KEY_RETURN )
  1153.     {
  1154.       if ( this.useARIA )
  1155.       {
  1156.         elem = Event.element ( event );
  1157.         (function() { page.util.fireClick( elem ); }.bind(this).defer());
  1158.         Event.stop( event );
  1159.       }
  1160.     }
  1161.   },
  1162.  
  1163.   onAnchorFocus: function ( event )
  1164.   {
  1165.     Event.element( event ).setStyle({ backgroundColor: '#FFFFFF' });
  1166.   },
  1167.  
  1168.   onAnchorBlur: function( event )
  1169.   {
  1170.     Event.element( event ).setStyle({ backgroundColor: '' });
  1171.   },
  1172.  
  1173.   afterMenuGeneration: function( req )
  1174.   {
  1175.     if ( this.dynamicMenu )
  1176.     {
  1177.       var result;
  1178.       this.dynamicMenu =  this.forceMenuRefresh;
  1179.       try
  1180.       {
  1181.         result = req.responseText.evalJSON( true );
  1182.         if ( result.success == "true" )
  1183.         {
  1184.           // append uniqueId to each li
  1185.           var menuHTML = result.contentMenuHTMLList.replace(/(<li.*?id=")(.*?)(".*?>)/g,"$1$2_"+this.uniqueId+"$3");
  1186.           if ( this.forceMenuRefresh )
  1187.           {
  1188.              this.contextMenuDiv.innerHTML = this.originalContextMenuDiv.innerHTML;
  1189.           }
  1190.           this.contextMenuDiv.insert({bottom:menuHTML});
  1191.           $A(this.contextMenuDiv.getElementsByTagName("ul")).each( function( list, index )
  1192.           {
  1193.             list.id = 'cmul'+index+'_'+this.uniqueId;
  1194.           }.bind(this) );
  1195.           var contexMenuItems = this.contextMenuDiv.getElementsBySelector("li > a").each( function (link )
  1196.           {
  1197.             if ( !link.up('li').hasClassName("contextmenubar_top") )
  1198.             {
  1199.               Event.observe( link, 'focus', this.onAnchorFocus.bindAsEventListener( this ) );
  1200.               Event.observe( link, 'blur', this.onAnchorBlur.bindAsEventListener( this ) );
  1201.              }
  1202.           }.bind( this ) );
  1203.         }
  1204.         else
  1205.         {
  1206.           new page.InlineConfirmation("error", result.errorMessage, false );
  1207.         }
  1208.       }
  1209.       catch ( e )
  1210.       {
  1211.          new page.InlineConfirmation("error", result.errorMessage, false );
  1212.       }
  1213.     }
  1214.  
  1215.     this.showMenu();
  1216.     //focus on the first menu item
  1217.     (function() { this.contextMenuDiv.down("a").focus(); }.bind(this).defer());
  1218.   },
  1219.  
  1220.   appendItems: function( items, menuItemContainer )
  1221.   {
  1222.     if (!menuItemContainer)
  1223.     {
  1224.       var uls = this.contextMenuDiv.getElementsBySelector("ul");
  1225.       menuItemContainer = uls[uls.length-1];
  1226.     }
  1227.  
  1228.     items.each( function ( item )
  1229.     {
  1230.       if ( item.type == "seperator" )
  1231.       {
  1232.         if (menuItemContainer.getElementsBySelector("li").length === 0)
  1233.         {
  1234.           return;
  1235.         }
  1236.         var ul = new Element('ul');
  1237.         menuItemContainer.parentNode.appendChild( ul );
  1238.         menuItemContainer = ul;
  1239.         return;
  1240.       }
  1241.       if ( !this.menuItemTempate )
  1242.       {
  1243.         var menuItems = this.contextMenuDiv.getElementsBySelector("li");
  1244.         this.menuItemTempate = menuItems[menuItems.length-1];
  1245.       }
  1246.       var mi = this.menuItemTempate.cloneNode( true );
  1247.       var a  =  mi.down('a');
  1248.       var name = item.key ? page.bundle.getString( item.key ) : item.name ? item.name : "?";
  1249.       a.update( name );
  1250.       a.title = item.title ? item.title : name;
  1251.       a.href = "#";
  1252.       menuItemContainer.appendChild( mi );
  1253.       Event.observe( a, 'focus', this.onAnchorFocus.bindAsEventListener( this ) );
  1254.       Event.observe( a, 'blur', this.onAnchorBlur.bindAsEventListener( this ) );
  1255.       Event.observe( a, 'click', this.onItemClick.bindAsEventListener( this, item.onclick, item.doNotSetFocusOnClick ) );
  1256.     }.bind( this ) );
  1257.  
  1258.   },
  1259.  
  1260.   onItemClick: function( evt, func, doNotSetFocusOnClick )
  1261.   {
  1262.     this.onCloseLinkClick( evt, doNotSetFocusOnClick );
  1263.     func();
  1264.   },
  1265.  
  1266.   setItems: function( items )
  1267.   {
  1268.     // rather than try to match up new items with existing items, it's easier to delete the existing items
  1269.     // (except for the close item) and then add the new items
  1270.  
  1271.     // remove existing menu items, except close menu
  1272.     var menuItems = this.contextMenuDiv.getElementsBySelector("li").each( function (li )
  1273.     {
  1274.       if ( !li.hasClassName("contextmenubar_top") )
  1275.       {
  1276.         if (!this.menuItemTempate)
  1277.         {
  1278.           this.menuItemTempate = li;
  1279.         }
  1280.         li.stopObserving();
  1281.         li.remove();
  1282.       }
  1283.     }.bind( this ) );
  1284.  
  1285.     // should only be one menuItemContainer
  1286.     var menuItemContainers = this.contextMenuDiv.getElementsBySelector("ul").each( function (ul)
  1287.     {
  1288.       if ( !ul.down("li") )
  1289.       {
  1290.         ul.remove();
  1291.       }
  1292.     }.bind( this ) );
  1293.  
  1294.     this.appendItems(items, menuItems[0].parentNode);
  1295.   },
  1296.  
  1297.   showMenu : function()
  1298.   {
  1299.     if (this.beforeShowFunc)
  1300.     {
  1301.       this.beforeShowFunc(this);
  1302.     }
  1303.     page.ContextMenu.registerContextMenu( this );
  1304.     this.reorderMenuItems();
  1305.     if ( this.useARIA )
  1306.     {
  1307.       this.initARIA();
  1308.     }
  1309.     var offset = this.displayContextMenuLink.cumulativeOffset();
  1310.     var scrollOffset = this.displayContextMenuLink.cumulativeScrollOffset();
  1311.     var viewportScrollOffset = document.viewport.getScrollOffsets();
  1312.     if ( this.displayContextMenuLink.up( 'div.lb-content' ) )
  1313.     {
  1314.       // Fix offset for context menu link inside a lightbox
  1315.       offset[0] = offset[0] + viewportScrollOffset[0];
  1316.       offset[1] = offset[1] + viewportScrollOffset[1];
  1317.     }
  1318.     else
  1319.     {
  1320.       // Fix the offset if the item is in a scrolled container
  1321.       offset[0] = offset[0] - scrollOffset[0] + viewportScrollOffset[0];
  1322.       offset[1] = offset[1] - scrollOffset[1] + viewportScrollOffset[1];
  1323.     }
  1324.     document.body.appendChild( this.contextMenuDiv );
  1325.     this.contextMenuDiv.setStyle({display: "block"});
  1326.     var width = this.contextMenuDiv.getWidth();
  1327.     var bodyWidth = $(document.body).getWidth();
  1328.  
  1329.     if ( page.util.isRTL() )
  1330.     {
  1331.       offset[0] = offset[0] + this.displayContextMenuLink.getWidth() - width;
  1332.     }
  1333.  
  1334.     if ( offset[0] + width > bodyWidth )
  1335.     {
  1336.       offset[0] = offset[0] - width + 30;
  1337.     }
  1338.  
  1339.     if ( this.keepMenuToRight )
  1340.     {
  1341.       // In case the link is very wide (i.e. gradecenter accessible mode cell link for really wide cell)
  1342.       // make sure the menu renders to the right side of the link
  1343.       var linkWidth = this.displayContextMenuLink.getDimensions().width;
  1344.       if (linkWidth > width)
  1345.       {
  1346.         // Only worry if the link is actually wider than the menu
  1347.         offset[0] += (linkWidth-width);
  1348.       }
  1349.     }
  1350.  
  1351.     // Don't start the menu off the left side of the window
  1352.     if ( offset[0] < 0 )
  1353.     {
  1354.       offset[0] = 0;
  1355.     }
  1356.  
  1357.     var height = this.contextMenuDiv.getHeight();
  1358.     var bodyHeight = $(document.body).getHeight();
  1359.     if (bodyHeight === 0)
  1360.     {
  1361.       // TODO This is kindof a hack since body height == 0 on a stream page, but we hacked in a special case for
  1362.       // lb-content above so it isn't entirely unheard of... would just be nicer to make this bodyheight choice
  1363.       // determined by the calling page rather than trial and error...
  1364.       var streamDiv = this.displayContextMenuLink.up( 'div.stream_full' );
  1365.       if (streamDiv)
  1366.       {
  1367.         bodyHeight = streamDiv.getHeight();
  1368.       }
  1369.     }
  1370.     var ypos = offset[1] + this.displayContextMenuLink.getHeight() + 17;
  1371.     if ( ( height + ypos ) > bodyHeight )
  1372.     {
  1373.       ypos -= height;
  1374.       ypos -= 34;
  1375.     }
  1376.     // Don't start the menu off the top of the screen
  1377.     if (ypos < 0 )
  1378.     {
  1379.       ypos = 0;
  1380.     }
  1381.     if (height > bodyHeight)
  1382.     {
  1383.       // If the menu is too big to fit on the screen, set it to the height of the screen and allow scrollbars inside the menu
  1384.       this.contextMenuDiv.setStyle({ height: bodyHeight + "px", overflowY: "auto", overflowX: "hidden", left: offset[0] + "px", top: ypos + "px" });
  1385.     }
  1386.     else
  1387.     {
  1388.       this.contextMenuDiv.setStyle({ left: offset[0] + "px", top: ypos + "px"});
  1389.     }
  1390.     if ( !this.shim )
  1391.     {
  1392.       this.shim = new page.popupShim( this.contextMenuDiv );
  1393.     }
  1394.     this.shim.open();
  1395.   },
  1396.  
  1397.   initARIA: function()
  1398.   {
  1399.     if ( !this.initializedARIA )
  1400.     {
  1401.       this.displayContextMenuLink.setAttribute( "aria-haspopup", "true" );
  1402.       this.displayContextMenuLink.setAttribute( "role", "menubutton" );
  1403.       this.contextMenuDiv.setAttribute( "role", "application" );
  1404.       this.contextMenuDiv.down( "ul" ).setAttribute( "role", "menu" );
  1405.       $A( this.contextMenuDiv.getElementsByTagName('a') ).each ( function( link )
  1406.       {
  1407.         link.setAttribute( "role", "menuitem" );
  1408.         link.parentNode.setAttribute( "role", "presentation" );
  1409.         if ( !link.href.include("#") )
  1410.         {
  1411.           Event.observe( link, 'click', function() {
  1412.             if ( this.ohref.toLowerCase().startsWith("javascript") )
  1413.             {
  1414.               eval( decodeURIComponent(this.ohref) );
  1415.             }
  1416.             else
  1417.             {
  1418.               if ( this.target )
  1419.               {
  1420.                 window.open( this.ohref, this.target );
  1421.               }
  1422.               else
  1423.               {
  1424.                 window.location = this.ohref;
  1425.               }
  1426.             }
  1427.           } );
  1428.           link.ohref = link.href;
  1429.           link.removeAttribute( "href" );
  1430.           link.tabIndex = "0";
  1431.           link.setStyle( {cursor: 'pointer'} ); // make it look like a link.
  1432.         }
  1433.       });
  1434.       this.initializedARIA = true; // Only initialize once.
  1435.     }
  1436.   },
  1437.  
  1438.   reorderMenuItems : function()
  1439.   {
  1440.     if ( !this.menuOrder || this.menuOrder.length < 2 )
  1441.     {
  1442.       return;
  1443.     }
  1444.  
  1445.     var orderMap = {};
  1446.     var closeItem = null;
  1447.     var extraItems = [];  // items not in order
  1448.  
  1449.     // Gather up all of the <li> tags in the menu and stick them in a map/object of id to the li object
  1450.     $A(this.contextMenuDiv.getElementsByTagName("li")).each( function( listItem )
  1451.     {
  1452.       if (listItem.hasClassName("contextmenubar_top"))
  1453.       {
  1454.         closeItem = listItem;
  1455.       }
  1456.       else
  1457.       {
  1458.         if (this.menuOrder.indexOf(listItem.id) > -1)
  1459.         {
  1460.           orderMap[listItem.id] = listItem;  // add item to map
  1461.         }
  1462.         else
  1463.         {
  1464.           extraItems.push(listItem); // listItem id not specified in menuOrder, so add listItem to extraItems
  1465.         }
  1466.       }
  1467.     }.bind(this) );
  1468.  
  1469.     // Remove all the content from the context menu div
  1470.     $A(this.contextMenuDiv.getElementsByTagName("ul")).each( function( list )
  1471.     {
  1472.       Element.remove(list);
  1473.     }.bind(this) );
  1474.  
  1475.     // Re-add the special "close" item as the first item.
  1476.     var ulElement = $(document.createElement("ul"));
  1477.     if ( this.useARIA )
  1478.     {
  1479.       ulElement.setAttribute('role','presentation');
  1480.     }
  1481.     this.contextMenuDiv.insert({bottom:ulElement});
  1482.     ulElement.insert({bottom:closeItem});
  1483.  
  1484.     // Loop through the order, adding a <ul> at the start, and starting a new <ul> whenever a "*separator*"
  1485.     //  is encountered, and adding the corresponding <li> for each of the ids in the order using the map/object
  1486.     this.menuOrder.each( function( id )
  1487.     {
  1488.       if (id == "*separator*")
  1489.       {
  1490.         ulElement = $(document.createElement("ul"));
  1491.         if ( this.useARIA )
  1492.         {
  1493.           ulElement.setAttribute('role','presentation');
  1494.         }
  1495.         this.contextMenuDiv.insert({bottom:ulElement});
  1496.       }
  1497.       else
  1498.       {
  1499.         ulElement.insert({bottom:orderMap[id]});
  1500.       }
  1501.     }.bind(this) );
  1502.  
  1503.  
  1504.     // Add any extraItems to thier own ul
  1505.     if (extraItems.length > 0)
  1506.     {
  1507.       ulElement = $(document.createElement("ul"));
  1508.       if ( this.useARIA )
  1509.       {
  1510.         ulElement.setAttribute('role','presentation');
  1511.       }
  1512.       this.contextMenuDiv.insert({bottom:ulElement});
  1513.       extraItems.each( function( lineItem )
  1514.       {
  1515.         ulElement.insert({bottom:lineItem});
  1516.       }.bind(this) );
  1517.     }
  1518.  
  1519.     // Remove any empty ULs and ensure that the added <ul>s have id of form "cmul${num}_${uniqueId}"
  1520.     $A(this.contextMenuDiv.getElementsByTagName("ul")).findAll( function( list )
  1521.     {
  1522.       if ( list.childElements().length === 0 )
  1523.       {
  1524.         list.remove(); return false;
  1525.       }
  1526.       else
  1527.       {
  1528.         return true;
  1529.       }
  1530.     }).each( function( list, index )
  1531.     {
  1532.       list.id = 'cmul'+index+'_'+this.uniqueId;
  1533.     }.bind(this) );
  1534.  
  1535.     this.menuOrder = null;  // only re-order once
  1536.   },
  1537.  
  1538.   generateDynamicMenu : function(event)
  1539.   {
  1540.     page.ContextMenu.closeAllContextMenus();
  1541.     if (this.dynamicMenu)
  1542.     {
  1543.       var context_parameters = this.contextParameters;
  1544.       var menu_generator_url = this.menuGeneratorURL;
  1545.       var nav = this.nav;
  1546.       var overwriteNavItems = this.overwriteNavItems;
  1547.  
  1548.       if ( context_parameters )
  1549.       {
  1550.         context_parameters = context_parameters.toQueryParams();
  1551.       }
  1552.       else
  1553.       {
  1554.         context_parameters = {};
  1555.       }
  1556.  
  1557.       var params = Object.extend({nav_item: nav }, context_parameters );
  1558.       params = Object.extend( params, { overwriteNavItems : overwriteNavItems } );
  1559.  
  1560.       new Ajax.Request(menu_generator_url,
  1561.       {
  1562.         method: 'post',
  1563.         parameters: params,
  1564.         onSuccess: this.afterMenuGeneration.bind( this )
  1565.       });
  1566.     }
  1567.     else
  1568.     {
  1569.       this.afterMenuGeneration(this);
  1570.     }
  1571.     $(event).preventDefault();
  1572.   },
  1573.  
  1574.   onDisplayLinkClick: function( event )
  1575.   {
  1576.     page.ContextMenu.closeAllContextMenus();
  1577.     if (this.dynamicMenu)
  1578.     {
  1579.      this.generateDynamicMenu(event);
  1580.      this.dynamicMenu = false;
  1581.     }
  1582.     else
  1583.     {
  1584.       this.showMenu();
  1585.       //focus on the first menu item
  1586.       (function() { if (this.contextMenuDiv.style.display != 'none') { this.contextMenuDiv.down("a").focus(); } }.bind(this).defer());
  1587.       $(event).preventDefault();
  1588.     }
  1589.   },
  1590.  
  1591.   onCloseLinkClick: function( event, doNotSetFocusOnClick )
  1592.   {
  1593.     this.close();
  1594.    
  1595.     var setFocusOnDisplayContextMenuLink = true;
  1596.    
  1597.     // grade center (in non-accessible mode) hides displayContextMenuLink onMouseOut, so we need to make sure it's doNotSetFocusOnClose flag is not set
  1598.     // before setting focus.
  1599.     if ( this.displayContextMenuLink.doNotSetFocusOnClose !== undefined && this.displayContextMenuLink.doNotSetFocusOnClose )
  1600.     {
  1601.       setFocusOnDisplayContextMenuLink = false;
  1602.     }
  1603.    
  1604.     // We may not want to set focus on displayContextMenuLink when one of the menu items (other than Close Menu) is clicked.
  1605.     // Initially this behavior was required for Grade Center Quick Comment of a grade in the grid (see getGradeContextMenuItems function in gradebookgrid_cellctrl.js)
  1606.     if ( doNotSetFocusOnClick !== undefined && doNotSetFocusOnClick )
  1607.     {
  1608.       setFocusOnDisplayContextMenuLink = false;
  1609.     }
  1610.    
  1611.     if ( setFocusOnDisplayContextMenuLink )
  1612.     {
  1613.       this.displayContextMenuLink.focus();
  1614.     }
  1615.     if (event)
  1616.     {
  1617.     Event.stop( event );
  1618.     }
  1619.   },
  1620.  
  1621.   close: function()
  1622.   {
  1623.     // Delay the removal of the element from the page so firefox will continue to process
  1624.     // the click on the menu item chosen (otherwise it stops processing as soon as we remove the
  1625.     // element resulting in the menu not actually working)
  1626.     (function() {
  1627.       this.closeNow();
  1628.     }.bind(this).delay(0.1));
  1629.   },
  1630.  
  1631.   closeNow: function()
  1632.   {
  1633.     if (this.contextMenuDiv.style.display != "none")
  1634.     {
  1635.       var links = this.contextMenuDiv.getElementsBySelector("li > a");
  1636.       links.each(function(link) {
  1637.         link.blur();
  1638.       });
  1639.       this.contextMenuDiv.style.display = "none";
  1640.       Element.remove( this.contextMenuDiv );
  1641.       if ( this.shim )
  1642.       {
  1643.         this.shim.close();
  1644.       }
  1645.     }
  1646.   }
  1647. };
  1648. /**
  1649.  * Function called to change the 'arrow' of a breadcrumb to face downward when they are clicked for the
  1650.  * contextual menu.
  1651.  * @param uniqId - unique number which identifies the crumb which was clicked
  1652.  * @param size - the size of the breadcrumb
  1653.  * @return
  1654.  */
  1655. page.ContextMenu.changeArrowInBreadcrumb = function (uniqId, event)
  1656. {
  1657.  
  1658.   page.ContextMenu.alignArrowsInBreadcrumb(event);
  1659.   $('arrowContext_'+uniqId).addClassName('contextArrowDown').removeClassName('contextArrow');
  1660.   //Stop the click event to propagate anymore -else all arrows will be aligned again
  1661.   Event.stop( event );
  1662.   return false;
  1663. };
  1664.  
  1665. //To align all breadcrumb arrows in one direction
  1666. page.ContextMenu.alignArrowsInBreadcrumb = function (event)
  1667. {
  1668.   if ($('breadcrumbs') !== null){
  1669.     var bList = $($('breadcrumbs').getElementsByTagName('ol')[0]);
  1670.     var bs = bList.immediateDescendants();
  1671.     if (bs.length !== null && bs.length >1){
  1672.       for (var i = 2; i <= bs.length; i++) {
  1673.         var arrowSpan = $('arrowContext_'+i);
  1674.         if (arrowSpan !== null ){
  1675.           $('arrowContext_'+i).addClassName('contextArrow').removeClassName('contextArrowDown');
  1676.         }
  1677.       }
  1678.     }
  1679.   }
  1680.  
  1681.   return false;
  1682. };
  1683.  
  1684. // "static" methods
  1685. page.ContextMenu.LI = function(event, divId, forceMenuRefresh)
  1686. {
  1687.   page.LazyInit(event,['focus','mouseover'],'new page.ContextMenu(page.util.upToClass(target,\'contextMenuContainer\'), \'' + divId + '\',' + forceMenuRefresh + ');');
  1688. };
  1689. page.ContextMenu.contextMenus = []; // _Open_ context menus
  1690. page.ContextMenu.registerContextMenu = function( menu )
  1691. {
  1692.   page.ContextMenu.contextMenus.push( menu );
  1693. };
  1694. page.ContextMenu.hiddenDivs = $H(); // All the menu divs on the page - only needed for cases such as view_spreadsheet2.js where we try to modify the menus outside this framework
  1695. page.ContextMenu.hideMenuDiv = function( uniqueId)
  1696. {
  1697.   var linkId = 'cmlink_' + uniqueId;
  1698.   var link = document.getElementById(linkId);
  1699.   if (link && !link.savedDiv ) {
  1700.     var elementId = 'cmdiv_' + uniqueId;
  1701.     var element = link.nextSibling; // Should be the text between the link and div but check anyways
  1702.     if ( !element || element.id != elementId)
  1703.     {
  1704.       element = element.nextSibling;
  1705.       if ( !element || element.id != elementId)
  1706.       {
  1707.         element = document.getElementById(elementId);
  1708.     }
  1709.     }
  1710.     if (element)
  1711.     {
  1712.       link.savedDiv = element;
  1713.       page.ContextMenu.hiddenDivs.set(uniqueId,element);
  1714.       Element.remove( element );
  1715.     }
  1716.   }
  1717. };
  1718. page.ContextMenu.addDivs = function()
  1719. {
  1720.   $H(page.ContextMenu.hiddenDivs).values().each(function(ele)
  1721.   {
  1722.     document.body.appendChild(ele);
  1723.   });
  1724. };
  1725.  
  1726. page.ContextMenu.removeDivs = function()
  1727. {
  1728.   $H(page.ContextMenu.hiddenDivs).values().each(function(ele)
  1729.   {
  1730.     Element.remove(ele);
  1731.   });
  1732. };
  1733.  
  1734. page.ContextMenu.closeAllContextMenus = function( event )
  1735. {
  1736.   var deferClose = false;
  1737.   if ( event )
  1738.   {
  1739.     var e = Event.findElement( event, 'a' );
  1740.     if ( e && e.href.indexOf("#contextMenu") >= 0 )
  1741.     {
  1742.       Event.stop( event );
  1743.       return;
  1744.     }
  1745.     deferClose = true;
  1746.   }
  1747.  
  1748.   page.ContextMenu.contextMenus.each( function( menu )
  1749.   {
  1750.     if ( menu != this )
  1751.     {
  1752.       if (deferClose) {
  1753.         menu.close();
  1754.       } else {
  1755.         menu.closeNow();
  1756.       }
  1757.     }
  1758.   });
  1759.   page.ContextMenu.contextMenus = [];
  1760. };
  1761.  
  1762. /**
  1763.  *  Enables flyout menus to be opened using a keyboard or mouse.  Enables
  1764.  *  them to be viewed properly in IE as well.
  1765.  */
  1766. page.FlyoutMenu = Class.create();
  1767. page.FlyoutMenu.prototype =
  1768. {
  1769.   initialize: function( subMenuListItem )
  1770.   {
  1771.     this.subMenuListItem = $(subMenuListItem);
  1772.     this.menuLink = $(subMenuListItem.getElementsByTagName('a')[0]);
  1773.     //special case to render iframe shim under new course content build menu
  1774.     if (this.subMenuListItem.hasClassName('bcContent'))
  1775.     {
  1776.       var buildContentDiv = this.subMenuListItem.down("div.flyout");
  1777.       if ( !buildContentDiv )
  1778.       {
  1779.         this.subMenu = $(subMenuListItem.getElementsByTagName('ul')[0]);
  1780.       }
  1781.       else
  1782.       {
  1783.         this.subMenu = buildContentDiv;
  1784.       }
  1785.     }
  1786.     else
  1787.     {
  1788.       this.subMenu = $(subMenuListItem.getElementsByTagName('ul')[0]);
  1789.     }
  1790.     this.menuLink.flyoutMenu = this;
  1791.  
  1792.     // calculate the next/previous tab stops
  1793.     this.previousSibling = this.subMenuListItem.previous();
  1794.     while ( this.previousSibling && (!this.previousSibling.down('a') || !this.previousSibling.visible()) )
  1795.     {
  1796.       this.previousSibling = this.previousSibling.previous();
  1797.     }
  1798.     this.nextSibling = this.subMenuListItem.next();
  1799.     while ( this.nextSibling && (!this.nextSibling.down('a') || !this.nextSibling.visible()) )
  1800.     {
  1801.       this.nextSibling = this.nextSibling.next();
  1802.     }
  1803.  
  1804.     var rumble = $(this.subMenuListItem.parentNode.parentNode);
  1805.     this.inListActionBar = rumble && ( rumble.hasClassName("rumble_top") || rumble.hasClassName("rumble") );
  1806.  
  1807.     Event.observe( this.menuLink, 'mouseover', this.onOpen.bindAsEventListener( this ) );
  1808.     Event.observe( subMenuListItem, 'mouseout', this.onClose.bindAsEventListener( this ) );
  1809.     Event.observe( this.menuLink, 'click', this.onLinkOpen.bindAsEventListener( this ) );
  1810.     Event.observe( this.subMenuListItem, 'keydown', this.onKeyPress.bindAsEventListener( this ) );
  1811.  
  1812.     $A( this.subMenu.getElementsByTagName('li') ).each ( function( li )
  1813.     {
  1814.       $A(li.getElementsByTagName('a')).each( function( link )
  1815.       {
  1816.         Event.observe( link, 'focus', this.onAnchorFocus.bindAsEventListener( this ) );
  1817.         Event.observe( link, 'blur', this.onAnchorBlur.bindAsEventListener( this ) );
  1818.         Event.observe( link, 'click', this.onLinkClick.bindAsEventListener( this, link ) );
  1819.       }.bind( this ) );
  1820.     }.bind( this ) );
  1821.  
  1822.     // ARIA menus currently don't work properly in IE8, JAWS consumes arrow up/down keys
  1823.     this.useARIA = page.util.useARIA() && !Prototype.Browser.IE;
  1824.     if ( this.useARIA )
  1825.     {
  1826.       this.initARIA();
  1827.     }
  1828.     this.enabled = true;
  1829.   },
  1830.  
  1831.   initARIA: function()
  1832.   {
  1833.     var inListActionBar = this.inListActionBar;
  1834.     if ( inListActionBar )
  1835.     {
  1836.       this.subMenuListItem.up('ul').setAttribute( "role", "menubar" );
  1837.     }
  1838.     this.subMenuListItem.setAttribute( "role", "menuitem" );
  1839.     this.subMenu.setAttribute( "role", "menu" );
  1840.     if ( !this.menuLink.hasClassName("notMenuLabel") )
  1841.     {
  1842.       this.subMenu.setAttribute( "aria-labelledby", this.menuLink.id );
  1843.     }
  1844.     $A( this.subMenu.getElementsByTagName('a') ).each ( function( link )
  1845.     {
  1846.       link.setAttribute( "role", "menuitem" );
  1847.       link.parentNode.setAttribute( "role", "presentation" );
  1848.       // List action bars have onclick handlers that prevent submission of the page
  1849.       // if no items are selected, so we can't register new onclicks here because
  1850.       // otherwise we can't stop them from executing.
  1851.       if ( !inListActionBar )
  1852.       {
  1853.         if ( !link.href.include("#") )
  1854.         {
  1855.           Event.observe( link, 'click', function() {
  1856.             if ( this.ohref.toLowerCase().startsWith("javascript") )
  1857.             {
  1858.               eval(decodeURIComponent(this.ohref) );
  1859.             }
  1860.             else
  1861.             {
  1862.               if ( this.target )
  1863.               {
  1864.                 window.open( this.ohref, this.target );
  1865.               }
  1866.               else
  1867.               {
  1868.                 window.location = this.ohref;
  1869.               }
  1870.             }
  1871.           } );
  1872.           link.ohref = link.href;
  1873.           link.removeAttribute( "href" );
  1874.           link.tabIndex = "-1";
  1875.           link.style.cursor = 'pointer'; // make it look like a link.
  1876.         }
  1877.       }
  1878.     });
  1879.  
  1880.   },
  1881.  
  1882.   setEnabled: function( enabled )
  1883.   {
  1884.     this.enabled = enabled;
  1885.     if ( !enabled )
  1886.     {
  1887.       this.subMenu.setStyle({ display: '' });
  1888.     }
  1889.   },
  1890.  
  1891.   onKeyPress: function( event )
  1892.   {
  1893.     if (!this.enabled)
  1894.     {
  1895.       return;
  1896.     }
  1897.     var key = event.keyCode || event.which;
  1898.     var elem = Event.element ( event );
  1899.     var children, index, link;
  1900.     if ( key == Event.KEY_UP )
  1901.     {
  1902.       children = this.subMenu.getElementsBySelector("li > a");
  1903.       index = children.indexOf( elem );
  1904.       if ( index > 0 )
  1905.       {
  1906.         children[index - 1].focus();
  1907.       }
  1908.       else if ( index === 0 )
  1909.       {
  1910.         children[children.length - 1].focus(); // wrap to bottom
  1911.       }
  1912.       Event.stop( event );
  1913.     }
  1914.     else if ( key == Event.KEY_DOWN )
  1915.     {
  1916.       children = this.subMenu.getElementsBySelector("li > a");
  1917.       index = children.indexOf( elem );
  1918.       if ( index == -1 )
  1919.       {
  1920.         this.open();
  1921.        (function() { this.subMenu.down("li > a").focus(); }.bind(this).defer());
  1922.       }
  1923.       else if ( index < ( children.length - 1 ) )
  1924.       {
  1925.         children[index + 1].focus();
  1926.       }
  1927.       else if ( index == ( children.length - 1 ) )
  1928.       {
  1929.         children[0].focus(); // wrap to top
  1930.       }
  1931.  
  1932.       Event.stop( event );
  1933.     }
  1934.     else if ( key == Event.KEY_LEFT )
  1935.     {
  1936.       if ( !this.previousSibling || ( this.previousSibling.hasClassName("mainButton") ||
  1937.                                   this.previousSibling.hasClassName("mainButtonType") ) )
  1938.       {
  1939.         this.executeTab( event, true, true );
  1940.       }
  1941.       else if ( this.previousSibling )
  1942.       {
  1943.         link = this.previousSibling.getElementsByTagName('a')[0];
  1944.         if ( !link || !this.previousSibling.hasClassName("sub") )
  1945.         {
  1946.           return;
  1947.         }
  1948.         this.close();
  1949.         page.util.fireClick( link );
  1950.         Event.stop( event );
  1951.       }
  1952.     }
  1953.     else if ( key == Event.KEY_RIGHT )
  1954.     {
  1955.       if ( !this.nextSibling || ( this.nextSibling.hasClassName("mainButton") ||
  1956.                               this.nextSibling.hasClassName("mainButtonType") ) )
  1957.       {
  1958.         this.executeTab( event, true, false );
  1959.       }
  1960.       else if ( this.nextSibling )
  1961.       {
  1962.         link = this.nextSibling.getElementsByTagName('a')[0];
  1963.         if ( !link || !this.nextSibling.hasClassName("sub") )
  1964.         {
  1965.           return;
  1966.         }
  1967.         this.close();
  1968.         page.util.fireClick( link );
  1969.         Event.stop( event );
  1970.       }
  1971.     }
  1972.     else if ( key == Event.KEY_ESC )
  1973.     {
  1974.       this.close();
  1975.       this.menuLink.focus();
  1976.       Event.stop( event );
  1977.     }
  1978.     else if ( key == Event.KEY_RETURN && this.useARIA && !this.inListActionBar )
  1979.     {
  1980.       page.util.fireClick( elem );
  1981.       Event.stop( event );
  1982.     }
  1983.     else if ( key == Event.KEY_TAB && this.useARIA )
  1984.     {
  1985.       this.executeTab( event, false, event.shiftKey );
  1986.     }
  1987.   },
  1988.  
  1989.   executeTab: function( event, forceMenuLinkTab, shift )
  1990.   {
  1991.     var elem = Event.element ( event );
  1992.     var link;
  1993.     if ( ( elem != this.menuLink ) || forceMenuLinkTab )
  1994.     {
  1995.       if ( shift )
  1996.       {
  1997.         // Go to previous menu
  1998.         if ( this.previousSibling )
  1999.         {
  2000.           link = this.previousSibling.getElementsByTagName('a')[0];
  2001.           if ( link ) { link.focus(); } else { this.menuLink.focus(); }
  2002.         }
  2003.         else
  2004.         {
  2005.           this.menuLink.focus();
  2006.         }
  2007.       }
  2008.       else
  2009.       {
  2010.         // Go to next menu
  2011.         if ( this.nextSibling )
  2012.         {
  2013.           link = this.nextSibling.getElementsByTagName('a')[0];
  2014.           if ( link ) { link.focus(); } else { this.menuLink.focus(); }
  2015.         }
  2016.         else
  2017.         {
  2018.           this.menuLink.focus();
  2019.         }
  2020.       }
  2021.  
  2022.       this.close();
  2023.       Event.stop( event );
  2024.     }
  2025.   },
  2026.  
  2027.   onOpen: function( event )
  2028.   {
  2029.     if (!this.enabled)
  2030.     {
  2031.       return;
  2032.     }
  2033.     this.open();
  2034.   },
  2035.  
  2036.   onClose: function( event )
  2037.   {
  2038.     var to = $(event.relatedTarget || event.toElement);
  2039.     if ( !to || to.up('li.sub') != this.subMenuListItem )
  2040.     {
  2041.       this.close();
  2042.     }
  2043.   },
  2044.  
  2045.   onLinkOpen: function( event )
  2046.   {
  2047.     if (!this.enabled)
  2048.     {
  2049.       return;
  2050.     }
  2051.     this.open();
  2052.     (function() { this.subMenu.down("li > a").focus(); }.bind(this).defer());
  2053.     Event.stop( event );
  2054.   },
  2055.  
  2056.   resizeAfterShowHide: function()
  2057.   {
  2058.   // TODO - ideally this would just resize the outer div, but closing and opening 'works'
  2059.   this.close();
  2060.   this.open();
  2061.   },
  2062.  
  2063.   open: function()
  2064.   {
  2065.     var alreadyShown = this.subMenu.getStyle('display') === 'block';
  2066.     // If the menu is already showing (i.e. as_ce4 theme, we don't need to position it)
  2067.     if ( !alreadyShown )
  2068.     {
  2069.       // Set position of action bar elements to static to enable z-index stack order
  2070.       page.util.setActionBarPosition( 'static' );
  2071.  
  2072.       var menuTop = this.subMenuListItem.getHeight();
  2073.       if ( this.subMenu.hasClassName( 'narrow' ) )
  2074.       {
  2075.         menuTop = 0;
  2076.       }
  2077.       this.subMenuListItem.setStyle( {position: 'relative'} );
  2078.       this.subMenu.setStyle(
  2079.       {
  2080.         display: 'block',
  2081.         zIndex: '999999',
  2082.         top: menuTop+'px',
  2083.         left: '0px',
  2084.         width: '',
  2085.         height: '',
  2086.         overflowY: ''
  2087.       });
  2088.       var offset = Position.cumulativeOffset( this.subMenuListItem );
  2089.       var menuDims = this.subMenu.getDimensionsEx();
  2090.       var menuHeight = menuDims.height;
  2091.       var popupWidth = this.subMenu.getWidth();
  2092.       var subListItemDims = this.subMenuListItem.getDimensions();
  2093.       var menuWidth = subListItemDims.width;
  2094.  
  2095.       var viewportDimensions = document.viewport.getDimensions();
  2096.       var scrollOffsets = document.viewport.getScrollOffsets();
  2097.  
  2098.       var offsetTop = offset[1] - scrollOffsets.top;
  2099.  
  2100.       this.subMenu.flyoutMenu = this;
  2101.  
  2102.       if ( (offsetTop + menuHeight + subListItemDims.height) > viewportDimensions.height)
  2103.       {
  2104.         if ( (offsetTop - menuHeight) > 0 )
  2105.         {
  2106.           // if menu goes below viewport but still fits on-page, show it above button
  2107.           this.subMenu.setStyle({ top: '-'+menuHeight+'px' });
  2108.         }
  2109.         else
  2110.         {
  2111.           // we need to create scrollbars
  2112.           var newWidth = this.subMenu.getWidth() + 15;
  2113.           popupWidth = newWidth + 5;
  2114.           var newMenuHeight = viewportDimensions.height - (offsetTop + subListItemDims.height) - 20;
  2115.           var newMenuTop = menuTop;
  2116.           if (newMenuHeight < offsetTop)
  2117.           {
  2118.             // More space above than below
  2119.             newMenuHeight = offsetTop;
  2120.             newMenuTop = -offsetTop;
  2121.           }
  2122.           this.subMenu.setStyle(
  2123.                                 {
  2124.                                   display: 'block',
  2125.                                   zIndex: '999999',
  2126.                                   top: newMenuTop+'px',
  2127.                                   left: '0px',
  2128.                                   width: newWidth + 'px',
  2129.                                   height: newMenuHeight + 'px',
  2130.                                   overflowY: 'auto'
  2131.                                 });
  2132.         }
  2133.       }
  2134.  
  2135.       var offsetLeft = offset[0] - scrollOffsets.left;
  2136.       if ( (offsetLeft + popupWidth) > viewportDimensions.width )
  2137.       {
  2138.         var subMenuWidth = this.subMenuListItem.getWidth();
  2139.         var newLeft = popupWidth - (viewportDimensions.width-offsetLeft);
  2140.         if ((newLeft > 0) && (newLeft < offsetLeft))
  2141.         {
  2142.           newLeft = -newLeft;
  2143.         }
  2144.         else
  2145.         {
  2146.           newLeft = -offsetLeft;
  2147.         }
  2148.         this.subMenu.setStyle({ left: newLeft+'px' });
  2149.       }
  2150.  
  2151.       if ( page.util.isRTL() )
  2152.       {
  2153.         var newRight = 0;
  2154.         if ( (offsetLeft + menuWidth) - popupWidth < 0 )
  2155.         {
  2156.           newRight = (offsetLeft + menuWidth) - popupWidth;
  2157.         }
  2158.         this.subMenu.setStyle({ left: '', right: newRight+'px'});
  2159.       }
  2160.  
  2161.       if (!this.shim)
  2162.       {
  2163.         this.shim = new page.popupShim( this.subMenu);
  2164.       }
  2165.  
  2166.       this.shim.open();
  2167.     }
  2168.   },
  2169.  
  2170.   close: function()
  2171.   {
  2172.     // Reset position of action bar elements to relative
  2173.     page.util.setActionBarPosition( 'relative' );
  2174.  
  2175.     this.subMenuListItem.setStyle({position: ''});
  2176.     this.subMenu.setStyle({
  2177.       display: '',
  2178.       top: '',
  2179.       left: '',
  2180.       width: '',
  2181.       height: '',
  2182.       overflowY: ''
  2183.     });
  2184.     if ( this.shim )
  2185.     {
  2186.       this.shim.close();
  2187.     }
  2188.   },
  2189.  
  2190.   onLinkClick: function( event, link )
  2191.   {
  2192.     if (!this.enabled)
  2193.     {
  2194.       return;
  2195.     }
  2196.     setTimeout( this.blurLink.bind( this, link), 100);
  2197.   },
  2198.  
  2199.   blurLink: function( link )
  2200.   {
  2201.     link.blur();
  2202.     if (page.util.hasClassName( link, "donotclose" ))
  2203.     {
  2204.       link.focus();
  2205.     }
  2206.     else
  2207.     {
  2208.       this.close();
  2209.     }
  2210.  
  2211.   },
  2212.  
  2213.   onAnchorFocus: function ( event )
  2214.   {
  2215.     if (!this.enabled)
  2216.     {
  2217.       return;
  2218.     }
  2219.     var link = Event.element( event );
  2220.     link.setStyle({ backgroundColor: '#FFFFFF' });
  2221.   },
  2222.  
  2223.   onAnchorBlur: function( event )
  2224.   {
  2225.     var link = Event.element( event );
  2226.     link.setStyle({ backgroundColor: '' });
  2227.   }
  2228. };
  2229.  
  2230. /**
  2231.  * Class for providing functionality to menu palettes
  2232.  */
  2233. page.PaletteController = Class.create();
  2234. page.PaletteController.prototype =
  2235. {
  2236.   /**
  2237.    * Constructor
  2238.    *
  2239.    * @param paletteIdStr        Unique string identifier for a palette
  2240.    * @param expandCollapseIdStr Id value of anchor tag to be assigned
  2241.    *                            the palette expand/collapse functionality
  2242.    * @param closeOtherPalettesWhenOpen Whether to close all other palettes when this one is open
  2243.    */
  2244.   initialize: function( paletteIdStr, expandCollapseIdStr, closeOtherPalettesWhenOpen, collapsed )
  2245.   {
  2246.     // palette id string
  2247.     this.paletteItemStr = paletteIdStr;
  2248.  
  2249.     // palette element
  2250.     this.paletteItem = $(this.paletteItemStr);
  2251.  
  2252.     // default id string to palette contents container element
  2253.     this.defaultContentsContainerId = page.PaletteController.getDefaultContentsContainerId(this.paletteItemStr);
  2254.  
  2255.     // the currently active palette contents container element
  2256.     this.activeContentsContainer = $(this.defaultContentsContainerId);
  2257.  
  2258.     // expand/collapse palette toggle element
  2259.     this.paletteToggle = $(expandCollapseIdStr);
  2260.  
  2261.     if (this.paletteToggle)
  2262.     {
  2263.       Event.observe(this.paletteToggle, 'click', this.toggleExpandCollapsePalette.bindAsEventListener(this));
  2264.     }
  2265.  
  2266.     this.closeOtherPalettesWhenOpen = closeOtherPalettesWhenOpen;
  2267.  
  2268.     page.PaletteController.registerPaletteBox(this);
  2269.     if (collapsed)
  2270.     {
  2271.       this.collapsePalette(true);
  2272.     }
  2273.   },
  2274.  
  2275.   /**
  2276.    * Set the currently active palette contents container element
  2277.    *
  2278.    * @param container palette contents container element
  2279.    */
  2280.   setActiveContentsContainer: function ( container )
  2281.   {
  2282.     this.activeContentsContainer = container;
  2283.   },
  2284.  
  2285.   /**
  2286.    * Get the currently active palette contents container element
  2287.    *
  2288.    * @return palette contents container element
  2289.    */
  2290.   getActiveContentsContainer: function ()
  2291.   {
  2292.     return this.activeContentsContainer;
  2293.   },
  2294.  
  2295.   /**
  2296.    * Expands the palette if it's not already expanded.
  2297.    *
  2298.    * @return palette contents container element
  2299.    */
  2300.   expandPalette: function ( doNotPersist )
  2301.   {
  2302.     var itemPalClass = [];
  2303.     itemPalClass = this.paletteItem.className.split(" ");
  2304.  
  2305.     var h2 = $(this.paletteItemStr+"_paletteTitleHeading");
  2306.     var expandCollapseLink = h2.getElementsByTagName('a')[0];
  2307.     if ( !this.useFirstTagForExpandCollapse( h2 ) )
  2308.     {
  2309.       expandCollapseLink = h2.getElementsByTagName('a')[1];
  2310.     }
  2311.  
  2312.     var itemList = this.activeContentsContainer;
  2313.  
  2314.     if ( itemList.style.display == "none" )
  2315.     {
  2316.       itemList.style.display = "block";
  2317.       itemPalClass.length = itemPalClass.length - 1;
  2318.       this.paletteItem.className = itemPalClass.join(" ");
  2319.       h2.className = "";
  2320.       var itemTitle = expandCollapseLink.innerHTML.stripTags().trim();
  2321.       if ( !this.useFirstTagForExpandCollapse( h2 ) )
  2322.       {
  2323.         itemTitle = h2.getElementsByTagName('a')[0].innerHTML.stripTags();
  2324.       }
  2325.       expandCollapseLink.title = page.bundle.getString('expandCollapse.collapse.section.param', itemTitle);
  2326.       expandCollapseLink.up().setAttribute("aria-expanded", "true");
  2327.     }
  2328.  
  2329.     if ( doNotPersist )
  2330.     {
  2331.       return;
  2332.     }
  2333.  
  2334.     this.saveSessionStickyInfo( itemList.id, itemList.style.display );
  2335.   },
  2336.  
  2337.   /**
  2338.    * Collapses the palette if it's not already collapsed.
  2339.    *
  2340.    * @return palette contents container element
  2341.    */
  2342.   collapsePalette: function ( doNotPersist )
  2343.   {
  2344.     var itemPalClass = [];
  2345.     itemPalClass = this.paletteItem.className.split(" ");
  2346.  
  2347.     // Note - h2 is actually a div, not an h2 :)
  2348.     var h2 = $(this.paletteItemStr+"_paletteTitleHeading");
  2349.     var expandCollapseLink = h2.getElementsByTagName('a')[0];
  2350.     if ( !this.useFirstTagForExpandCollapse( h2 ) )
  2351.     {
  2352.       expandCollapseLink = h2.getElementsByTagName('a')[1];
  2353.     }
  2354.  
  2355.     var itemList = this.activeContentsContainer;
  2356.  
  2357.     if ( itemList.style.display != "none" )
  2358.     {
  2359.       itemList.style.display = "none";
  2360.       itemPalClass[itemPalClass.length] = 'navPaletteCol';
  2361.       this.paletteItem.className = itemPalClass.join(" ");
  2362.  
  2363.       if (itemPalClass.indexOf('controlpanel') != -1)
  2364.       {
  2365.       }
  2366.  
  2367.       if (itemPalClass.indexOf('listCm')!=-1)
  2368.       {
  2369.         h2.className = "listCmCol"; // colors h2 background (removes background image)
  2370.       }
  2371.  
  2372.       if (itemPalClass.indexOf('tools') != -1)
  2373.       {
  2374.         h2.className = "toolsCol";
  2375.       }
  2376.       var itemTitle = expandCollapseLink.innerHTML.stripTags();
  2377.       if ( !this.useFirstTagForExpandCollapse( h2 ) )
  2378.       {
  2379.         itemTitle = h2.getElementsByTagName('a')[0].innerHTML.stripTags().trim();
  2380.       }
  2381.       expandCollapseLink.title = page.bundle.getString('expandCollapse.expand.section.param', itemTitle);
  2382.       expandCollapseLink.up().setAttribute("aria-expanded", "false");
  2383.     }
  2384.  
  2385.     if (doNotPersist)
  2386.     {
  2387.       return;
  2388.     }
  2389.  
  2390.     this.saveSessionStickyInfo( itemList.id, itemList.style.display );
  2391.   },
  2392.  
  2393.   /**
  2394.    * Takes in a key value pair to save to the session as sticky data.
  2395.    *
  2396.    * @param key The key that will have the current course id appended to it to be saved to the session.
  2397.    * @param value The value to the key.
  2398.    */
  2399.   saveSessionStickyInfo: function( key, value )
  2400.   {
  2401.     /* Get the course id off of the global variable if exists, so that data is saved per
  2402.      * user session per course. If course doesn't exist, use empty string.
  2403.      */
  2404.     var current_course_id = window.course_id ? window.course_id : "";
  2405.     UserDataDWRFacade.setStringTempScope( key + current_course_id, value );
  2406.   },
  2407.  
  2408.   /**
  2409.    * Whether the first tag has js onclick event binding on it for palette collapse/expand
  2410.    *
  2411.    * @param h2
  2412.    */
  2413.   useFirstTagForExpandCollapse: function ( h2 )
  2414.   {
  2415.     return h2.getElementsByTagName('a')[0].id.indexOf( "noneExpandCollapseTag" ) > -1 ? false : true;
  2416.   },
  2417.  
  2418.   /**
  2419.    * Toggles a palette from expand to collapse and vice versa.
  2420.    *
  2421.    * @param event Optional event object if this method was bound to event.
  2422.    */
  2423.   toggleExpandCollapsePalette: function ( event, doNotPersist )
  2424.   {
  2425.     // To prevent default event behavior
  2426.     if ( event )
  2427.     {
  2428.       Event.stop( event );
  2429.     }
  2430.  
  2431.     if ( this.activeContentsContainer.style.display == "none" )
  2432.     {
  2433.       // palette is currently closed, so we will be expanding it
  2434.       if ( this.closeOtherPalettesWhenOpen )
  2435.       {
  2436.         // if closeOtherPalettesWhenOpen is set to true for this palette, close all other palettes
  2437.         page.PaletteController.closeAllOtherPalettes(this.paletteItemStr, doNotPersist);
  2438.       }
  2439.       this.expandPalette( doNotPersist );
  2440.     }
  2441.     else
  2442.     {
  2443.       // palette is currently expanded, so we will be collapsing it
  2444.       this.collapsePalette( doNotPersist );
  2445.     }
  2446.   }
  2447. };
  2448.  
  2449. // "static" methods
  2450.  
  2451. page.PaletteController.paletteBoxes = [];
  2452. page.PaletteController.registerPaletteBox = function( paletteBox )
  2453. {
  2454.   page.PaletteController.paletteBoxes.push( paletteBox );
  2455. };
  2456.  
  2457. /**
  2458.  * Get the palette controller js object by palette id
  2459.  *
  2460.  * @param paletteId
  2461.  */
  2462. page.PaletteController.getPaletteControllerObjById = function( paletteId )
  2463. {
  2464.   return page.PaletteController.paletteBoxes.find( function( pb )
  2465.          { return ( pb.paletteItemStr == paletteId ); } );
  2466. };
  2467.  
  2468.  
  2469. /**
  2470.  * Closes all palettes except the specified one
  2471.  *
  2472.  * @param paletteToKeepOpen
  2473.  */
  2474. page.PaletteController.closeAllOtherPalettes = function( paletteToKeepOpen, doNotPersist )
  2475. {
  2476.   for(var i = 0; i < page.PaletteController.paletteBoxes.length; i++)
  2477.   {
  2478.     var paletteItem = page.PaletteController.paletteBoxes[i];
  2479.     if (paletteToKeepOpen !== paletteItem.paletteItemStr)
  2480.     {
  2481.       paletteItem.collapsePalette( doNotPersist );
  2482.     }
  2483.   }
  2484. };
  2485.  
  2486. /**
  2487.  * Toggles (expand/collapse) the contents of a nav palette by palette id
  2488.  *
  2489.  * @param paletteId
  2490.  * @param doNotPersist - optional param to suppress persisting state, default is to persist
  2491.  */
  2492. page.PaletteController.toggleExpandCollapsePalette = function( paletteId, doNotPersist )
  2493. {
  2494.   var paletteObj = page.PaletteController.getPaletteControllerObjById( paletteId );
  2495.   paletteObj.toggleExpandCollapsePalette( null, doNotPersist);
  2496. };
  2497.  
  2498.  
  2499. /**
  2500.  * Collapses the contents of a nav palette by palette id
  2501.  *
  2502.  * @param paletteId
  2503.  * @param doNotPersist - optional param to suppress persisting state, default is to persist
  2504.  */
  2505. page.PaletteController.collapsePalette = function( paletteId, doNotPersist )
  2506. {
  2507.   var paletteObj = page.PaletteController.getPaletteControllerObjById( paletteId );
  2508.   paletteObj.collapsePalette( doNotPersist);
  2509. };
  2510.  
  2511.  
  2512. /**
  2513.  * Expand the contents of a nav palette by palette id
  2514.  *
  2515.  * @param paletteId
  2516.  * @param doNotPersist - optional param to suppress persisting state, default is to persist
  2517.  */
  2518. page.PaletteController.expandPalette = function( paletteId, doNotPersist )
  2519. {
  2520.   var paletteObj = page.PaletteController.getPaletteControllerObjById( paletteId );
  2521.   paletteObj.expandPalette( doNotPersist);
  2522. };
  2523.  
  2524.  
  2525. /**
  2526.  * Set the active palette contents container (element containing the body
  2527.  * contents of a palette). The active contents container is used to toggle
  2528.  * visibility when expanding and collapsing menu palettes.
  2529.  *
  2530.  * @param paletteId
  2531.  * @param paletteContentsContainer Optional container to set.
  2532.  *                                 If not given, the palette's active
  2533.  *                                 container will not be changed.
  2534.  * @return The new active palette contents container element.
  2535.  *         If no paletteContentsContainer element was passed,
  2536.  *         The current active palette contents container element
  2537.  *         will be returned.
  2538.  */
  2539. page.PaletteController.setActivePaletteContentsContainer = function( paletteId, paletteContentsContainer )
  2540. {
  2541.   var paletteObj = page.PaletteController.getPaletteControllerObjById( paletteId );
  2542.   if ( paletteContentsContainer )
  2543.   {
  2544.     paletteObj.setActiveContentsContainer( paletteContentsContainer );
  2545.   }
  2546.   return paletteObj.getActiveContentsContainer();
  2547. };
  2548.  
  2549. /*
  2550.  * Get the default palette contents container id string
  2551.  *
  2552.  * @param paletteId
  2553.  */
  2554. page.PaletteController.getDefaultContentsContainerId = function( paletteId )
  2555. {
  2556.   return paletteId + "_contents";
  2557. };
  2558.  
  2559.  
  2560. /**
  2561.  * Class for providing expand/collapse functionality (with dynamic loading)
  2562.  */
  2563. page.ItemExpander = Class.create();
  2564. page.ItemExpander.prototype =
  2565. {
  2566.   /**
  2567.    * Constructor
  2568.    * - expandLink - the link that when clicked will expand/collapse the item
  2569.    * - expandArea - the actual area that will get expanded/collapsed (if the item is dynamically loaded, this area will be populated dynamically)
  2570.    * - expandText - the text to show as a tooltip on the link for expanding
  2571.    * - collapseText - the text to show as a tooltip on the link for collapsing
  2572.    * - expandTitleText - the customized text for link title afer expanding the item; if null/undefined, use expandText
  2573.    * - collapseTitleText - the customized text for link title after collapsing the item;if null/undefined, use collapseText
  2574.    * - dynamic - whether the contents are dynamically loaded
  2575.    * - dynamicUrl - the URL to get the contents of the item from
  2576.    * - contextParameters - additional URL parameters to add when calling the dynamicUrl
  2577.    * - sticky - load/save expand state from UserData; true if null/undefined
  2578.    * - expanded - initially expanded; false if null/undefined
  2579.    */
  2580.   initialize: function( expandLink, expandArea, expandText, collapseText, dynamic, dynamicUrl, contextParameters, expandTitleText, collapseTitleText, sticky, expanded )
  2581.   {
  2582.     this.expandLink = $(expandLink);
  2583.     this.expandArea = $s(expandArea);
  2584.     // Register the expander so it can be found
  2585.     page.ItemExpander.itemExpanderMap[this.expandLink.id] = this;
  2586.     this.expandText = expandText.unescapeHTML();
  2587.     this.collapseText = collapseText.unescapeHTML();
  2588.     if ( expandTitleText !== null && expandTitleText !== undefined )
  2589.     {
  2590.       this.expandTitleText = expandTitleText.unescapeHTML();
  2591.     }
  2592.     else
  2593.     {
  2594.       this.expandTitleText = this.expandText;
  2595.     }
  2596.     if ( collapseTitleText !== null && collapseTitleText !== undefined )
  2597.     {
  2598.       this.collapseTitleText = collapseTitleText.unescapeHTML();
  2599.     }
  2600.     else
  2601.     {
  2602.       this.collapseTitleText = this.collapseText;
  2603.     }
  2604.     this.dynamic = dynamic;
  2605.     this.dynamicUrl = dynamicUrl;
  2606.  
  2607.     if ( contextParameters !== null && contextParameters !== undefined )
  2608.     {
  2609.       this.contextParameters = contextParameters.toQueryParams();
  2610.     }
  2611.     else
  2612.     {
  2613.       this.contextParameters = {};
  2614.     }
  2615.  
  2616.     this.sticky = ( sticky !== null && sticky !== undefined ) ? sticky : true;
  2617.     this.expanded = ( expanded !== null && expanded !== undefined ) ? expanded : false;
  2618.     this.hasContents = !this.dynamic;
  2619.  
  2620.     if ( this.sticky )
  2621.     {
  2622.       // get the course id off of the global variable if exists, because data is saved per user session per course
  2623.       var current_course_id = ( (typeof course_id != "undefined") && course_id !== null ) ? course_id : "";
  2624.       UserDataDWRFacade.getStringTempScope( this.expandLink.id + current_course_id, this.getAvailableResponse.bind( this ) );
  2625.     }
  2626.     this.expandCollapse( !this.expanded );
  2627.     Event.observe( this.expandLink, "click", this.onToggleClick.bindAsEventListener( this ) );
  2628.   },
  2629.  
  2630.   getAvailableResponse : function ( response  )
  2631.   {
  2632.     var originalExpanded = this.expanded ;
  2633.     var cachedExpanded = false;
  2634.     if ( response.length > 0 )
  2635.     {
  2636.       if ( response == 'true' )
  2637.       {
  2638.         cachedExpanded = true;
  2639.       }
  2640.       else
  2641.       {
  2642.         cachedExpanded = false;
  2643.     }
  2644.     }
  2645.  
  2646.     if ( originalExpanded != cachedExpanded )
  2647.     {
  2648.       //because we want the menu to be in the cached state,
  2649.       //we pass in the opposite so that expandCollapse changes the menu state.
  2650.       this.expandCollapse(originalExpanded);
  2651.     }
  2652.   },
  2653.  
  2654.   onToggleClick: function( event )
  2655.   {
  2656.     if ( event )
  2657.     {
  2658.       Event.stop( event );
  2659.     }
  2660.  
  2661.     this.expandCollapse(this.expanded);
  2662.  
  2663.     if ( this.sticky )
  2664.     {
  2665.       // get the course id off of the global variable if exists, so that data is saved per user session per course
  2666.       var current_course_id = ( (typeof course_id != "undefined") && course_id !== null ) ? course_id : "";
  2667.       UserDataDWRFacade.setStringTempScope( this.expandLink.id + current_course_id, this.expanded );
  2668.     }
  2669.   },
  2670.  
  2671.   expandCollapse: function(shouldCollapse)
  2672.   {
  2673.     var combo;
  2674.     if ( shouldCollapse ) //Collapse the item
  2675.     {
  2676.       $(this.expandArea).hide();
  2677.       this.expandLink.title = this.expandTitleText;
  2678.       this.expandLink.up().setAttribute("aria-expanded", "false");
  2679.       if ( this.expandLink.hasClassName("comboLink_active") )
  2680.       {
  2681.         combo = this.expandLink.up("li").down(".submenuLink_active");
  2682.         this.expandLink.removeClassName("comboLink_active");
  2683.         this.expandLink.addClassName("comboLink");
  2684.         if ( combo )
  2685.         {
  2686.           combo.removeClassName("submenuLink_active");
  2687.           combo.addClassName("submenuLink");
  2688.         }
  2689.       }
  2690.       else
  2691.       {
  2692.         this.expandLink.removeClassName("open");
  2693.       }
  2694.       this.expanded = false;
  2695.     }
  2696.     else //Expand the item
  2697.     {
  2698.       if ( this.hasContents )
  2699.       {
  2700.         $(this.expandArea).setStyle({ zoom: 1 });
  2701.         this.expandArea.show();
  2702.         this.expandLink.title = this.collapseTitleText;
  2703.         this.expandLink.up().setAttribute("aria-expanded", "true");
  2704.         if ( this.expandLink.hasClassName("comboLink") )
  2705.         {
  2706.           combo = this.expandLink.up("li").down(".submenuLink");
  2707.           this.expandLink.removeClassName("comboLink");
  2708.           this.expandLink.addClassName("comboLink_active");
  2709.           if ( combo )
  2710.           {
  2711.             combo.removeClassName("submenuLink");
  2712.             combo.addClassName("submenuLink_active");
  2713.           }
  2714.         }
  2715.         else
  2716.         {
  2717.           this.expandLink.addClassName("open");
  2718.         }
  2719.       }
  2720.       else if ( this.dynamic )
  2721.       {
  2722.         this.loadData();
  2723.       }
  2724.  
  2725.       this.expanded = true;
  2726.     }
  2727.   },
  2728.  
  2729.   loadData: function()
  2730.   {
  2731.     new Ajax.Request( this.dynamicUrl,
  2732.     {
  2733.       method: "post",
  2734.       parameters: this.contextParameters,
  2735.       requestHeaders: { cookie: document.cookie },
  2736.       onSuccess: this.afterLoadData.bind( this )
  2737.     });
  2738.   },
  2739.  
  2740.   afterLoadData: function( req )
  2741.   {
  2742.     try
  2743.     {
  2744.       var result = req.responseText.evalJSON( true );
  2745.       if ( result.success != "true" )
  2746.       {
  2747.         new page.InlineConfirmation("error", result.errorMessage, false );
  2748.       }
  2749.       else
  2750.       {
  2751.         this.hasContents = true;
  2752.         this.expandArea.innerHTML = result.itemContents;
  2753.         $(this.expandArea).setStyle({ zoom: 1 });
  2754.         this.expandArea.show();
  2755.         this.expandLink.title = this.collapseTitleText;
  2756.         this.expandLink.up().setAttribute("aria-expanded", "true");
  2757.         if ( this.expandLink.hasClassName("comboLink") )
  2758.         {
  2759.           var combo = this.expandLink.up("li").down(".submenuLink");
  2760.           this.expandLink.removeClassName("comboLink");
  2761.           this.expandLink.addClassName("comboLink_active");
  2762.           if ( combo )
  2763.           {
  2764.             combo.removeClassName("submenuLink");
  2765.             combo.addClassName("submenuLink_active");
  2766.           }
  2767.         }
  2768.         else
  2769.         {
  2770.           this.expandLink.addClassName("open");
  2771.         }
  2772.         this.expanded = true;
  2773.       }
  2774.     }
  2775.     catch ( e )
  2776.     {
  2777.       //Invalid response
  2778.     }
  2779.   }
  2780. };
  2781. page.ItemExpander.itemExpanderMap = {};
  2782.  
  2783. /**
  2784.  * Class for controlling the "breadcrumb expansion" (i.e. the "..." hiding the inner
  2785.  * breadcrumbs)
  2786.  */
  2787. page.BreadcrumbExpander = Class.create();
  2788. page.BreadcrumbExpander.prototype =
  2789. {
  2790.   initialize: function( breadcrumbBar )
  2791.   {
  2792.     var breadcrumbListElement = $(breadcrumbBar.getElementsByTagName('ol')[0]);
  2793.     var breadcrumbs = breadcrumbListElement.immediateDescendants();
  2794.     if ( breadcrumbs.length > 4 )
  2795.     {
  2796.       this.ellipsis = document.createElement("li");
  2797.       var ellipsisLink = document.createElement("a");
  2798.       ellipsisLink.setAttribute("href", "#");
  2799.       ellipsisLink.setAttribute("title", page.bundle.getString('breadcrumbs.expand') );
  2800.       ellipsisLink.innerHTML = "...";
  2801.       this.ellipsis.appendChild( ellipsisLink );
  2802.       this.ellipsis = Element.extend( this.ellipsis );
  2803.       Event.observe( ellipsisLink, "click", this.onEllipsisClick.bindAsEventListener( this ) );
  2804.       this.hiddenItems = $A(breadcrumbs.slice(2,breadcrumbs.length - 2));
  2805.       breadcrumbListElement.insertBefore( this.ellipsis, this.hiddenItems[0] );
  2806.       this.hiddenItems.invoke( "hide" );
  2807.     }
  2808.  
  2809.     // Make sure the breadcrumbs don't run into the mode switcher
  2810.     var breadcrumbContainer = $(breadcrumbListElement.parentNode);
  2811.     var modeSwitcher = breadcrumbBar.down('.modeSwitchWrap');
  2812.     if ( modeSwitcher )
  2813.     {
  2814.       var containerWidth = breadcrumbContainer.getWidth();
  2815.       var containerOffset = breadcrumbContainer.cumulativeOffset();
  2816.       var modeSwitcherOffset = modeSwitcher.cumulativeOffset();
  2817.       var modeSwitcherWidth = modeSwitcher.getWidth();
  2818.       if ( page.util.isRTL() )
  2819.       {
  2820.         if ( modeSwitcherOffset[0] + modeSwitcherWidth > containerOffset[0] )
  2821.         {
  2822.           breadcrumbContainer.setStyle({ paddingLeft: ( modeSwitcherOffset[0] + modeSwitcherWidth ) + 'px'} );
  2823.         }
  2824.       }
  2825.      // else
  2826.       //{
  2827.        // breadcrumbContainer.setStyle({ paddingRight: ( containerWidth - ( modeSwitcherOffset[0] - containerOffset[0] ) ) + 'px'} );
  2828.       //}
  2829.     }
  2830.   },
  2831.  
  2832.   onEllipsisClick: function( event )
  2833.   {
  2834.     this.hiddenItems.invoke( "show" );
  2835.     this.ellipsis.hide();
  2836.     Event.stop( event );
  2837.   }
  2838. };
  2839.  
  2840. /**
  2841.  * Dynamically creates an inline confirmation.
  2842.  */
  2843. page.InlineConfirmation = Class.create();
  2844. page.InlineConfirmation.prototype =
  2845. {
  2846.   initialize: function( type, message, showRefreshLink, oneReceiptPerPage )
  2847.   {
  2848.     var receiptId = $s('receipt_id');
  2849.     // do not insert a duplicate receipt, if one already exists
  2850.     if(receiptId && oneReceiptPerPage)
  2851.     {
  2852.      return;
  2853.     }
  2854.     var cssClass = "bad";
  2855.     if ( type == "success" )
  2856.     {
  2857.       cssClass = "good";
  2858.     }
  2859.     else if ( type == "warning" )
  2860.     {
  2861.       cssClass = "warningReceipt";
  2862.     }
  2863.     var contentPane = $('contentPanel') || $('portalPane');
  2864.     var receiptHtml = '<div id="receipt_id" class="receipt '+ cssClass +'">'+
  2865.                       '<span class="inlineReceiptSpan" tabindex="-1" style="color:#FFFFFF">'+message+'</span>';
  2866.     if ( showRefreshLink )
  2867.     {
  2868.       receiptHtml += ' <a href="#refresh" onClick="document.location.href = document.location.href; return false;">' + page.bundle.getString("inlineconfirmation.refresh") + '</a>';
  2869.     }
  2870.     receiptHtml += '<a class="close" href="#close" title="'+ page.bundle.getString("inlineconfirmation.close") +'" onClick="Element.remove( $(this).up(\'div.receipt\') ); return false;"><img alt="'+ page.bundle.getString("inlineconfirmation.close") +'" src="/images/ci/ng/close_mini.gif"></a></div>';
  2871.     contentPane.insert({top:receiptHtml});
  2872.     // use aria live region to announce this confirmation message rather than setting focus to it. (Too many things are fighting over setting focus)
  2873.     // Note: if this confirmation is invoked from a menu handler, it may not announce if focus is lost when the menu closes. See how courseTheme.js sets focus before invoking.
  2874.     var insertedA = contentPane.down('span.inlineReceiptSpan');
  2875.     insertedA.setAttribute("aria-live","assertive");
  2876.     insertedA.parentNode.setAttribute("role","application");
  2877.     (function() { insertedA.update( insertedA.innerHTML ); }.defer(2));  // update live region so it is announced
  2878.   }
  2879. };
  2880.  
  2881. page.NestedInlineConfirmationIdCounter = 0;
  2882. page.NestedInlineConfirmation = Class.create();
  2883. page.NestedInlineConfirmation.prototype =
  2884. {
  2885.   initialize: function( type, message, showRefreshLink, previousElement,showCloseLink, extracss, insertBefore, oneReceiptPerPage, fadeAway, focusDiv, fadingTime, insertTop, receiptDivId, focusOnRender )
  2886.   {
  2887.     if ( Object.isUndefined( focusOnRender ) )
  2888.     {
  2889.       focusOnRender = true;
  2890.     }
  2891.    var receiptId = $s('receipt_nested_id');
  2892.     // do not insert a duplicate receipt, if one already exists
  2893.    var newDivId = 'receipt_nested_id';
  2894.     if(receiptId)
  2895.     {
  2896.       if (oneReceiptPerPage)
  2897.       {
  2898.         return;
  2899.       }
  2900.       newDivId = newDivId + (page.NestedInlineConfirmationIdCounter++);
  2901.     }
  2902.    
  2903.     // if receiptDivId is provided, we are explicitly using it as the id for the new receipt replacing the existing receipt with the same id
  2904.     if (receiptDivId)
  2905.     {
  2906.       // Remove the old message with the same receiptDivId if there is one before adding a new one
  2907.       if ( $( receiptDivId ) != null )
  2908.       {
  2909.         $( receiptDivId ).remove();
  2910.       }
  2911.       newDivId = receiptDivId;
  2912.     }
  2913.  
  2914.     var cssClass = "bad";
  2915.     if ( type == "success" )
  2916.     {
  2917.       cssClass = "good";
  2918.     }
  2919.     else if (type == "warning")
  2920.     {
  2921.       cssClass = "warningReceipt";
  2922.     }
  2923.  
  2924.     if (!extracss)
  2925.     {
  2926.       extracss = "";
  2927.     }
  2928.  
  2929.     var arrowSpan = '';
  2930.     if (extracss.indexOf( "point", 0 ) != -1)
  2931.     {
  2932.       arrowSpan = '<span class="arrow"></span>';
  2933.     }
  2934.  
  2935.     var contentPane = $(previousElement);
  2936.     if (!contentPane)
  2937.     {
  2938.       // TODO - if we can't find the element we wanted to insert before, is it OK to just drop the notification?
  2939.       return;
  2940.     }
  2941.     var receiptHtml = '<div id="'+newDivId+'" style="display:none" class="receipt '+ cssClass +' '+extracss +'">'+arrowSpan+
  2942.                       '<span class="inlineReceiptSpan areceipt" tabindex="-1">'+message+'</span>';
  2943.     if ( showRefreshLink )
  2944.     {
  2945.       receiptHtml += ' <a href="#refresh" onClick="document.location.href = document.location.href; return false;">' + page.bundle.getString("inlineconfirmation.refresh") + '</a>';
  2946.     }
  2947.  
  2948.     if (showCloseLink)
  2949.     {
  2950.       // either this is a JS Snippet to execute on close or a simple true in which case we do nothing extra
  2951.       var onCloseFunction = "";
  2952.       if ( typeof showCloseLink === "string" || showCloseLink instanceof String )
  2953.       {
  2954.         if ( !page.closeReceiptLinkCounter )
  2955.         {
  2956.           page.closeReceiptLinkCounter = 0;
  2957.         }
  2958.         else
  2959.         {
  2960.           ++page.closeReceiptLinkCounter;
  2961.         }
  2962.         onCloseFunction = "onReceiptClosed" + page.closeReceiptLinkCounter;
  2963.         receiptHtml += "<script type='text/javascript'>window." + onCloseFunction + " = function( ) { " + showCloseLink + " ; }; </script>";
  2964.         onCloseFunction += "( );";
  2965.       }
  2966.       receiptHtml += '<a class="close" href="#close" style="z-index:1000" title="' + page.bundle.getString("inlineconfirmation.close") + '" onClick="' + onCloseFunction + 'Element.remove( $(this).up(\'div.receipt\') ); return false;"><img alt="' + page.bundle.getString("inlineconfirmation.close") + '" src="/images/ci/ng/close_mini.gif"></a></div>';
  2967.     }
  2968.  
  2969.     if ( insertBefore )
  2970.     {
  2971.       contentPane.insert({before:receiptHtml});
  2972.     }
  2973.     else if (insertTop)
  2974.     {
  2975.       contentPane.insert({top:receiptHtml});
  2976.     }
  2977.     else
  2978.     {
  2979.       contentPane.insert({after:receiptHtml});
  2980.     }
  2981.     this.insertedDiv = insertBefore?contentPane.previousSibling:(insertTop?contentPane.firstChild:contentPane.nextSibling);
  2982.     $(this.insertedDiv).show();
  2983.     var insertedA = $(this.insertedDiv).down('span.inlineReceiptSpan');
  2984.     var fadingDuration = fadingTime ? fadingTime : 5000;
  2985.  
  2986.     // For all cases (focus or not), set the aria assertive attribute to make sure this is announced by the screen reader
  2987.     insertedA.setAttribute("aria-live","assertive");
  2988.     this.insertedDiv.setAttribute("role","application");
  2989.     (function() { insertedA.update( insertedA.innerHTML ); }.defer(2));  // update live region so it is announced (needed for jaws 12)
  2990.  
  2991.     if ( focusOnRender )
  2992.     {
  2993.         try
  2994.         {
  2995.          ( function()
  2996.             {
  2997.            try
  2998.            {
  2999.               if ( focusDiv )
  3000.               {
  3001.                 page.util.focusAndScroll( $( focusDiv ) );
  3002.               }
  3003.               else
  3004.               {
  3005.                 page.util.focusAndScroll( insertedA );
  3006.               }
  3007.            }
  3008.            catch ( focusError )
  3009.            {
  3010.              // Ignore focus errors. These can happens sometimes on IE if focus is set on an element that is located
  3011.              // inside another element that has recently been switched from a hidden state to a visible one.
  3012.            }
  3013.  
  3014.             }.defer() );
  3015.         }
  3016.         catch ( focusError )
  3017.         {
  3018.           // Ignore focus errors. These can happens sometimes on IE if focus is set on an element that is located
  3019.           // inside another element that has recently been switched from a hidden state to a visible one.
  3020.         }
  3021.     }
  3022.     else
  3023.     {
  3024.         // not setting focus to this confirmation - but still make sure it is visible.
  3025.         if ( focusDiv )
  3026.         {
  3027.           page.util.ensureVisible( $( focusDiv ) );
  3028.         }
  3029.         else
  3030.         {
  3031.           page.util.ensureVisible( insertedA );
  3032.         }
  3033.     }
  3034.     if ( fadeAway )
  3035.       {
  3036.         setTimeout( function()
  3037.         {
  3038.           Element.fade( $(this.insertedDiv),
  3039.           {
  3040.             duration : 0.3
  3041.           } );
  3042.         }.bind(this), fadingDuration );
  3043.       }
  3044.   },
  3045.  
  3046.   close: function()
  3047.   {
  3048.     if ( this.insertedDiv )
  3049.     {
  3050.       this.insertedDiv.remove();
  3051.     }
  3052.   }
  3053. };
  3054.  
  3055.  
  3056. page.NestedInlineFadeAwayConfirmation = Class.create();
  3057. page.NestedInlineFadeAwayConfirmation.prototype =
  3058. {
  3059.   initialize: function( type, message, showRefreshLink, element,showCloseLink, insertBefore, time  )
  3060.   {
  3061.   var fadingDuration = time ? time : 2000;
  3062.   new page.NestedInlineConfirmation(type, message, showRefreshLink, element,showCloseLink, "", insertBefore );
  3063.   var elementToFade = insertBefore?element.previousSibling:element.nextSibling;
  3064.  
  3065.     setTimeout(
  3066.       function()
  3067.       {
  3068.         Element.fade( elementToFade, {duration:0.3} );
  3069.       }, fadingDuration );
  3070.   }
  3071. };
  3072.  
  3073. page.NestedInlineFadeAwaySingleConfirmation = Class.create();
  3074. page.NestedInlineFadeAwaySingleConfirmation.prototype =
  3075. {
  3076.   initialize: function( type, message, showRefreshLink, element,showCloseLink, insertBefore, time, newDivId )
  3077.   {
  3078.   var fadingDuration = time ? time : 2000;
  3079.   new page.NestedInlineConfirmation(type, message, showRefreshLink, element,showCloseLink, "", insertBefore, false /*only one instance*/, null, null, null, null, newDivId );
  3080.   var elementToFade = insertBefore?element.previousSibling:element.nextSibling;
  3081.  
  3082.     setTimeout(
  3083.       function()
  3084.       {
  3085.         Element.fade( elementToFade, {duration:0.3} );
  3086.       }, fadingDuration );
  3087.   }
  3088. };
  3089.  
  3090. /**
  3091.  * Make sure the container as position: relative so that the offset can work
  3092.  */
  3093. page.MiniReceipt = Class.create();
  3094. page.MiniReceipt.prototype =
  3095. {
  3096.     initialize: function( message, containerElement, top, left, time )
  3097.     {
  3098.       var visibleDuration = time ? time : 2000;
  3099.       var top = top?top:-22; // usually show receipt above
  3100.       var left = left?left:0;
  3101.       var alreadyExistingReceipt = $( containerElement ).down( "div.miniReceipt" );
  3102.       if  ( alreadyExistingReceipt )
  3103.       {
  3104.         alreadyExistingReceipt.hide( );
  3105.       }
  3106.       var receiptHtml = '<div class="miniReceipt adding" style="display: none; top:' + top + 'px; left:'+ left + 'px" role="alert" aria-live="assertive">' + message + '</div>';
  3107.       var receiptElement = $( containerElement ).insert( { top:receiptHtml } ).firstDescendant( );
  3108.       receiptElement.show( );
  3109.       setTimeout(
  3110.         function()
  3111.         {
  3112.           Element.fade( receiptElement, {duration:0.3, afterFinish: function() { receiptElement.remove(); } } );
  3113.         }, visibleDuration );
  3114.     }
  3115. };
  3116.  
  3117. page.extendedHelp = function( helpattributes, windowName )
  3118. {
  3119.   window.helpwin = window.open('/webapps/blackboard/execute/viewExtendedHelp?' +
  3120.                helpattributes,windowName,'menubar=1,resizable=1,scrollbars=1,status=1,width=480,height=600');
  3121.   window.helpwin.focus();
  3122. };
  3123.  
  3124. page.decoratePageBanner = function()
  3125. {
  3126.   var bannerDiv = $('pageBanner');
  3127.   // TODO: review this logic - we used to actually add a style to containerDiv but
  3128.   // we do not need it anymore - does the pagetitlediv hiding depend on containerdiv existing?  probably, so leaving
  3129.   var containerDiv = $('contentPanel') || $('contentPane');
  3130.   if ( bannerDiv && containerDiv )
  3131.   {
  3132.     // hide empty title bar
  3133.     if ( !$('pageTitleText') && $('pageTitleDiv') )
  3134.     {
  3135.       $('pageTitleDiv').hide();
  3136.     }
  3137.   }
  3138. };
  3139.  
  3140. page.initializeSinglePopupPage = function( pageId )
  3141. {
  3142.   // Initialize the single popup page, make sure the window will be closed by clicking submit or cancel, and the parent
  3143.   // window will be refreshed after submit.
  3144.   var items = document.forms;
  3145.   for ( var i = 0; i < items.length; i++ )
  3146.   {
  3147.     var formItem = items[ i ];
  3148.     formItem.observe( 'submit', function()
  3149.     {
  3150.        (function()
  3151.        {
  3152.          window.close();
  3153.          if( window.opener.refreshConfirm )
  3154.          {
  3155.             window.opener.refreshConfirm(pageId);
  3156.          }
  3157.        }.defer());
  3158.     } );
  3159.     if ( formItem.top_Cancel )
  3160.     {
  3161.       Event.observe( formItem.top_Cancel, 'click', function( event )
  3162.       {
  3163.         Event.stop( event );
  3164.         window.close();
  3165.       } );
  3166.     }
  3167.     if ( formItem.bottom_Cancel )
  3168.     {
  3169.  
  3170.       Event.observe( formItem.bottom_Cancel, 'click', function( event )
  3171.       {
  3172.         Event.stop( event );
  3173.         window.close();
  3174.       } );
  3175.     }
  3176.   }
  3177. };
  3178.  
  3179. page.openLightbox = function( link, title, url, width, height )
  3180. {
  3181.   var lightboxParam =
  3182.   {
  3183.       defaultDimensions :
  3184.       {
  3185.           w : width ? width : 1000,
  3186.           h : height ? height : 800
  3187.       },
  3188.       contents : '<iframe src="' + url + '" width="100%" height="100%"/>',
  3189.       title : title,
  3190.       closeOnBodyClick : false,
  3191.       showCloseLink : true,
  3192.       useDefaultDimensionsAsMinimumSize : true
  3193.   };
  3194.   var lightboxInstance = new lightbox.Lightbox( lightboxParam );
  3195.   lightboxInstance.open();
  3196. };
  3197.  
  3198. page.printAndClose = function()
  3199. {
  3200.   (function() {
  3201.     window.print();
  3202.     window.close();
  3203.   }.defer());
  3204. };
  3205.  
  3206. /**
  3207.  * Utility for data collection step manipulation
  3208.  */
  3209. page.steps = {};
  3210. page.steps.HIDE = "hide";
  3211. page.steps.SHOW = "show";
  3212.  
  3213. /**
  3214.  * Hide or show an array of steps given the step ids and
  3215.  * renumber all visible steps on the page.
  3216.  *
  3217.  * @param action - either page.steps.HIDE or page.steps.SHOW
  3218.  * @param stepIdArr - string array of step ids
  3219.  */
  3220. page.steps.hideShowAndRenumber = function ( action, stepIdArr )
  3221. {
  3222.   // hide or show each of the step ids given
  3223.   ($A(stepIdArr)).each( function( stepId )
  3224.   {
  3225.       page.steps.hideShow( action, stepId );
  3226.   });
  3227.  
  3228.   // get all H3 elements that contain css class of "steptitle"
  3229.   var stepTitleTags = [];
  3230.   $A(document.getElementsByTagName('h3')).each( function( tag )
  3231.   {
  3232.     if ( page.util.hasClassName( tag, 'steptitle' ) )
  3233.     {
  3234.       stepTitleTags.push( $(tag) );
  3235.     }
  3236.   });
  3237.  
  3238.   // starting at number 1, renumber all of the visible steps
  3239.   var number = 1;
  3240.   stepTitleTags.each(function( stepTitleTag )
  3241.   {
  3242.     if ( stepTitleTag.up('div').visible() )
  3243.     {
  3244.       stepTitleTag.down('span').update(number);
  3245.       number++;
  3246.     }
  3247.   });
  3248. };
  3249.  
  3250. /**
  3251.  * Hide or show a single step given the step id.
  3252.  *
  3253.  * @param action - either page.steps.HIDE or page.steps.SHOW
  3254.  * @param stepId - string identifier to a single step
  3255.  */
  3256. page.steps.hideShow = function ( action, stepId )
  3257. {
  3258.   if ( action == page.steps.SHOW )
  3259.   {
  3260.     $(stepId).show();
  3261.   }
  3262.   else if ( action == page.steps.HIDE )
  3263.   {
  3264.     $(stepId).hide();
  3265.   }
  3266. };
  3267.  
  3268. page.showChangeTextSizeHelp = function( )
  3269. {
  3270.   page.extendedHelp('internalhandle=change_text_size&helpkey=change_text_size','change_text_size' );
  3271.   return false;
  3272. };
  3273.  
  3274. page.showAccessibilityOptions = function()
  3275. {
  3276.    var win = window.open('/webapps/portal/execute/changePersonalStyle?cmd=showAccessibilityOptions',
  3277.        'accessibilityOptions','menubar=1,resizable=1,scrollbars=1,status=1,width=480,height=600');
  3278.    win.focus();
  3279. };
  3280.  
  3281. page.toggleContrast = function( )
  3282. {
  3283.   new Ajax.Request('/webapps/portal/execute/changePersonalStyle?cmd=toggleContrast',
  3284.   {
  3285.     onSuccess: function(transport, json)
  3286.     {
  3287.       var fsWin;
  3288.       if (window.top.nav)
  3289.       {
  3290.         fsWin = window.top;
  3291.       }
  3292.       else if (window.opener && window.opener.top.nav)
  3293.       {
  3294.         fsWin = window.opener.top;
  3295.         window.close();
  3296.       }
  3297.       if (fsWin)
  3298.       {
  3299.         fsWin.nav.location.reload();
  3300.         fsWin.content.location.reload();
  3301.       }
  3302.       else
  3303.       {
  3304.         window.top.location.reload();
  3305.       }
  3306.     }
  3307.   });
  3308.   return false;
  3309. };
  3310.  
  3311. /**
  3312.  * IFrame-based shim used with popups so they render on top of all other page elements (including applets)
  3313.  */
  3314. page.popupShim = Class.create();
  3315. page.popupShim.prototype =
  3316. {
  3317.   initialize: function( popup )
  3318.   {
  3319.     this.popup = popup;
  3320.   },
  3321.  
  3322.   close: function( )
  3323.   {
  3324.     this.toggleOverlappingEmbeds( false );
  3325.   },
  3326.  
  3327.   open: function( )
  3328.   {
  3329.     this.toggleOverlappingEmbeds( true );
  3330.   },
  3331.  
  3332.   toggleOverlappingEmbeds: function( turnOff )
  3333.   {
  3334.     ['embed','object','applet','select'].each( function( tag ) {
  3335.       var elems = document.getElementsByTagName( tag );
  3336.       for ( var i = 0, l = elems.length; i < l; i++ )
  3337.       {
  3338.         var e = $(elems[i]);
  3339.        
  3340.         /* Only show/hide overlapping object if the element is visible in the first place, otherwise there is no point.
  3341.          * Note that visible() checks the display property, and behaves differently from the visibility property being
  3342.          * set below, so we're safe when this method is being called with turn off|on.
  3343.          */
  3344.         if( e.visible() )
  3345.         {
  3346.           if ( !turnOff || ( page.util.elementsOverlap( this.popup, e ) && !e.descendantOf( this.popup ) ) )
  3347.           {
  3348.             elems[i].style.visibility = ( turnOff ? 'hidden' : '' );
  3349.           }
  3350.         }
  3351.       }
  3352.     }.bind( this ) );
  3353.   }
  3354. };
  3355.  
  3356. /**
  3357.  * Looks through the children of the specified element for links with the specified
  3358.  * class name, and if it finds any, autowires lightboxes to them.  If lightbox.js/effects.js
  3359.  * hasn't already been loaded, load it.
  3360.  */
  3361. page.LightboxInitializer = Class.create(
  3362. {
  3363.   initialize: function( className, parentElement, justThisParent )
  3364.   {
  3365.     this.className = className;
  3366.     if (justThisParent)
  3367.     {
  3368.       this.parentElement = parentElement;
  3369.     }
  3370.     var links = parentElement.getElementsByTagName('a');
  3371.     for ( var i = 0, l = links.length; i < l; i++ )
  3372.     {
  3373.       if ( page.util.hasClassName( links[i], className ) )
  3374.       {
  3375.         if ( window.lightbox && window.Effect)
  3376.         {
  3377.           this._autowire();
  3378.         }
  3379.         else
  3380.         {
  3381.           this._load();
  3382.         }
  3383.         break;
  3384.       }
  3385.     }
  3386.   },
  3387.  
  3388.   _autowire: function()
  3389.   {
  3390.     lightbox.autowireLightboxes( this.className, this.parentElement );
  3391.   },
  3392.  
  3393.   _load: function()
  3394.   {
  3395.     var h = $$('head')[0];
  3396.     // TODO: This code does not take version into account (so immediately after an upgrade this won't get the new file)...
  3397.     var scs = ( !window.lightbox ? ['/javascript/ngui/lightbox.js'] : []).concat(
  3398.                 !window.Effect ? ['/javascript/scriptaculous/effects.js'] : [] );
  3399.     scs.each( function( sc )
  3400.     {
  3401.       var s = new Element('script', { type: 'text/javascript', src: sc } );
  3402.       h.appendChild( s );
  3403.     });
  3404.     this._wait();
  3405.   },
  3406.  
  3407.   _wait: function()
  3408.   {
  3409.     var count = 0;
  3410.     new PeriodicalExecuter( function( pe )
  3411.     {
  3412.       if ( count < 100 )
  3413.       {
  3414.         count++;
  3415.         if ( window.lightbox && window.Effect )
  3416.         {
  3417.           pe.stop();
  3418.           this._autowire();
  3419.         }
  3420.       }
  3421.       else // give up if it takes longer than 5s to load lightbox.js/effects.js
  3422.       {
  3423.         pe.stop();
  3424.       }
  3425.     }.bind(this), 0.05 );
  3426.   }
  3427. });
  3428.  
  3429. page.YouTubeControls = {
  3430.   toggleAXControls : function( playerid, openYtControlsId, event )
  3431.   {
  3432.     if( $( playerid.sub( 'ytEmbed', 'controls' ) ).style.display != 'block' ) {
  3433.       $( playerid.sub( 'ytEmbed', 'controls' ) ).style.display = 'block';
  3434.       $( playerid.sub( 'ytEmbed', 'strip' ) ).style.display = 'block';
  3435.       $( openYtControlsId ).addClassName( 'liveAreaTab' );
  3436.       if ( window.lightbox && lightbox.getCurrentLightbox() )
  3437.       {
  3438.         lightbox.getCurrentLightbox()._resizeAndCenterLightbox( false );
  3439.       }
  3440.  
  3441.     }
  3442.     else
  3443.     {
  3444.       $( playerid.sub( 'ytEmbed', 'controls' ) ).style.display = 'none';
  3445.       $( playerid.sub( 'ytEmbed', 'strip' ) ).style.display = 'none';
  3446.       $( openYtControlsId ).removeClassName( 'liveAreaTab' );
  3447.       if ( window.lightbox && lightbox.getCurrentLightbox() )
  3448.       {
  3449.         lightbox.getCurrentLightbox()._resizeAndCenterLightbox( false );
  3450.       }
  3451.  
  3452.     }
  3453.     Event.stop( event );
  3454.   },
  3455.   formatTime : function ( sec )
  3456.   {
  3457.     var duration = parseInt( sec, 10 );
  3458.     var totalMinutes = Math.floor( duration / 60 );
  3459.     var hours = Math.floor( totalMinutes / 60 );
  3460.     var seconds = duration % 60;
  3461.     var minutes = totalMinutes % 60;
  3462.     if ( hours > 0 )
  3463.     {
  3464.       return hours + ':' + this.padZero( minutes ) + ':' + this.padZero( seconds );
  3465.     }
  3466.     else
  3467.     {
  3468.       return this.padZero( minutes ) + ':' + this.padZero( seconds );
  3469.     }
  3470.   },
  3471.   padZero : function ( number )
  3472.   {
  3473.     if (number < 10)
  3474.     {
  3475.       return "0" + number;
  3476.     }
  3477.     else
  3478.     {
  3479.       return number;
  3480.     }
  3481.   },
  3482.   updateButtonLabels : function ( ytplayer, muteBtnId, playBtnId, status )
  3483.   {
  3484.     if( ytplayer.isMuted() )
  3485.     {
  3486.       $( muteBtnId ).update( page.bundle.getString( 'yt.unmute' ) );
  3487.     }
  3488.     else
  3489.     {
  3490.       $( muteBtnId ).update( page.bundle.getString( 'yt.mute' ) );
  3491.     }
  3492.     if( status == 1 )
  3493.     {
  3494.       $( playBtnId ).update( page.bundle.getString( 'yt.pause' ) );
  3495.     }
  3496.     else
  3497.     {
  3498.       $( playBtnId ).update( page.bundle.getString( 'yt.play' ) );
  3499.     }
  3500.   },
  3501.   updateIframeButtonLabels : function ( ytplayer, muteBtnId, playBtnId, status )
  3502.   {
  3503.     if ( typeof ytplayer.isMuted !== 'undefined'  ) {
  3504.     if( ytplayer.isMuted() )
  3505.     {
  3506.       $( muteBtnId ).update( page.bundle.getString( 'yt.unmute' ) );
  3507.     }
  3508.     else
  3509.     {
  3510.       $( muteBtnId ).update( page.bundle.getString( 'yt.mute' ) );
  3511.     }
  3512.     }
  3513.     if( status == 1 )
  3514.     {
  3515.       $( playBtnId ).update( page.bundle.getString( 'yt.pause' ) );
  3516.     }
  3517.     else
  3518.     {
  3519.       $( playBtnId ).update( page.bundle.getString( 'yt.play' ) );
  3520.     }
  3521.   }  
  3522. };
  3523.  
  3524. function onYouTubePlayerReady( playerid )
  3525. {
  3526.   var ytplayer = $( playerid );
  3527.   if( !ytplayer )
  3528.   { //ie fix: grab object tag instead of embed tag
  3529.     var objTagId = playerid.sub( 'ytEmbed', 'ytObject' );
  3530.     ytplayer = $( objTagId );
  3531.   }
  3532.   var playBtnId = playerid.sub( 'ytEmbed', 'playVideo' );
  3533.   Event.observe( $( playBtnId ), 'click',
  3534.     function( event ) {
  3535.       if( ytplayer.getPlayerState() == 1 )
  3536.       {
  3537.         ytplayer.pauseVideo();
  3538.       }
  3539.       else
  3540.       {
  3541.         ytplayer.playVideo();
  3542.       }
  3543.       Event.stop( event );
  3544.     }
  3545.   );
  3546.   var stopBtnId = playerid.sub( 'ytEmbed', 'stopVideo' );
  3547.   Event.observe( $( stopBtnId ), 'click',
  3548.     function( event ) {
  3549.       ytplayer.pauseVideo();
  3550.       ytplayer.seekTo( "0" );
  3551.       $( playBtnId ).update( page.bundle.getString( 'yt.play' ) );
  3552.       Event.stop( event );
  3553.     }
  3554.   );
  3555.   var volUpBtnId = playerid.sub( 'ytEmbed', 'volUp' );
  3556.   Event.observe( $( volUpBtnId ), 'click',
  3557.     function( event ) {
  3558.       var currVol = ytplayer.getVolume();
  3559.       if( currVol > 89 )
  3560.       {
  3561.         ytplayer.setVolume( 100 );
  3562.       }
  3563.       else
  3564.       {
  3565.         ytplayer.setVolume( currVol + 10 );
  3566.       }
  3567.       Event.stop( event );
  3568.     }
  3569.   );
  3570.   var volDownBtnId = playerid.sub( 'ytEmbed', 'volDown' );
  3571.   Event.observe( $( volDownBtnId ), 'click',
  3572.     function( event ) {
  3573.       var currVol = ytplayer.getVolume();
  3574.       if( currVol < 11 )
  3575.       {
  3576.         ytplayer.setVolume( 0 );
  3577.       }
  3578.       else
  3579.       {
  3580.         ytplayer.setVolume( currVol - 10 );
  3581.       }
  3582.       Event.stop( event );
  3583.     }
  3584.   );
  3585.   var muteBtnId = playerid.sub( 'ytEmbed', 'mute' );
  3586.   Event.observe( $( muteBtnId ), 'click',
  3587.     function( event ) {
  3588.       if( ytplayer.isMuted() )
  3589.       {
  3590.         ytplayer.unMute();
  3591.       }
  3592.       else
  3593.       {
  3594.         ytplayer.mute();
  3595.       }
  3596.       Event.stop( event );
  3597.     }
  3598.   );
  3599.   var timeDivId = playerid.sub( 'ytEmbed', 'currentTime' );
  3600.   var statusDivId = playerid.sub( 'ytEmbed', 'currentStatus');
  3601.   var dtTime = new Date();
  3602.   new PeriodicalExecuter( function( pe )
  3603.   {
  3604.     //lightbox closed, so stop this PeriodicalExecuter
  3605.     if( !$( timeDivId ) )
  3606.     {
  3607.       pe.stop();
  3608.       return;
  3609.     }
  3610.     //update the current time
  3611.     $( timeDivId ).update( page.YouTubeControls.formatTime( ytplayer.getCurrentTime() ) );
  3612.     //update the current status
  3613.     var status = ytplayer.getPlayerState();
  3614.     var statusStr = page.bundle.getString( 'yt.stopped' );
  3615.     switch( status )
  3616.     {
  3617.     case -1 : statusStr = page.bundle.getString( 'yt.stopped' ); break;
  3618.     case 0  : statusStr = page.bundle.getString( 'yt.ended' ); break;
  3619.     case 1  : statusStr = page.bundle.getString( 'yt.playing' ); break;
  3620.     case 2  : statusStr = page.bundle.getString( 'yt.paused' ); break;
  3621.     case 3  : statusStr = page.bundle.getString( 'yt.buffering' ); break;
  3622.     case 5  : statusStr = page.bundle.getString( 'yt.cued' ); break;
  3623.     }
  3624.     page.YouTubeControls.updateButtonLabels( ytplayer, muteBtnId, playBtnId, status );
  3625.  
  3626.     $( statusDivId ).update( statusStr );
  3627.   }.bind(this), 0.5 );
  3628.  
  3629.   //wire the open/close controls wrapper
  3630.   var openYtControlsId = playerid.sub( 'ytEmbed', 'openYtControls' );
  3631.   Event.observe( $( openYtControlsId ), 'click',
  3632.     function( event ) {
  3633.       page.YouTubeControls.toggleAXControls( playerid, openYtControlsId, event );
  3634.     }
  3635.   );
  3636.   var closeYtControlsId = playerid.sub( 'ytEmbed', 'closeYtControls' );
  3637.   Event.observe( $( closeYtControlsId ), 'click',
  3638.     function( event ) {
  3639.       page.YouTubeControls.toggleAXControls( playerid, openYtControlsId, event );
  3640.     }
  3641.   );
  3642. };
  3643.  
  3644. var youtubeisready;
  3645. var tag = document.createElement( 'script' );
  3646. tag.src = "//www.youtube.com/iframe_api";
  3647. var firstScriptTag = document.getElementsByTagName( 'script' )[ 0 ];
  3648. firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
  3649.  
  3650. function onYouTubeIframeAPIReady()
  3651. {
  3652.   youtubeisready = true;
  3653.   page.util.bbPreptextareasforyoutube();
  3654. };
  3655.  
  3656. page.util.bbPreptextareasforyoutube = function(frameIds)
  3657. {
  3658.   if( youtubeisready )
  3659.   {
  3660.     if ( !frameIds )
  3661.     {
  3662.       frameIds = $$( '.ytIframeClass' );
  3663.     }
  3664.     var ytPlayers = [];
  3665.     frameIds.each( function( frameId )
  3666.     {
  3667.       var ytPlayerId = frameId.id;
  3668.       var controlsId = ytPlayerId.replace("ytEmbed","");
  3669.       var frameData = frameId.outerHTML;
  3670.       var videoId = "";
  3671.       if( frameData.indexOf('//www.youtube.com/embed') != -1 )
  3672.       {
  3673.         videoId = frameData.match("//www.youtube.com/embed/([\\d\\w-_]+)")[1];
  3674.       }
  3675.      
  3676.       if( frameData.indexOf('//www.youtube.com/v') != -1 )
  3677.       {
  3678.         videoId = frameData.match("//www.youtube.com/v/([\\d\\w-_]+)")[1];
  3679.       }
  3680.      
  3681.       if( videoId  === "" )
  3682.         return;
  3683.      
  3684.         ytPlayerId.replace("ytEmbed", "videoId");
  3685.         var ytplayer = new YT.Player(frameId, {
  3686.             videoId : videoId,
  3687.             playerVars : {
  3688.               wmode: 'transparent',
  3689.               rel : 0,
  3690.               modestbranding : 1,
  3691.               menu: 'disable'
  3692.             },
  3693.             events : {
  3694.                 'onReady' : onPlayerReady,
  3695.                 'onStateChange' : onytplayerStateChange,
  3696.                 'onError' : onPlayerError
  3697.             }
  3698.         });
  3699.      
  3700.       ytPlayers.push(ytplayer);
  3701.       var playBtnId = $("playVideo" + controlsId );
  3702.       Event.observe( playBtnId, 'click', function( event )
  3703.       {
  3704.         if ( ytplayer.getPlayerState() == 1 )
  3705.         {
  3706.           ytplayer.pauseVideo();
  3707.         }
  3708.         else
  3709.         {
  3710.           ytplayer.playVideo();
  3711.         }
  3712.         Event.stop( event );
  3713.       } );
  3714.       var stopBtnId = $("stopVideo" + controlsId );
  3715.       Event.observe(  stopBtnId , 'click', function( event )
  3716.       {
  3717.         ytplayer.pauseVideo();
  3718.         ytplayer.seekTo( "0" );
  3719.         $( playBtnId ).update( page.bundle.getString( 'yt.play' ) );
  3720.         Event.stop( event );
  3721.       } );
  3722.       var volUpBtnId = $("volUp" + controlsId );
  3723.       Event.observe( volUpBtnId , 'click',
  3724.         function( event ) {
  3725.           var currVol = ytplayer.getVolume();
  3726.           if( currVol > 89 )
  3727.           {
  3728.             ytplayer.setVolume( 100 );
  3729.           }
  3730.           else
  3731.           {
  3732.             ytplayer.setVolume( currVol + 10 );
  3733.           }
  3734.           Event.stop( event );
  3735.         }
  3736.       );
  3737.       var volDownBtnId = $("volDown" + controlsId );
  3738.       Event.observe( volDownBtnId, 'click',
  3739.         function( event ) {
  3740.           var currVol = ytplayer.getVolume();
  3741.           if( currVol < 11 )
  3742.           {
  3743.             ytplayer.setVolume( 0 );
  3744.           }
  3745.           else
  3746.           {
  3747.             ytplayer.setVolume( currVol - 10 );
  3748.           }
  3749.           Event.stop( event );
  3750.         }
  3751.       );
  3752.       var muteBtnId = $("mute" + controlsId );
  3753.       Event.observe( muteBtnId, 'click',
  3754.         function( event ) {
  3755.         if ( typeof ytplayer.isMuted !== 'undefined'  ) {
  3756.           if( ytplayer.isMuted() )
  3757.           {
  3758.             ytplayer.unMute();
  3759.           }
  3760.           else
  3761.           {
  3762.             ytplayer.mute();
  3763.           }
  3764.         }
  3765.           Event.stop( event );
  3766.         }
  3767.       );    
  3768.      
  3769.       var timeDivId = ytPlayerId.sub( 'ytEmbed', 'currentTime' );
  3770.       var statusDivId = ytPlayerId.sub( 'ytEmbed', 'currentStatus');
  3771.       var dtTime = new Date();
  3772.       new PeriodicalExecuter( function( pe )
  3773.       {
  3774.         //lightbox closed, so stop this PeriodicalExecuter
  3775.         if( !$( timeDivId ) )
  3776.         {
  3777.           pe.stop();
  3778.           return;
  3779.         }
  3780.         //update the current time
  3781.         if ( typeof ytplayer.getCurrentTime !== 'undefined'  )
  3782.         {
  3783.           $( timeDivId ).update( page.YouTubeControls.formatTime( ytplayer.getCurrentTime() ) );
  3784.         }
  3785.         //update the current status
  3786.         var status = -1;
  3787.         if ( typeof ytplayer.getPlayerState !== 'undefined'  )
  3788.         {
  3789.           status = ytplayer.getPlayerState();
  3790.         }
  3791.         var statusStr = page.bundle.getString( 'yt.stopped' );
  3792.         switch( status )
  3793.         {
  3794.         case -1 : statusStr = page.bundle.getString( 'yt.stopped' ); break;
  3795.         case 0  : statusStr = page.bundle.getString( 'yt.ended' ); break;
  3796.         case 1  : statusStr = page.bundle.getString( 'yt.playing' ); break;
  3797.         case 2  : statusStr = page.bundle.getString( 'yt.paused' ); break;
  3798.         case 3  : statusStr = page.bundle.getString( 'yt.buffering' ); break;
  3799.         case 5  : statusStr = page.bundle.getString( 'yt.cued' ); break;
  3800.         }
  3801.         page.YouTubeControls.updateIframeButtonLabels( ytplayer, muteBtnId, playBtnId, status );
  3802.  
  3803.         $( statusDivId ).update( statusStr );
  3804.       }.bind(this), 0.5 );
  3805.      
  3806.       //wire the open/close controls wrapper
  3807.       var openYtControlsId =$("openYtControls" + controlsId);
  3808.       Event.observe( openYtControlsId, 'click', function( event )
  3809.       {
  3810.         page.YouTubeControls.toggleAXControls( ytPlayerId, openYtControlsId, event );
  3811.       } );
  3812.       var closeYtControlsId = ytPlayerId.sub( 'ytEmbed', 'closeYtControls' );
  3813.       Event.observe( $( closeYtControlsId ), 'click', function( event )
  3814.       {
  3815.         page.YouTubeControls.toggleAXControls( ytPlayerId, openYtControlsId, event );
  3816.       } );
  3817.     }
  3818.     );
  3819.   }
  3820.   return ytPlayers;
  3821. };
  3822.  
  3823. function onPlayerReady( event )
  3824. {
  3825.   event.target.stopVideo();
  3826. };
  3827.  
  3828. function onPlayerError( event )
  3829. {
  3830.   event.target.stopVideo();
  3831. };
  3832.  
  3833. function onytplayerStateChange( event )
  3834. {
  3835.   if ( event.data === 0 )
  3836.   {
  3837.     event.target.playVideo();
  3838.     event.target.stopVideo();
  3839.   }
  3840. };
  3841.  
  3842. page.util.flyoutMenuMainButtonKeyboardHandler = function( event )
  3843. {
  3844.   var key = event.keyCode || event.which;
  3845.   if (key == Event.KEY_LEFT || key == Event.KEY_RIGHT)
  3846.   {
  3847.     var elem = Event.element( event );
  3848.     var target = elem.up( 'li' );
  3849.     while ( true )
  3850.     {
  3851.       if ( key == Event.KEY_LEFT )
  3852.       {
  3853.         target = target.previous();
  3854.       }
  3855.       else if ( key == Event.KEY_RIGHT )
  3856.       {
  3857.         target = target.next();
  3858.       }
  3859.       if ( !target || page.util.hasClassName( target, 'sub' ) ||
  3860.                       page.util.hasClassName( target, 'mainButton' ) ||
  3861.                       page.util.hasClassName( target, 'mainButtonType' ) )
  3862.       {
  3863.         break;
  3864.       }
  3865.     }
  3866.     if ( target )
  3867.     {
  3868.       var menuLinks = $A( target.getElementsByTagName( 'a' ) );
  3869.       if ( menuLinks && menuLinks.length > 0 )
  3870.       {
  3871.         menuLinks[ 0 ].focus();
  3872.         Event.stop( event );
  3873.       }
  3874.     }
  3875.   }
  3876. };
  3877.  
  3878. page.util.initFlyoutMenuBehaviourForListActionMenuItems = function( container ) {
  3879.   //Initialize accessible flyout menu behavior
  3880.   if ( !container )
  3881.   {
  3882.     container = document;
  3883.   }
  3884.   var uls = document.getElementsByTagName('ul');
  3885.   if (uls) {
  3886.     var numUls = uls.length;
  3887.     for (var i = 0; i < numUls; i++) {
  3888.       var ul = uls[i];
  3889.       if (page.util.hasClassName(ul, 'nav')) {
  3890.         var lis = ul.getElementsByTagName('li');
  3891.         if (lis) {
  3892.           var numLis = lis.length;
  3893.           for (var j = 0; j < numLis; j++) {
  3894.             var li = lis[j];
  3895.             if (page.util.hasClassName(li, 'sub')) {
  3896.               new page.FlyoutMenu($(li));
  3897.             } else if (page.util.hasClassName(li, 'mainButton') || page.util.hasClassName(li, 'mainButtonType')) {
  3898.               var menuLinks = $A($(li).getElementsByTagName('a'));
  3899.               if (menuLinks && menuLinks.length > 0) {
  3900.                 Event.observe(menuLinks[0], 'keydown', page.util.flyoutMenuMainButtonKeyboardHandler.bindAsEventListener(menuLinks[0]));
  3901.               }
  3902.             }
  3903.           }
  3904.         }
  3905.       }
  3906.     }
  3907.   }
  3908. };
  3909.  
  3910. page.util.getMaxContentHeight = function( iframeElement )
  3911. {
  3912.   var maxHeight = iframeElement.contentWindow.document.body.scrollHeight;
  3913.   var frameElements;
  3914.   var iframeElements;
  3915.   if ( iframeElement.contentDocument )
  3916.   {
  3917.     // getElementsByTagName() returns a NodeList object, which is immutable and cannot easily be converted to an array
  3918.     frameElements = iframeElement.contentDocument.getElementsByTagName("frame");
  3919.     iframeElements = iframeElement.contentDocument.getElementsByTagName("iframe");
  3920.   }
  3921.  
  3922.   var i = 0;
  3923.   var frameHeight;
  3924.   var frameElement;
  3925.  
  3926.   for( i = 0; i < frameElements.length; i++ )
  3927.   {
  3928.     frameElement = frameElements[i];
  3929.  
  3930.     if( frameElement.contentWindow && frameElement.contentWindow.document && frameElement.contentWindow.document.body )
  3931.     {
  3932.       frameHeight = frameElement.contentWindow.document.body.scrollHeight;
  3933.     }
  3934.  
  3935.     if( frameHeight > maxHeight )
  3936.     {
  3937.       maxHeight = frameHeight;
  3938.     }
  3939.   }
  3940.  
  3941.   for( i = 0; i < iframeElements.length; i++ )
  3942.   {
  3943.     frameElement = iframeElements[i];
  3944.  
  3945.     if( frameElement.contentWindow && frameElement.contentWindow.document && frameElement.contentWindow.document.body )
  3946.     {
  3947.       frameHeight = frameElement.contentWindow.document.body.scrollHeight;
  3948.     }
  3949.  
  3950.     if( frameHeight > maxHeight )
  3951.     {
  3952.       maxHeight = frameHeight;
  3953.     }
  3954.   }
  3955.  
  3956.   return maxHeight;
  3957. };
  3958.  
  3959. page.util.getMaxContentWidth = function( iframeElement )
  3960. {
  3961.   var maxWidth = iframeElement.contentWindow.document.body.scrollWidth;
  3962.   var frameElements;
  3963.   var iframeElements;
  3964.   if ( iframeElement.contentDocument )
  3965.   {
  3966.     // getElementsByTagName() returns a NodeList object, which is immutable and cannot easily be converted to an array
  3967.     frameElements = iframeElement.contentDocument.getElementsByTagName("frame");
  3968.     iframeElements = iframeElement.contentDocument.getElementsByTagName("iframe");
  3969.   }
  3970.  
  3971.   var i = 0;
  3972.   var frameWidth;
  3973.   var frameElement;
  3974.  
  3975.   for( i = 0; i < frameElements.length; i++ )
  3976.   {
  3977.     frameElement = frameElements[i];
  3978.  
  3979.     if( frameElement.contentWindow && frameElement.contentWindow.document && frameElement.contentWindow.document.body )
  3980.     {
  3981.       frameWidth = frameElement.contentWindow.document.body.scrollWidth;
  3982.     }
  3983.  
  3984.     if( frameWidth > maxWidth )
  3985.     {
  3986.       maxWidth = frameWidth;
  3987.     }
  3988.   }
  3989.  
  3990.   for( i = 0; i < iframeElements.length; i++ )
  3991.   {
  3992.     frameElement = iframeElements[i];
  3993.  
  3994.     if( frameElement.contentWindow && frameElement.contentWindow.document && frameElement.contentWindow.document.body )
  3995.     {
  3996.       frameWidth = frameElement.contentWindow.document.body.scrollWidth;
  3997.     }
  3998.  
  3999.     if( frameWidth > maxWidth )
  4000.     {
  4001.       maxWidth = frameWidth;
  4002.     }
  4003.   }
  4004.  
  4005.   return maxWidth;
  4006. };
  4007.  
  4008. page.subheaderCleaner =
  4009. {
  4010.   init : function( entityKind )
  4011.   {
  4012.   var allHidden = true;
  4013.   var firstUl = null;
  4014.   var className = 'portletList-img courseListing ' + entityKind;
  4015.   $A( document.getElementsByClassName( className ) ).each( function( ul ) {
  4016.       if ( !ul.down() )
  4017.       {
  4018.         ul.previous( 'h3' ).hide();
  4019.         ul.hide();
  4020.         if ( !firstUl )
  4021.         {
  4022.           firstUl = ul;
  4023.         }
  4024.       }
  4025.       else
  4026.       {
  4027.         allHidden = false;
  4028.       }
  4029.     });
  4030.     if ( allHidden && firstUl )
  4031.     {
  4032.       firstUl.up( 'div' ).previous( 'div' ).show();
  4033.     }
  4034.   }
  4035. };
  4036.  
  4037.  /**
  4038.   * Set up any JavaScript that will always be run on load (that doesn't depend on
  4039.   * any application logic / localization) here.
  4040.   *
  4041.   * Please leave this at the bottom of the file so it's easy to find.
  4042.   *
  4043.   */
  4044. FastInit.addOnLoad( function()
  4045. {
  4046.   Event.observe( document.body, "click", page.ContextMenu.closeAllContextMenus.bindAsEventListener( window ) );
  4047.  
  4048.   Event.observe( document.body, "click", page.ContextMenu.alignArrowsInBreadcrumb.bindAsEventListener( window ) );
  4049.  
  4050.   Event.observe( document.body, 'keydown', function(event) {
  4051.     var key = event.keyCode || event.which;
  4052.     if ( key == 116 )  // reload current page on F5 key press
  4053.     {
  4054.       Event.stop( event );  // prevent browser from reloading complete frameset
  4055.       if ( Prototype.Browser.IE )
  4056.       {
  4057.         event.keyCode = 0;
  4058.       }
  4059.       (function() { window.location.reload( true ); }.defer());
  4060.       return false;
  4061.     }
  4062.   });
  4063.  
  4064.   page.util.initFlyoutMenuBehaviourForListActionMenuItems();
  4065.  
  4066.   if ( $('breadcrumbs') )
  4067.   {
  4068.     new page.BreadcrumbExpander($('breadcrumbs'));
  4069.     // If we're in the content wrapper, hide the content wrapper breadcrumb frame
  4070.     // so that we don't get stacked breadcrumbs.
  4071.     if ( window.name === 'contentFrame' )
  4072.     {
  4073.       var parent = window.parent;
  4074.       if ( parent )
  4075.       {
  4076.         var frameset = parent.document.getElementById( 'contentFrameset' );
  4077.         if ( frameset )
  4078.         {
  4079.           frameset.rows = "*,100%";
  4080.         }
  4081.       }
  4082.     }
  4083.   }
  4084.  
  4085.   var contentPane = $('contentPanel') || $('portalPane');
  4086.   if ( contentPane )
  4087.   {
  4088.     new page.LightboxInitializer( 'lb', contentPane );
  4089.   }
  4090.  
  4091.   // add a label for inventory table checkboxes, if needed
  4092.   $A(document.getElementsByTagName("table")).each( function( table )
  4093.   {
  4094.     if ( !page.util.hasClassName( table, 'inventory' ) )
  4095.     {
  4096.       return;
  4097.     }
  4098.     var rows = table.rows;
  4099.     if ( rows.length < 2 )
  4100.     {
  4101.       return;
  4102.     }
  4103.     for (var r = 0, rlen = rows.length - 1; r < rlen; r++)
  4104.     {
  4105.       var cells = rows[r+1].cells; // skip header row
  4106.       for (var c = 0, clen = cells.length; c < clen; c++)
  4107.       {
  4108.         var cell = $(cells[c]);
  4109.         var inp = cell.down('input');
  4110.  
  4111.         if ( !inp || ( inp.type != 'checkbox' && inp.type != 'radio' ) )
  4112.         {
  4113.           // We're only looking for checkbox/radio cells to label, so move on
  4114.           continue;
  4115.         }
  4116.  
  4117.         var lbl = cell.down('label');
  4118.  
  4119.         if (lbl && !lbl.innerHTML.blank())
  4120.         {
  4121.           break; // skip cells that already have a non-blank label
  4122.         }
  4123.  
  4124.         if ( !lbl )
  4125.         {  // add new label to checkbox
  4126.           lbl = new Element('label', {htmlFor: inp.id} );
  4127.           lbl.addClassName('hideoff');
  4128.           cell.insert({bottom:lbl});
  4129.         }
  4130.         var headerCell = $(cell.parentNode).down('th');
  4131.         if ( !headerCell )
  4132.         {
  4133.           break; // skip rows without header cell
  4134.         }
  4135.  
  4136.         // create a temporary clone of the header cell and remove any hidden divs I.e. context menus
  4137.         var tempCell = $(headerCell.cloneNode(true));
  4138.         var tempCellDivs = tempCell.getElementsByTagName("div");
  4139.         for ( var i = 0; i < tempCellDivs.length; i++ )
  4140.         {
  4141.           var d = tempCellDivs[i];
  4142.           if ( d && !$(d).visible() )
  4143.           {
  4144.             d.remove();
  4145.           }
  4146.         }
  4147.         var lblBody = tempCell.innerHTML.replace( /<\/?[^>]*>/g, '' );  // strip html tags from header
  4148.         lblBody = page.bundle.getString('inventoryList.select.item', lblBody);
  4149.         lbl.update( lblBody );  // set label to header contents (minus tags)
  4150.         break;
  4151.       }
  4152.     }
  4153.   });
  4154.  
  4155.   //set default font sizes to display text. hack to fix IE7 default font size issue.
  4156.   var sizes = {1:'xx-small', 2:'x-small', 3:'small', 4:'medium', 5:'large', 6:'x-large', 7:'xx-large'};
  4157.   var fonts = document.getElementsByTagName('font');
  4158.   for ( var i = 0; i < fonts.length; i++ )
  4159.   {
  4160.     var font = fonts[i];
  4161.     if ( font.size )
  4162.     {
  4163.       // Since some font elements may be manually created by end users we have to handle random
  4164.       // values in here.
  4165.       if (!font.size.startsWith("+") && !font.size.startsWith("-"))
  4166.       {
  4167.         var fsize = parseInt(font.size, 10);
  4168.         if (fsize > 0 && fsize < 8)
  4169.         {
  4170.           font.style.fontSize = sizes[fsize];
  4171.         }
  4172.       }
  4173.     }
  4174.   }
  4175.  
  4176.   page.scrollToEnsureVisibleElement();
  4177.   page.isLoaded = true;
  4178.  
  4179. });
  4180.  
  4181. /**
  4182.  * Class for adding an insertion marker within a list
  4183.  */
  4184. page.ListInsertionMarker = Class.create();
  4185. page.ListInsertionMarker.prototype =
  4186. {
  4187.   initialize: function( listId, position, key, text )
  4188.   {
  4189.     var list = $(listId);
  4190.     var listElements = list.childElements();
  4191.     // create a marker list item
  4192.     var marker = new Element('li',{'id':listId+':'+key, 'class':'clearfix separator' });
  4193.     marker.update('<h3 class="item" id=""><span class="reorder editmode"><span><img alt="" src="/images/ci/icons/generic_updown.gif"></span></span><span class="line"></span><span class="text">'+text+'</span></h3>');
  4194.     //marker.setStyle({  position: 'relative', minHeight: '10px', padding: '0px', background: '#CCCCCC' });
  4195.     position = ( position > listElements.length ) ? listElements.length : position;
  4196.  
  4197.     // add marker to list
  4198.     if (listElements.length === 0)
  4199.     {
  4200.       list.insert({top:marker}); // add marker to top of empty list
  4201.     }
  4202.     else if (listElements.length == position)
  4203.     {
  4204.       list.insert({bottom:marker});  // add marker after last element
  4205.     }
  4206.     else
  4207.     {
  4208.       listElements[position].insert({before:marker});  // add marker before element at position
  4209.     }
  4210.  
  4211.     var select = $('reorderControls'+listId).down('select');
  4212.     // add a option for the marker to the keyboard repostioning select, if any
  4213.     if (select)
  4214.     {
  4215.       var option = new Element('option',{'value':key}).update( '-- '+text+' --' );
  4216.       if (listElements.length === 0)
  4217.       {
  4218.         select.insert({top:option});
  4219.       }
  4220.       else if (listElements.length == position)
  4221.       {
  4222.         select.insert({bottom:option});
  4223.       }
  4224.       else
  4225.       {
  4226.         $(select.options[position]).insert({before:option});
  4227.       }
  4228.     }
  4229.   }
  4230. };
  4231.  
  4232. page.scrollToEnsureVisibleElement = function( )
  4233. {
  4234.   var params = window.location.search.parseQuery();
  4235.   var ensureVisibleId = params.ensureVisibleId;
  4236.   if ( !ensureVisibleId )
  4237.   {
  4238.     return;
  4239.   }
  4240.   var ensureVisibleElement = $(ensureVisibleId);
  4241.   if ( !ensureVisibleElement )
  4242.   {
  4243.     return;
  4244.   }
  4245.   var pos = ensureVisibleElement.cumulativeOffset();
  4246.   var scrollY = pos.top;
  4247.   var bodyHeight = $( document.body ).getHeight();
  4248.   if (scrollY + ensureVisibleElement.getHeight() < bodyHeight)
  4249.   {
  4250.     return; // element is already visible
  4251.   }
  4252.  
  4253.   var receipt = $('inlineReceipt_good');
  4254.   if ( receipt && receipt.visible() ) // pin receipt to top
  4255.   {
  4256.     var offset = receipt.cumulativeOffset();
  4257.     offset.top = 0;
  4258.     var w = parseInt(receipt.getStyle('width'), 10);
  4259.     if ( Prototype.Browser.IE ) // width in IE includes border & padding, need to remove it
  4260.     {
  4261.       var bw = parseInt(receipt.getStyle('borderLeftWidth'), 10) + parseInt(receipt.getStyle('borderRightWidth'), 10);
  4262.       var pw = parseInt(receipt.getStyle('paddingLeft'), 10) + parseInt(receipt.getStyle('paddingRight'), 10);
  4263.       w = w - bw - pw;
  4264.     }
  4265.     receipt.setStyle({
  4266.       position:"fixed",
  4267.       zIndex:"1000",
  4268.       left: offset.left + "px",
  4269.       top: offset.top + "px",
  4270.       width: w + "px"});
  4271.     scrollY = scrollY -  2 * receipt.getHeight();
  4272.   }
  4273.   // scroll window to show ensureVisibleElement
  4274.   window.scrollTo(0, scrollY );
  4275. };
  4276.  
  4277. /**
  4278.  * Recursively walks up the frameset stack asking each window to change their
  4279.  * document.domain attribute in anticipation of making a cross-site scripting
  4280.  * call to an LMS integration.
  4281.  *
  4282.  * <p>This should only be called from popup windows, as changing the document.domain
  4283.  * value of a window that is going to be reused later could do surprising things.
  4284.  *
  4285.  * @param domain Domain name shared by the Learn and LMS servers.
  4286.  */
  4287. page.setLmsIntegrationDomain = function( domain )
  4288. {
  4289.   if ( '' == domain )
  4290.   {
  4291.     return;
  4292.   }
  4293.  
  4294.   try
  4295.   {
  4296.     if ( parent.page.setLmsIntegrationDomain )
  4297.     {
  4298.       parent.page.setLmsIntegrationDomain( domain );
  4299.   }
  4300.   }
  4301.   catch ( err ) { /* Ignore */ }
  4302.  
  4303.   document.domain = domain;
  4304. };
  4305.  
  4306. page.refreshTopFrame = function()
  4307. {
  4308.   if ( window.top.nav )
  4309.   {
  4310.     window.top.nav.location.reload();
  4311.   }
  4312. };
  4313.  
  4314. // See BreadcrumbBarRenderer.java for code that calls this method.
  4315. page.rewriteTaskStatusUntilDone = function( spanId, taskId, courseId )
  4316. {
  4317.   var theSpan = $(spanId);
  4318.   if (theSpan)
  4319.   {
  4320.     new Ajax.Request("/webapps/blackboard/execute/getSystemTaskStatus?taskId=" + taskId + "&course_id=" + courseId ,
  4321.                      {
  4322.                        method: 'post',
  4323.                        onSuccess: function(transport, json)
  4324.                        {
  4325.                          var result = transport.responseText.evalJSON( true );
  4326.                          theSpan = $(spanId); // reload it just in case it was removed between the request and response
  4327.                          if (theSpan)
  4328.                          {
  4329.                            theSpan.update(result.text);
  4330.                            if (result.complete == "false")
  4331.                            {
  4332.                              setTimeout(function() {page.rewriteTaskStatusUntilDone(spanId, taskId, courseId);}, 3000);
  4333.                            }
  4334.                          }
  4335.                        },
  4336.                        onFailure: function(transport, json)
  4337.                        {
  4338.                          theSpan = $(spanId); //reload the span as above
  4339.                          if (theSpan)
  4340.                          {
  4341.                            theSpan.hide();
  4342.                            $(spanId+'error').show();
  4343.                          }
  4344.                        }
  4345.                      });
  4346.   }
  4347. };
  4348.  
  4349. /*
  4350.  * Clean up the task id which associated with the specified course, so that the inline warning does not show up again
  4351.  */
  4352. page.cleanTaskId = function( courseId )
  4353. {
  4354.   // we don't care about the result, at worse it will display again on the next page
  4355.   var url = "/webapps/blackboard/execute/courseMain?action=cleanTaskId&course_id=" + courseId +
  4356.             "&sessionId=" + getCookie( 'JSESSIONID' );
  4357.   new Ajax.Request( url, { method: 'post' } );
  4358. };
  4359.  
  4360. //that doesn't then any code utilizing these methods will not work 'as expected'. Current usage
  4361. //as/of the writing of this code is "ok" with that - the user won't get the perfect experience but it won't completely fail either.
  4362. page.putInSessionStorage = function( key, value )
  4363. {
  4364.   if ( typeof sessionStorage !== 'undefined' )
  4365.   {
  4366.     sessionStorage[ getCookie( 'JSESSIONID' ) + key ] = value;
  4367.   }
  4368. };
  4369.  
  4370. // any code utilizing these methods must have separately included cookie.js
  4371. // since we don't always include cookie.js
  4372. page.getFromSessionStorage = function( key )
  4373. {
  4374.   if ( typeof sessionStorage !== 'undefined' )
  4375.   {
  4376.     return sessionStorage[ getCookie( 'JSESSIONID' ) + key ];
  4377.   }
  4378.   return undefined;
  4379. };
  4380.  
  4381. page.aria = {};
  4382.  
  4383. page.aria.show = function ( element )
  4384. {
  4385.   $(element).show();
  4386.   element.setAttribute("aria-expanded", "true");
  4387. };
  4388.  
  4389. page.aria.hide = function ( element )
  4390. {
  4391.   $(element).hide();
  4392.   element.setAttribute("aria-expanded", "false");
  4393. };
  4394.  
  4395. page.aria.toggle = function ( element )
  4396. {
  4397.   if (Element.visible($(element)))
  4398.   {
  4399.     page.aria.hide(element);
  4400.   }
  4401.   else
  4402.   {
  4403.     page.aria.show(element);
  4404.   }
  4405. };
  4406.  
  4407. }
Advertisement
Add Comment
Please, Sign In to add comment