/////////////////////////////////////////////////////////////////////////////////
//
// 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;
});
String.addMethod("encodeHtml",function()
{
var returnString = this.replace(/\>/g, "&gt;").replace(/\</g, "&lt;").replace(/\&/g, "&amp;").replace(/\'/g, "&#039;").replace(/\"/g, "&quot;");
return returnString;
});
String.addMethod("decodeHtml",function()
{
var returnString = this.replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&").replace(/&#039;/g, "'").replace(/&quot;/g, '"');
return returnString;
});
String.addMethod("encodeURIComponent",function() {
return (typeof encodeURIComponent != "undefined" ? encodeURIComponent(this) : escape(this));
});
String.addMethod("decodeURIComponent",function() {
return (typeof decodeURIComponent != "undefined" ? decodeURIComponent(this) : unescape(this));
});
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" );
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 = "";
}
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 = {}; }
//shortcuts
var dom = Msn.DOM;
var d = document;
var w = window;
var form = el;
//Init variables from args
var siteSearchOn = argWithDefault(args.siteSearchOn, "false");
var searchUrl = argWithDefault(args.searchUrl, "");
var searchParam = argWithDefault(args.searchParam, "");
var searchParams = argWithDefault(args.searchParams, "");
//if site search not enabled, scope default to web
var scope = siteSearchOn == "true" ? "site" : "web";
//create scope tabs if sitesearch is on
if (scope == "site")
{
createScopeTabs();
}
//IE 6 png treatment
var onepxgif = argWithDefault(args.onepxgif, "http://blstc.msn.com/br/gbl/css/8/decoration/t.gif");
var nav = navigator.userAgent, index = nav.indexOf("MSIE");
if (index >= 0)
{
// we have MSIE in the user agent string. parse the float value
// immediately following it.
if (parseFloat(nav.substring(index + 4)) < 7 && parseFloat(nav.substring(index + 4)) > 5)
{
pngfix(d.getElementById("ntwseperator"), "bgimage", "image");
pngfix(d.getElementById("leftcorner"), "bgimage", "image");
pngfix(d.getElementById("searchform"), "bgimage", "scale");
pngfix(d.getElementById("ntwlogo"), "bgimage", "scale");
pngfix(d.getElementById("logoimg"), "img", "image");
pngfix(d.getElementById("rightcorner"), "bgimage", "image");
pngfix(elSelectedArrow, "bgimage", "image");
}
}
// pointer to dom objects
var elSitesearchDiv;
var elSiteLink;
var elWebLink;
var elSelectedArrow;
var elWebDiv;
var elSpyglass = d.getElementById("spyglass");
var elSearchText = d.getElementById("q");
//some varaible to use in functions
var searchTerm = "";
var helperText = (scope == "web") ? argWithDefault(args.helpertext, "") : "";
if (scope == "web")
{
elSearchText.className = "";
}
elSearchText.value = helperText; //clear the inputbox or set to helpertext.
//hook up events
doSpyglass.hook(elSpyglass, "click");
focusTxtbox.hook(elSearchText, "focus");
blurTxtbox.hook(elSearchText, "blur");
var formOnSubmit = form.onsubmit; //grab onsubmit function
form.onsubmit = function()
{
if (elSearchText.value == helperText) { elSearchText.value = ""; }
return !!formOnSubmit ? formOnSubmit(form) : null;
};
// 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.
doEnter.hook(elSearchText, "keypress");
function createScopeTabs()
{
var scopesDiv = d.getElementById("ntwscopes");
var siteText = argWithDefault(args.sitetext, "Search site");
var webText = argWithDefault(args.webtext, "Web");
elSitesearchDiv = d.createElement("div");
elSitesearchDiv.className = "selected";
elSiteLink = d.createElement("a");
elSiteLink.innerHTML = siteText;
elSiteLink.href = searchUrl;
elSelectedArrow = d.createElement("span");
elSelectedArrow.id = "selectedscope";
elSitesearchDiv.appendChild(elSiteLink);
elSitesearchDiv.appendChild(elSelectedArrow);
var seperatorDiv = d.createElement("div");
seperatorDiv.id = "ntwseperator";
elWebDiv = d.createElement("div");
elWebLink = d.createElement("a");
elWebLink.innerHTML = webText;
elWebLink.href = form.action;
elWebDiv.appendChild(elWebLink);
scopesDiv.appendChild(elSitesearchDiv);
scopesDiv.appendChild(seperatorDiv);
scopesDiv.appendChild(elWebDiv);
doSiteSearch.hook(elSiteLink, "click");
doWebSearch.hook(elWebLink, "click");
}
function pngfix(elem, mode, method)
{
if (!elem) return;
var imagesrc;
if (mode == "bgimage")
{
imagesrc = elem.currentStyle.backgroundImage;
}
else if (mode == "img")
{
imagesrc = elem.src;
}
if (imagesrc == "") return;
var pngsrc = imagesrc.search(new RegExp('(http[s]?://.+\\.png)', 'i')) === -1 ? "" : RegExp.$1;
if (pngsrc.length > 0)
{
if (mode == "bgimage")
{
elem.style.backgroundImage = "none";
}
else if (mode == "img")
{
elem.src = onepxgif;
}
elem.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled='true', sizingMethod='" + method + "', src='" + pngsrc + "')";
}
}
function doEnter(ev)
{
if (ev.keyCode == 13)
{
switch (scope)
{   //Use switch for more tabs in the future
//Do not need t a case for web, since it textbox in the form default to submit when enter.                                                                        
case "site":
doSiteSearch(null);
ev = dom.Event(ev);
return dom.CancelEvent(ev); //need to cancel event to stop form submission
break;
}
}
}
function doSiteSearch(ev)
{
var url = searchUrl + "?" + searchParam + "=" + getSearchTerm();
if (searchParams)
{
url = url + "&" + searchParams.replace(/&amp;/g, "&");
}
if (ev && dom.Target(ev).tagName.toLowerCase() == "a")
{
dom.Target(ev).href = url; //set href with search term, so omniture linktrack will have search term tracked.
}
else
{
window.top.location.href = url; //for doSiteSearch(null) to work
}
}
function doWebSearch(ev)
{
if (ev && dom.Target(ev).tagName.toLowerCase() == "a")
{
//in order to track omniture, construct the url from hidden value, instead of doing form submission directly.
//web submission is not working in firefox, so do linktracking.
var elem = dom.Target(ev);
var url = form.action;
for (var cnt = 0; cnt < form.length; cnt++)
{
if (cnt == 0)
{
url += "?";
}
else
{
url += "&";
}
if (form[cnt].name.toLowerCase() == "q")
{
url += form[cnt].name + "=" + getSearchTerm();
}
else
{
url += form[cnt].name + "=" + encodeURIComponent(form[cnt].value);
}
}
elem.href = url;
}
}
function getSearchTerm()
{
searchTerm = elSearchText ? elSearchText.value : "";
if (scope == "web")
{
searchTerm = (searchTerm == helperText) ? "" : searchTerm;
}
return encodeURIComponent(searchTerm);
}
function doSpyglass(ev)
{
switch (scope)
{
case "site":
form.onsubmit = cancelSubmit; //cancel form submit, doSiteSearch instead.
doSiteSearch(null);
break;
}
}
function cancelSubmit(ev)
{
ev = dom.Event(ev);
return dom.CancelEvent(ev);
}
function focusTxtbox(ev)
{
elSearchText.className = "typing";
if (elSearchText.value == helperText)
elSearchText.value = "";
}
function blurTxtbox(ev)
{
if (elSearchText.value.trim() == "")
{
elSearchText.className = "";
elSearchText.value = helperText;
}
}
this.dispose = function()
{   //unhook events to avoid MM leak
if (scope = "site")
{
doSiteSearch.unhook(elSiteLink, "click");
doWebSearch.unhook(elWebLink, "click");
}
doSpyglass.unhook(elSpyglass, "click");
focusTxtbox.unhook(elSearchText, "focus");
blurTxtbox.unhook(elSearchText, "blur");
doEnter.unhook(elSearchText, "keypress");
form.onsubmit = null;
el = null;
};
//helper function
function argWithDefault(arg, def)
{
return (typeof arg != "undefined" ? arg : def)
}
}).as("Msn.SiteSearch");