/////////////////////////////////////////////////////////////////////////////////
//
// File: base.js
// Defines:
// Dependencies:
// Description: this is the base framework module. This must be the first JS
//              file; all other framework files depend on the prototypes defined
//              within.
//
// Copyright (c) 2006 by Microsoft Corporation. All rights reserved.
// 
/////////////////////////////////////////////////////////////////////////////////
Function.prototype.addMethod = function(name,func) 
{
  // if the function doesn't already exist
  if ( !this.prototype[name] )
  {
    // add it now
    this.prototype[name] = func;
  }
  // return this to allow chaining
  return this;
};
Function.addMethod("as", function(ns,isSingleton) 
{
  // split the namespace string on the periods
  var chain = (ns ? ns.split('.') : []);
  // if the array is empty, then nothing to do
  if ( chain.length > 0 )
  {
    // the root is the window object
    var base = window;
    // start with the first stop with the second-to-last portion
    // in the namespace chain
    for(var ndx = 0; ndx < chain.length - 1; ++ndx)
    {
      // the token should not be empty -- skip it if it is
      var token = chain[ndx];
      if ( token )
      {
        // if the namespace object for this token doesn't
        // already exist, then we need to create it now
        if ( !base[token] )
        {
          // make it an empty object
          base[token] = {};
        }
        // walk down the chain
        base = base[token];
      }
    }
    // the last namespace in the chain is a new instance
    // of this object
    base[chain.last()] = (isSingleton ? new this() : this);
  }
  return this;
});
Function.addMethod("ns",function(ns)
{
  // just define this function as the namespace, but as a singleton
  // (just a shortcut for readability purposes)
  this.as(ns,1);
});

String.addMethod("trim",function()
{
  // trim off all leading and trailing spaces
  return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/,"$1");
});
String.addMethod("collapse", function()
{
  // trim off all leading and trailing spaces, and collapse all instances
  // of whitespace to a single space character
  return this.replace(/\s+/g,' ').trim();
});
String.addMethod("wrap", function(delim)
{
  // return the text wrapped in the delimeter
  var close, delims = {"(":")", "{":"}", "[":"]", "<":">", "«":"»", "‹":"›", "“":"”", "‘":"’"};
  if ( delims[delim] )
  {
    close = delims[delim];
  }
  else
  {
    var m = (/^<(\w+)(\s+\w+\s*=\s*"[^"]*")*\s*>$/).exec( delim );
    if ( m )
    {
      close = "</" + m[1] + ">";
    }
  }
  return delim + this + (close ? close : delim);
});
String.addMethod("format",function()
{
  // use this string as the format
  var fmt = this;
  // walk through each argument passed in
  for(var ndx=0; ndx < arguments.length; ++ndx) 
  {
    // replace {0} with argument[0], {1} with argument[1], etc.
    fmt = fmt.replace( new RegExp('\\{' + ndx + '\\}',"g"), arguments[ndx] );
  }
  // return the formatted string
  return fmt;
});

Array.addMethod("last",function()
{
  // return the last element in the array, 
  // or "undefined" if the array is empty
  return (this.length > 0 ? this[this.length-1] : void(0));
});
Array.addMethod("remove",function(obj)
{
  // walk backwards because we might be removing items
  for(var ndx=this.length-1; ndx >= 0; --ndx) 
  {
    // if this element is the same as the argument...
    if ( this[ndx] === obj ) 
    {
      // splice it out now
      this.splice(ndx, 1);
    }
  }
  return this;
});
Array.addMethod("contains", function(obj)
{
  // walk all the items in the array
  for(var ndx=0; ndx < this.length; ++ndx)
  {
    // check to see if this item is the exact same (no conversion)
    // as the object passed to us
    if ( this[ndx] === obj )
    {
      // we can bail as soon as we find it
      return 1;
    }
  }
  // if we get here, we didn't find it
  return 0;
});

// these methods are typically already defined on more-recent browsers
// but we'll try to add them in case this is an older browser that doesn't
// support them (since we use them).
// examples are IE 5.0 or earlier, IE for the Mac, etc.
Array.addMethod("push",function(obj)
{
  // add the object to the end of the array
  this[this.length] = obj;
  // return the new array length
  return this.length;
});
Array.addMethod("shift",function()
{
  // splice returns an array of the deleted items, so delete one item
  // from the head, and return the one element in the returned array
  return this.splice(0,1)[0];
});
Array.addMethod("splice",function(start,delCount)
{
  // we might be adding new elements -- calculate how many were passed in (if any)
  var delta;
  var addCount = arguments.length - 2;
  // verify some values based on the length of the array
  // can't start past the end of the array
  if ( start > this.length )
  {
    start = this.length;
  }
  // can't delete more items than we have
  if ( start + delCount > this.length )
  {
    delCount = this.length - start;
  }
  // create the array of deleted items (if any)
  var deleted = [];
  for(var ndx = 0; ndx < delCount; ++ndx)
  {
    deleted.push(this[start+ndx]);
  }
  if ( addCount > delCount )
  {
    // have to push out elements to make room for added items
    delta = addCount - delCount;
    for(ndx = this.length + delta - 1; ndx >= start + delta; --ndx)
    {
      this[ndx] = this[ndx - delta];
    }
    // don't have to adjust the length when adding -- gets done 
    // automatically by adding items beyond the old length
  }
  else if ( addCount < delCount )
  {
    // pushing in elements because more deleted than added
    delta = delCount - addCount;
    for(ndx = start + addCount; ndx < this.length - delta; ++ndx)
    {
      this[ndx] = this[ndx + delta];
    }
    // delete the excess items starting where we left off
    for(; ndx < this.length - 1; ++ndx)
    {
      delete this[ndx];
    }
    // adjust the length
    this.length -= delta;
  }
  // if we're adding items, add them now
  for(ndx = 0; ndx < addCount; ++ndx )
  {
    this[start+ndx] = arguments[2+ndx];
  }
  return deleted;
});

/////////////////////////////////////////////////////////////////////////////////
//
// File: dom.js
// Defines: Msn.DOM
// Dependencies: base.js
// Description: implements framework DOM functionality. Common DOM-walking
//              methods and event hooking.
//
// Copyright (c) 2006 by Microsoft Corporation. All rights reserved.
// 
/////////////////////////////////////////////////////////////////////////////////
(function()
{
  // shortcut
  var dom = this;
  
  Function.addMethod("hook",function(element,eventName)
  {
    if ( element )
    {
      var isSafari = checkSafari();
      if ( !isSafari && element.addEventListener ) 
      {
        // if the browser supports the w3c model, use it
        element.addEventListener( eventName, this, false );
      }
      else if ( !isSafari &&  element.attachEvent ) 
      {
        // if the browser supports the IE model, use it
        element.attachEvent( 'on' + eventName, this );
      }
      else 
      {
        // normally we'd just assign to "on"+eventName, but let's try
        // to fake it by setting up an array of event handlers as a property
        // on the element. Then we'll set the real event handler to a function
        // that walks that array, calling each of the handlers. If any of the handlers
        // returns false, we stop walking and return false. Otherwise we'll return true.
        //
        // see if there's already an event handler for this event
        var handlers = element["x"+eventName];
        if ( handlers && handlers.constructor == Array )
        {
          // yes -- add this new function to the list if it isn't already
          if ( handlers.contains(this) )
          {
            // already there -- null the handler variable so we do nothing
            handlers = null;
          }
          else
          {
            // add it
            handlers.push(this);
          }
        }
        else
        {
          // no -- create a new array with this function as the only item
          handlers = element["x"+eventName] = [this];
        }
        if ( handlers )
        {
          // set the real handler to be a custom function
          element['on' + eventName] = function(ev)
          {
            var returnValue = true;
            // the event object
            ev = dom.Event(ev);
            // walk the array
            for(var ndx=0; ndx < handlers.length; ++ndx)
            {
              // call the handler
              var handlerReturn = handlers[ndx](ev);
              if ( typeof handlerReturn != "undefined" && !handlerReturn )
              {
                // return false to cancel the event and stop calling handlers
                returnValue = false;
              }
            }
            // everything was fine and dandy
            return returnValue;
          };
          // the above closure will leak memory under IE.
          // null out the element reference and the leak won't happen.
          element = null;
        }
      }
    }
    return this;
  });
  
  Function.addMethod("unhook",function(element,eventName)
  {
    if ( element )
    {
      var isSafari = checkSafari();
      if ( !isSafari && element.removeEventListener ) 
      {
        // if the browser supports the w3c model, use it
        element.removeEventListener( eventName, this, false );
      }
      else if ( !isSafari && element.detachEvent ) 
      {
        // if the browser supports the IE model, use it
        element.detachEvent( 'on' + eventName, this );
      }
      else 
      {
        // see if we've set up an array of handlers on a property of this element
        var arr = element["x"+eventName];
        if ( arr && arr.constructor == Array )
        {
          // remove this function from the array
          arr.remove( this );
        }
        else
        {
          // just set the handler to be null, just in case
          element["on"+eventName] = null;
        }
      }
    }
    return this;
  });
  
  // call this method in your handlers in order to cancel an event 
  // and prevent bubbling
  dom.CancelEvent = function( ev ) 
  {
    // if no event is passed, pull the event from the window object
    ev = dom.Event(ev);
    if ( ev ) 
    {
      // this cancels the bubble in order to prevent the
      // event from bubbling up the dom chain
      // (eg: OOB script won't log link click)
      ev.cancelBubble = true;
      if ( ev.stopPropagation ) 
      {
        ev.stopPropagation();
      }
      
      // this sets the return value to false and stops the
      // default action from occurring
      // (eg: clicks on links won't navigate)
      ev.returnValue = false;
      if ( ev.preventDefault ) 
      {
        ev.preventDefault();
      }
    }
    // return false in case we want to use in an onclick property
    return false;
  };
  
  // this function is pretty easily coded, but once it's crunched, it
  // will actually save a few bytes for every time it's used.
  dom.Event = function(ev)
  {
    return (ev ? ev : window.event);
  };
  
  // this function is a shortcut for getting the browser-independent
  // source element from the event
  dom.Target = function(ev)
  {
    // typically this is done in the calling code, but just in case...
    ev = dom.Event(ev);
    
    // get the browser-independent target property
    var target = (ev.target ? ev.target : ev.srcElement);
    
    // some browsers (like Safari) might give us the actual text element
    // that was clicked. But we want to return an element, so if the
    // target isn't an element...
    if( target && target.nodeType != 1 )
    {
      // get the first parent element from the tree
      target = dom.ParentElem( target );
    }
    return target;
  };

  // browser-neutral innerText method.
  // if the browser supports the innerText property, return it;
  // otherwise calculate the inner text by walking the dom nodes.
  dom.InnerText = function( el ) 
  {
    var text = '';
    //for each child of the node    
    for (var ndx=0 ; ndx < el.childNodes.length; ndx++)
    {
      var child = el.childNodes[ndx];
      if (child.nodeType == 1) 
      {
        // recurse child element nodes, adding their text to the string we're building
        text += dom.InnerText(child);
      }
      else if (child.nodeType == 3)
      {
        // just add the value of the text nodes to the string we're building
        text += child.data;
      }
    }
    return text;	
  };
  
  // given an element, returns the next sibling element in the dom,
  // or null if there is none
  // if tagName is specified, it will keep looking until it finds an element of that tag name
  dom.NextElem = function( element, tagName ) 
  {
    var nextElement = element.nextSibling;
    while( nextElement && (nextElement.nodeType != 1 || (tagName && nextElement.nodeName != tagName)) ) 
    {
      nextElement = nextElement.nextSibling;
    }
    return nextElement;
  };
  
  // given an element, returns the previous sibling element in the dom,
  // or null if there is none
  // if tagName is specified, it will keep looking until it finds an element of that tag name
  dom.PrevElem = function( element, tagName ) 
  {
    var prevElement = element.previousSibling;
    while( prevElement && (prevElement.nodeType != 1 || (tagName && prevElement.nodeName != tagName)) ) 
    {
      prevElement = prevElement.previousSibling;
    }
    return prevElement;
  };
 
  // get the fist element up the dom tree from element.
  // if tagName is specified, it will keep looking until it finds an element of that tag name
  dom.ParentElem = function( element, tagName ) 
  {
    var parentNode = element.parentNode;
    while( parentNode && (parentNode.nodeType != 1 || (tagName && parentNode.nodeName != tagName)) ) 
    {
      parentNode = parentNode.parentNode;
    }
    return parentNode;
  };
  
  // get the first child element under node parentNode.
  // if optional parameter, tagName, is specified, keep looking
  // until we find the first child with that tag name
  // if immediate is non-zero, only the immediate children are checked
  dom.ChildElem = function( parentNode, tagName, immediate ) 
  {
    var element = null, childNode;
    // check all our child elements
    for(var ndx=0; !element && ndx < parentNode.childNodes.length; ++ndx) 
    {
      childNode = parentNode.childNodes[ndx];
      if ( childNode.nodeType == 1 ) 
      {
        // child is an element. if we are not looking for a particular tag name,
        // or if we are and they match, then we are done
        if ( !tagName || childNode.nodeName == tagName ) 
        {
          // save this element and pop out of the recursion
          element = childNode;
        }
      }
    }
    if ( !immediate )
    {
      // now recurse each of the children if we haven't found anything yet
      for(ndx=0; !element && ndx < parentNode.childNodes.length; ++ndx)
      {
        // check to make sure it's an element
        childNode = parentNode.childNodes[ndx];
        if ( childNode.nodeType == 1 )
        {
          // it is -- recurse it
          element = dom.ChildElem( childNode, tagName );
        }
      }
    }
    return element;
  };
  
  // for each immediate child of the parent node
  // (optionally only those with the supplied tag name),
  // call the function provided, passing in the node as the parameter
  dom.ForEach = function( func, parent, tagName )
  {
    for(var ndx = 0; ndx < parent.childNodes.length; ++ndx)
    {
      var child = parent.childNodes[ndx];
      if ( child.nodeType == 1 && (!tagName || child.nodeName == tagName) )
      {
        if ( func( child ) )
        {
          break;
        }
      }
    }
  };
  
  // returns the number of child elements for the given node
  // if nodeName is supplied, it will only count those child nodes with the given name
  // returns the new className for the element
  dom.ChildCount = function( element, nodeName )
  {
    var count = 0;
    var ndx,child;
    for(ndx=0; ndx < element.childNodes.length; ++ndx)
    {
      child = element.childNodes[ndx];
      count += (child.nodeType == 1 && (!nodeName || child.nodeName == nodeName) ? 1 : 0);
    }
    return count;
  }
  
  // add the specified class name to the element's list of classes
  // if it isn't already there
  dom.AddClass = function( element, className )
  {
    // get the current classes
    var originalValue = element.className;
    if ( originalValue )
    {
      // split on spaces (after collapsing all the whitespace)
      var originalClasses = originalValue.collapse().split(' ');
      var newClasses = className.collapse().split(' ');
      for( var ndx = 0; ndx < newClasses.length; ++ndx )
      {
        var newClass = newClasses[ndx];
        // if the array doesn't contain the given class name...
        if ( !originalClasses.contains( newClass ) )
        {
          // add it to the end
          element.className += ' ' + newClass;
        }
      }
    }
    else
    {
      // nothing there now -- just set the new one
      element.className = className;
    }
    return element.className;
  };
  
  // if the element has a class name in its class list, remove it.
  // returns the new className for the element
  dom.DelClass = function( element, className )
  {
    // get the current classes
    var originalValue = element.className;
    // if it's blank, there's nothing to do
    if ( originalValue )
    {
      // collapse all the whitespace, split on the space, and remove
      // the given class name from the array (if it exists)
      var classes = originalValue.collapse().split(' ');
      var oldClasses = className.collapse().split(' ');
      for(var ndx = 0; ndx < oldClasses.length; ++ndx)
      {
        classes.remove( oldClasses[ndx] );
      }
      // caluclate the new value, separated with a space
      var newValue = classes.join(' ');
      // if the newvalue is different from the original value...
      if ( newValue != originalValue )
      {
        // change it
        element.className = newValue;
      }
    }
    return element.className;
  };
  
  // returns true if the element has the given className in its list
  dom.HasClass = function( element, className )
  {
    return element.className.collapse().split(' ').contains( className );
  };
  
  // call this function whenever you update the DOM.
  // This function expects some other code to add an Impl function
  // to it for a site-specific implementation of accessibility notification.
  // This method is mainly provided as a stub that is always present, but can
  // use multiple implmentations for specific sites in the future, while allowing
  // other code to remain unchanged.
  dom.Updated = function()
  {
    // if some other code has implemented accessibility feature for the DOM...
    if (dom.Access && typeof dom.Access.Updated == 'function')
    {
      // let's call into it
      dom.Access.Updated();
    }
  };
  
  function checkSafari()
  {
    return (navigator.userAgent.indexOf("Safari") >= 0);
  }
  
}).ns("Msn.DOM");

/////////////////////////////////////////////////////////////////////////////////
//
// File: bind.js
// Defines: Msn.Bind
// Dependencies: base.js
// Description: framework implementation of binding.
//              "bind" method can bind to an element reference, an array of 
//              element references, the results of getElementsByTagName, or
//              a string representing a CSS selector.
//
// Copyright (c) 2006 by Microsoft Corporation. All rights reserved.
// 
/////////////////////////////////////////////////////////////////////////////////
(function()
{
  // shortcut
  var bind = this;
  
  // this is the binding array -- every time we create a binding, we also
  // add a reference to it here. The unload event is hooked to loop through
  // this array and call the dispose method (if it exists) on each binding.
  var allBindings = [];
  
  // bind an object of type, typ, to the DOM element(s) specified by
  // the selector, sel.
  Function.addMethod("bind",function( sel, args ) 
  {
    var elements;
    switch(typeof sel)
    {
      case 'object':
        // could be an element or an array of elements.
        // if this is an element, we'll have the nodeType property and it will be 1.
        // if this is the document object, we'll have a nodeType property of 9.
        // if we are an element, create an array with the element as the one item.
        // if we have a length property, then assume we are some sort of array
        // otherwise just return null becasue we don't know what it is
        elements = (sel.nodeType == 1 || sel.nodeType == 9) ? [sel] : (sel.length ? sel : null);
        break;
      case 'string':
        // a string should be a css selector that we can
        // evaluate to return an array of elements
        elements = bind.Select( sel );
        break;
    }
    if ( elements ) 
    {
      // walk each element...
      for(var ndx = 0; ndx < elements.length; ++ndx) 
      {
        var element = elements[ndx];
        // create a new binding object, passing in the element we are binding to,
        // and the parameters object and add the binding to our reference array
        // so we can dispose of it during the unload event
        var binding = new this( element, args );
        if ( element.bindings ) 
        {
          element.bindings.push( binding );
        }
        else 
        {
          element.bindings = [ binding ];
        }
        allBindings.push( binding );
      }
    }
    return this;
  });
  
  // unbind any existing bindings from the given element, el.
  // if r is true, all child elements of el are recursively unbound.
  bind.Unbind = function( element, recurse ) 
  {
    var ndx;
    // if there are any bindings on this element
    if ( element.bindings && element.bindings.length ) 
    {
      // walk the array backwards because we might decide we
      for(ndx=0; ndx < element.bindings.length; ++ndx) 
      {
        var binding = element.bindings[ndx];
        // if there is a dispose method on the binding, call it
        if ( binding && typeof binding.dispose == 'function' ) 
        {
          binding.dispose();
        }
        // remove from the global bindings array
        allBindings.remove( binding );
      }
      // clear the array
      element.bindings = null;
    }
    // if we want to recursively unbind...
    if( recurse ) 
    {
      // for each child node of this element...
      for(ndx=0; ndx < element.childNodes.length; ++ndx) 
      {
        var child = element.childNodes[ndx];
        // if the child is an element...
        if ( child.nodeType == 1 ) 
        {
          // recurse.
          bind.Unbind( child, recurse );
        }
      }
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  //
  // this method takes a css-style selector and returns an array of elements
  // within the DOM that satisfy that criteria.
  // The selector should follow this pattern:
  //     selector:   simpsel [ combinator? simpsel ]*
  //     combinator: [ '>' | '+' | <empty> ]
  //     simpsel:    element? hash? class*
  //     element:    ident
  //     hash:       '#' ident
  //     class:      '.' ident
  //     ident:      [a-zA-Z0-9-]+
  // at least one of the three elements of the simpsel must be present.
  //
  /////////////////////////////////////////////////////////////////////////////

  // given a css selector, returns an array of elements
  // within the document dom that match that selector
  bind.Select = function( cssSelector ) 
  {
    // read an identifier from the selector at the current location
    function getIdentifier() 
    {
      var identifier = null;
      if ( cssSelector ) 
      {
        // an asterisk means all elements
        if ( cssSelector.charAt(pos) == '*' ) 
        {
          identifier = '*';
        }
        else 
        {
          // loop until the end of the string.
          // will break out of the loop when encountering a non-identifier char
          while( pos < cssSelector.length ) 
          {
            var ch = cssSelector.charAt( pos );
            // we allow a-z, A-Z, 0-9, and a hyphen.
            // Underscores are not allowed.
            if ( ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9') || ch == '-' ) 
            {
              // add the character to the identifier string
              identifier = (identifier ? identifier + ch : ch);
              ++pos;
            }
            else 
            {
              // not a valid ident character -- we're done
              break;
            }
          }
        }
      }
      return identifier;
    }
    // skip over any whitespace at the currrent selector position
    function skipSpace() 
    {
      while( pos < cssSelector.length && cssSelector.charAt(pos) == ' ' ) 
      {
        ++pos;
      }
    }
    // return a simple selector combinator character, if any
    function getCombinator() 
    {
      var combinator = null;
      // skip any space
      skipSpace();
      // check the current character for an allowed combinator
      switch( cssSelector.charAt(pos) ) 
      {
        case '+':
        case '>':
          // this is an allowed combinator
          combinator = cssSelector.charAt(pos);
          ++pos;
          // skip any space after the combinator
          skipSpace();
          break;
      }
      return combinator;
    }
    // get a hash or class from the selector stream
    function getHashOrClass() 
    {
      // skip over the # or the . character -- we don't want it
      ++pos;
      // just return the character part
      return getIdentifier();
    }
    // get a simple selector from the selector stream.
    // return null if no selector on the stream
    function getSimpleSelector() 
    {
      var selector = null;
      // see if there's an element identifier first
      var element = getIdentifier();
      if ( element !== null ) 
      {
        // there is -- create a simple selector object from it
        selector = new SimpSelector( element );
      }
      // loop while we still have characters to read. 
      // we will break out of the loop if we encounter a
      // character that doesn't make a simple selector
      while( cssSelector && pos < cssSelector.length ) 
      {
        var ch = cssSelector.charAt(pos);
        if ( ch == '#' ) 
        {
          // ids are prefaced with a hash character
          // if we don't already have a simple selector object,
          if ( !selector ) 
          {
            // create it now with no element
            selector = new SimpSelector();
          }
          // set the id property on the simple selector object.
          // there SHOULD only be a single id per simple selector
          // TODO: if (sel.id) error!
          selector.setID( getHashOrClass() );
        }
        else if ( ch == '.' ) 
        {
          // classes are prefaced with a period character
          // if we don't already have a simple selector object,
          if ( !selector ) 
          {
            selector = new SimpSelector();
          }
          // add the class to the simple selector.
          // add the class to the simple selector. There can be multiple
          // per simple selector
          selector.addClass( getHashOrClass() );
        }
        else 
        {
          // unexpected character; break out of the loop
          break;
        }
      }
      return selector;
    }
    // get the full, complex selector, represented as an array of
    // simple selector objects.
    function getSelectors() 
    {
      // initially empty array
      var selectors = [];
      // get the first selector (if any)
      var simpleSelector = getSimpleSelector();
      if ( simpleSelector ) 
      {
        // add the simple selector to the array
        selectors.push(simpleSelector);
        // loop while we still have characters in the selector stream.
        // we will break out when we can no longer find a simple selector
        while( pos < cssSelector.length ) 
        {
          // check for a combinator
          var combinator = getCombinator();
          // get the next simple selector
          simpleSelector = getSimpleSelector();
          if ( simpleSelector ) 
          {
            // if there was a combinator before this selector,
            if ( combinator ) 
            {
              // add the combinator to the new selector's properties
              simpleSelector.setComb( combinator );
            }
            // push the new selector onto the array
            selectors.push( simpleSelector );
          }
          else 
          {
            // no more simple selectors; bail
            // TODO: if (comb) error!
            break;
          }
        }
      }
      return selectors;
    }
    
    // simple selector object
    // element? [ hash | class ]*
    // there can be multiple classes for each simple selector,
    // but there really should only be one id
    function SimpSelector( element ) 
    {
      // shortcut
      var simp = this;
      
      // fields
      var id = '';
      var combinator = null;
      var classes = null;
      
      // method to set the id for this selector
      simp.setID = function(idValue)
      {
        id = idValue;
      };
      
      // set the combinator
      simp.setComb = function(comb)
      {
        combinator = comb;
      };
      
      // add a class to the array
      simp.addClass = function( className ) 
      {
        if ( classes ) 
        {
          // push the new class to the end of the existing array
          classes.push( className );
        }
        else 
        {
          // no class array yet; add this first class in a new array
          classes = [ className ];
        }
      };
      
      // return a list of nodes under parent that match this simple selector
      simp.getNodes = function( parent ) 
      {
        var ndx, node, nextElement, nodeList = [];
        if ( id ) 
        {
          // there's an id -- only one node is possible
          switch ( combinator ) 
          {
            case '>':
              // we only want to check the direct children
              for(ndx=0; ndx < parent.childNodes.length; ++ndx) {
                // if the child is an element, and the id matched what we're looking for...
                if ( parent.childNodes[ndx].nodeType == 1 && parent.childNodes[ndx].id == id ) 
                {
                  // we'll save this element for further tests (element and class)
                  node = parent.childNodes[ndx];
                  break;
                }
              }
              break;
            case '+':
              // we only want the next sibling node
              nextElement = getNextElement( parent );
              // and it has to match the desired id
              if ( nextElement && nextElement.id == id ) 
              {
                // save this element for further tests (element and class)
                node = nextElement;
              }
              break;
            default:
              // any child anywhere under the parent
              node = parent.getElementById( id );
              break;
          }
          // if there is a specified element and it's not a star wildcard, it must match
          // (convert to lower-case, because nodeName is in upper-case for some weird reason).
          // and the classes must match. 
          if ( node && (!element || element == '*' || element.toLowerCase() == node.nodeName.toLowerCase()) && checkClasses(node) ) 
          {
            // this is a selected node; add it to the array
            nodeList.push( node );
          }
        }
        else if ( element && element != '*' ) 
        {
          // no id, but there is a specific element we're after
          switch( combinator ) 
          {
            case '>':
              // we only want direct children
              for(ndx=0; ndx < parent.childNodes.length; ++ndx) 
              {
                node = parent.childNodes[ndx];
                // which match our tag names and our classes
                if ( node.nodeType == 1 && node.nodeName.toLowerCase() == element && checkClasses(node) ) 
                {
                  // this node is selected
                  nodeList.push( node );
                }
              }
              break;
            case '+':
              // just the next sibling
              nextElement = getNextElement( parent );
              // and only if the tag name matches and the classes check out
              if ( nextElement && nextElement.nodeName.toLowerCase() == element && checkClasses(nextElement) ) 
              {
                // this node is selected
                nodeList.push( nextElement );
              }
              break;
            default:
              // any subelements at any depth
              var elements = parent.getElementsByTagName( element );
              // test the classes on each one, adding matching elements to the selected array
              for( ndx = 0; ndx < elements.length; ++ndx ) 
              {
                if ( checkClasses(elements[ndx]) ) 
                {
                  nodeList.push( elements[ndx] );
                }
              }
              break;
          }
        }
        else 
        {
          // no id and no element, just a class.
          switch( combinator ) 
          {
            case '>':
              // we only want direct children
              for(ndx=0; ndx < parent.childNodes.length; ++ndx) 
              {
                node = parent.childNodes[ndx];
                // which match our tag names
                if ( node.nodeType == 1 && checkClasses(node) ) 
                {
                  nodeList.push( node );
                }
              }
              break;
            case '+':
              // just the next sibling
              nextElement = getNextElement( parent );
              // and only if the tag name matches
              if ( nextElement && checkClasses(nextElement) ) 
              {
                nodeList.push( nextElement );
              }
              break;
            default:
              // easiest just to recursively walk the dom node tree
              // looking for nodes that match our desired classes
              checkNodeClasses( parent, nodeList );
              break;
          }
        }
        return nodeList;
      };
      
      function checkNodeClasses( parent, nodes ) 
      {
        // for each child node....
        for( var ndx=0; ndx < parent.childNodes.length; ++ndx ) 
        {
          var node = parent.childNodes[ndx];
          // if this is an element node...
          if ( node.nodeType == 1 ) 
          {
            // if the classes check out, then add it to the match list
            if ( checkClasses(node) ) 
            {
              nodes.push( node );
            }
            // recursively walk this element node
            checkNodeClasses( node, nodes );
          }
        }
      }
      
      function checkClasses( element ) 
      {
        var okay = 1;
        if ( classes ) 
        {
          var className = element.className;
          if ( className ) 
          {
            // make an array of which classes we have on the node
            var classNames = className.collapse().split(' ');
            // each class we want to match must be available or we're not okay
            for(var ndx=0; ndx < classes.length; ++ndx) 
            {
              // if the available class array doesn't contain the wanted class...
              if ( !classNames.contains(classes[ndx]) ) 
              {
                // then the check fails
                okay = 0;
                break;
              }
            }
          }
          else 
          {
            // matching classes, but none on the node
            okay = 0;
          }
        }
        return okay;
      }
    }
    
    // take the array of elements and apply the given simple selector to
    // return another array of elements
    function applySelector( elements, simpleSelector ) 
    {
      // initially empty array
      var matchedElements = [];
      // for each element in the source list...
      for(var ndx = 0; ndx < elements.length; ++ndx) 
      {
        // add the elements selected by the selector on this element
        matchedElements = matchedElements.concat( simpleSelector.getNodes( elements[ndx] ) );
      }
      return matchedElements;
    }
    
    // start at the beginning of the selector
    var pos = 0;
    // get the array of simple selectors from the selector string
    var sels = getSelectors();
    // initially we'll start with the entire document
    var elements = [document];
    // for each simple selector, we'll apply the selector to the current list
    // of elements, then apply the next selector on the results, etc.
    for(var ndx = 0; ndx < sels.length && elements.length > 0; ++ndx) 
    {
      elements = applySelector( elements, sels[ndx] );
    }
    return elements;
  };

  // given an element, returns the next sibling element in the dom,
  // or null if there is none
  function getNextElement( element ) 
  {
    var nextElement = element.nextSibling;
    while( nextElement && nextElement.nodeType != 1 ) 
    {
      nextElement = nextElement.nextSibling;
    }
    return nextElement;
  }
  
  
  /////////////////////////////////////////////////////////////////////////////
  //
  // this function is automatically hooked into the unload event of the window
  // to handle calling the dispose method of all our bindings
  //
  /////////////////////////////////////////////////////////////////////////////

  // this function walks through all the bindings we may have
  // on our page and calls the dispose method (if there is one).
  (function() 
  {
    // make sure all elements are unbound
    bind.Unbind( document, 1 );
    // make sure the global bindings are empty
    allBindings = [];
  }).hook(window,"unload");
  
}).ns("Msn.Bind");

/////////////////////////////////////////////////////////////////////////////////
//
// File: header.js
// Defines: Msn.Header
// Dependencies: base.js bind.js dom.js
// Description: implements the javascript routines for the header
//
// Copyright (c) 2006 by Microsoft Corporation. All rights reserved.
// 
/////////////////////////////////////////////////////////////////////////////////
(function(el, args)
{
  if ( !args ) { args = {}; }
      
  var dom = Msn.DOM;
  var d = document;
  var w = window;

  //hide the network navigation more links
  var elMoreDIV = d.getElementById( "more" ); // div containing network navigation links
  elMoreDIV.style.display = "none";
  
  //create more Link
  var elMoreUL = d.getElementById( "xnav" );
  var elMoreLI = d.createElement( "li" );
  var elMoreA = d.createElement( "a" );
  elMoreLI.className = "more";
  elMoreA.href = "#";
  elMoreA.className = "expand";
  elMoreA.innerHTML = argWithDefault(args.more,"more");
  elMoreLI.appendChild( elMoreA );
  elMoreUL.appendChild( elMoreLI );
  
  //bind toggle function to click event of more link
  toggle.hook(elMoreA, "click");

  // click event for more link, toggles display of cross-network navigation div
  function toggle(ev)
  {
    var state = elMoreDIV.style.display;
    var expand;
    if (state == "block") 
    {
        state = "none";
        expand = "expand";
        elMoreLI.className = "more";
    }
    else 
    {
        state = "block";
        expand = "collapse";
        elMoreLI.className = "last";
    } 

    elMoreDIV.style.display = state;
    elMoreA.className = expand;
    
    ev = dom.Event(ev);
    return dom.CancelEvent( ev );
  }

  this.dispose = function()
  {
    el = null;
    elMoreDIV = null;
    elMoreUL = null;
    elMoreLI = null;
    elMoreA = null;
  };
  
  function argWithDefault( arg, def )
  {
    return (typeof arg != "undefined" ? arg : def)
  }

}).as("Msn.Header");


(function(el, args)
{
  if ( !args ) { args = {}; }
      
  var dom = Msn.DOM;
  var d = document;
  var w = window;

  var searchParam = argWithDefault(args.searchParam,"");
  var searchParams = argWithDefault(args.searchParams,"");
  var searchText = argWithDefault(args.searchSite,"");
  var searchUrl = argWithDefault(args.searchUrl,"");
  var searchWeb = argWithDefault(args.searchWeb,"");

  if (searchText !== "")
  {
      //create site search button
      var elSiteSearch = d.getElementById( "sitesearch" );
      var elSiteInput = d.createElement( "input" );
      elSiteInput.className = "button";
      elSiteInput.id = "site";
      elSiteInput.name = "site";
      elSiteInput.type = "submit";
      elSiteInput.value = searchText;
      elSiteSearch.appendChild( elSiteInput );

      //update web search value
      var elWebInput = d.getElementById( "web" );
      elWebInput.value = searchWeb;

      doSiteSearch.hook(elSiteInput, "click");
      
      // When more than one input type=submit is present, IE processes enter as a click to the
      // web search button, not the site search button.  We hook doEnter to the keypress event to correct.
      var elSearchText = d.getElementById( "q" );
      doEnter.hook(elSearchText, "keypress");
  }

  function doEnter(ev)
  {
    if(ev.keyCode == 13) 
    {
        doSiteSearch(null);
        ev = dom.Event(ev);
        return dom.CancelEvent(ev);
    }    
  }

  function doSiteSearch(ev)
  {
    if (ev !== null)
    {
        if (dom.Target(ev).id != "site")
            return;
    }
    
    var term = escape(d.getElementById( "q" ).value);
    var url = searchUrl + "?" + searchParam + "=" + term;
    if (searchParams)
    {
        url = url + "&" + searchParams.replace("&amp;","&");
    }    
    window.top.location.href = url;

    ev = dom.Event(ev);
    return dom.CancelEvent(ev);
  }

  this.dispose = function()
  {
    el = null;
  };
  
  function argWithDefault( arg, def )
  {
    return (typeof arg != "undefined" ? arg : def)
  }

}).as("Msn.SiteSearch");